skynet lua與c交互相關函數

我們都知道skynet底層是用c實現的,邏輯層用lua實現。那麼c底層是怎麼樣與lua層實現交互的呢?這篇文章將會講解這方面的內容。

系列文章中的skynet怎麼啓動lua文件講到了skynet是如何啓動lua代碼的,無非是調用lua api加載lua文件。有個QQ羣友問到c底層究竟是如何調用到lua層的回調函數的,正好這篇文章會分析這個。

加載我們的lua邏輯文件後,一般lua 服務都會有一個主函數skynet.start(),開始執行。他會調用c層函數lcallback來設置服務的回調函數:

static int
lcallback(lua_State *L) {
    struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));
    int forward = lua_toboolean(L, 2);
    luaL_checktype(L,1,LUA_TFUNCTION);
    lua_settop(L,1);
    lua_rawsetp(L, LUA_REGISTRYINDEX, _cb);

    lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD);
    lua_State *gL = lua_tothread(L,-1);

    if (forward) {
        skynet_callback(context, gL, forward_cb);
    } else {
        skynet_callback(context, gL, _cb);
    }

    return 0;
}

關於第一句struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));的含義後面會有講到,這裏主要講解這段代碼的意思。首先檢查棧頂是否是個函數,並且講棧頂的函數保存在一個table中,這個table是一個內置的表stack[LUA_REGISTRYINDEX]。棧頂函數就是skynet.start()函數中c.callback的參數skynet.dispatch_message。

接下來就是設置回調函數爲_cb(forward爲null)。我們再看看_cb函數:

static int
_cb(struct skynet_context * context, void * ud, int type, int session, uint32_t source, const void * msg, size_t sz) {
    lua_State *L = ud;
    int trace = 1;
    int r;
    int top = lua_gettop(L);
    if (top == 0) {
        lua_pushcfunction(L, traceback);
        lua_rawgetp(L, LUA_REGISTRYINDEX, _cb);
    } else {
        assert(top == 2);
    }
    lua_pushvalue(L,2);              --把lua層回調函數壓棧

    lua_pushinteger(L, type);
    lua_pushlightuserdata(L, (void *)msg);
    lua_pushinteger(L,sz);
    lua_pushinteger(L, session);
    lua_pushinteger(L, source);

    r = lua_pcall(L, 5, 0 , trace);

    if (r == LUA_OK) {
        return 0;
    }
    ...
    ...
    return 0;
}

我們看到c層的回調函數會調用lua_rawgetp把我們先前保存的回調函數取出,然後壓棧。接下來就是壓入回調函數的參數,type,msg,sz,session,source,然後調用lua_pcall(L, 5, 0 , trace),他將會執行剛纔保存的lua層回調函數skynet.dispatch_message。至此我們就知道了c層是如何調用lua層的回調函數了。

上面提到的struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1))是什麼鬼呢?我們知道每個服務都有一個上下文context,當lua層調用c層的函數時,是如何關聯到那個context的呢?我們會想到在lua虛擬機的棧上來保存。沒錯,snlua服務初始化的時候,就把context保存在關聯的虛擬機棧上了:

static int
init_cb(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {
    lua_State *L = l->L;
    l->ctx = ctx;
    lua_gc(L, LUA_GCSTOP, 0);
    lua_pushboolean(L, 1);  /* signal for libraries to ignore env. vars. */
    lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");
    luaL_openlibs(L);
    lua_pushlightuserdata(L, ctx);
    lua_setfield(L, LUA_REGISTRYINDEX, "skynet_context");   //將context保存在棧的一個特殊表裏
    ...
}

按理說,在lua層調用c層函數時,只需要調用lua_getfield(L, LUA_REGISTRYINDEX, "skynet_context")取出該context就好了,但是這裏使用的是lua函數的upvalue技巧。有關lua函數的upvalue請看lua5.3中luaL_setfunc設置upvalue的用法示例。在設計函數時是利用函數upvalue來保存context:

LUAMOD_API int
luaopen_skynet_core(lua_State *L) {
    luaL_checkversion(L);
    luaL_Reg l[] = {
        { "send" , lsend },
        { "genid", lgenid },
        { "redirect", lredirect },
        { "command" , lcommand },
        { "intcommand", lintcommand },
        { "error", lerror },
        { "tostring", ltostring },
        { "harbor", lharbor },
        { "pack", luaseri_pack },
        { "unpack", luaseri_unpack },
        { "packstring", lpackstring },
        { "trash" , ltrash },
        { "callback", lcallback },
        { "now", lnow },
        { NULL, NULL },
    };

    luaL_newlibtable(L, l);
    lua_getfield(L, LUA_REGISTRYINDEX, "skynet_context");   //將保存的context入棧頂
    struct skynet_context *ctx = lua_touserdata(L,-1);
    if (ctx == NULL) {
        return luaL_error(L, "Init skynet context first");
    }
    luaL_setfuncs(L,l,1);
    return 1;
}

調用函數時利用lua_touserdata(L, lua_upvalueindex(1))來獲取context,例如上面的lcallback函數。

利用upvalue來存儲數據的地方還有一處,就是msgpack模塊:

int
luaopen_skynet_msgpack(lua_State *L) {
    luaL_checkversion(L);
    luaL_Reg l[] = {
        { "pop", lpop },
        { "pack", lpack },
        { "clear", lclear },
        { "tostring", ltostring },
        { NULL, NULL },
    };
    luaL_newlib(L,l);
    // the order is same with macros : TYPE_* (defined top)
    lua_pushliteral(L, "data");
    lua_pushliteral(L, "more");
    lua_pushliteral(L, "error");
    lua_pushliteral(L, "open");
    lua_pushliteral(L, "close");
    lua_pushliteral(L, "warning");

    lua_pushcclosure(L, lfilter, 6);   //壓入一個函數,這個函數帶6個upvalue
    lua_setfield(L, -2, "filter");     //將上面的luaL_Reg表添加一個函數filter
    return 1;
}

調用filter函數時,根據消息不同的類型,來壓入不同的upvalue:

static int
lfilter(lua_State *L) {
    struct skynet_socket_message *message = lua_touserdata(L,2);
    int size = luaL_checkinteger(L,3);
    char * buffer = message->buffer;
    if (buffer == NULL) {
        buffer = (char *)(message+1);
        size -= sizeof(*message);
    } else {
        size = -1;
    }

    lua_settop(L, 1);

    switch(message->type) {
    case SKYNET_SOCKET_TYPE_DATA:
        // ignore listen id (message->id)
        assert(size == -1); // never padding string
        return filter_data(L, message->id, (uint8_t *)buffer, message->ud);
    case SKYNET_SOCKET_TYPE_CONNECT:
        // ignore listen fd connect
        return 1;
    case SKYNET_SOCKET_TYPE_CLOSE:
        // no more data in fd (message->id)
        close_uncomplete(L, message->id);
        lua_pushvalue(L, lua_upvalueindex(TYPE_CLOSE));    //利用函數的upvalue
        lua_pushinteger(L, message->id);
        return 3;
    case SKYNET_SOCKET_TYPE_ACCEPT:
        lua_pushvalue(L, lua_upvalueindex(TYPE_OPEN));
        // ignore listen id (message->id);
        lua_pushinteger(L, message->ud);
        pushstring(L, buffer, size);
        return 4;
    case SKYNET_SOCKET_TYPE_ERROR:
        // no more data in fd (message->id)
        close_uncomplete(L, message->id);
        lua_pushvalue(L, lua_upvalueindex(TYPE_ERROR));
        lua_pushinteger(L, message->id);
        pushstring(L, buffer, size);
        return 4;
    case SKYNET_SOCKET_TYPE_WARNING:
        lua_pushvalue(L, lua_upvalueindex(TYPE_WARNING));
        lua_pushinteger(L, message->id);
        lua_pushinteger(L, message->ud);
        return 4;
    default:
        // never get here
        return 1;
    }
}

以上就是常用到的skynet lua層和c層交互的相關函數,還有其他的一些零散的函數,我們下次有空再講解。

 

歡迎加入QQ羣 858791125 討論skynet,遊戲後臺開發,lua腳本語言等問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章