skynet snlua分析

snlua 是所有lua服务的载体,负责加载lua脚本以及绑定lua消息回调。所有的lua服务其实都可以统称为snlua服务。

由之前的module文章可知,skynet启动一个C 服务,会自动调用C服务的create 和init函数。下面看看snlua的create和init分别做了什么。

snlua_create:创建lua虚拟机,创建snlua实例。

//service_snlua.lua
struct snlua {
    lua_State * L;               //lua虚拟机
    struct skynet_context * ctx; //服务contex
    size_t mem;                     //当前使用的内存
    size_t mem_report;             //内存警告临界值
    size_t mem_limit;             //内存使用上线
};

struct snlua *
snlua_create(void) {
    struct snlua * l = skynet_malloc(sizeof(*l));
    memset(l,0,sizeof(*l));
    l->mem_report = MEMORY_WARNING_REPORT;
    l->mem_limit = 0;
    l->L = lua_newstate(lalloc, l);
    return l;
}

snlua_init 设置snlua的消息处理函数为launch_cb。调用skynet_command时传入null这时并不是注册服务,而是返回服务的句柄。然后调用skynet_send给自己发了一个消息,消息内容是 init传入的参数。这是底层框架的消息分发就触发了launch_cb。
在launch_cb中先是skynet_callback(context, NULL, NULL);讲回调函数设置为空,然后执行init_cb。可以看到这个launch_cb只是一个临时的回调函数

static int
launch_cb(struct skynet_context * context, void *ud, int type, int session, uint32_t source , const void * msg, size_t sz) {
    assert(type == 0 && session == 0);
    struct snlua *l = ud;
    skynet_callback(context, NULL, NULL);
    int err = init_cb(l, context, msg, sz);
    if (err) {
        skynet_command(context, "EXIT", NULL);
    }

    return 0;
}

int
snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) {
    int sz = strlen(args);
    char * tmp = skynet_malloc(sz);
    memcpy(tmp, args, sz);
    skynet_callback(ctx, l , launch_cb);
    const char * self = skynet_command(ctx, "REG", NULL);
    uint32_t handle_id = strtoul(self+1, NULL, 16);
    // it must be first message
    skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);
    return 0;
}

在init_cb中,首先从配置的lua虚拟机中获取到全绝配置信息,然后将配置信息加载到我们服务的lua虚拟机中。然后加载并执行loader.lua脚本。最后获取在lua脚本中设置的memlimit更改默认的memlimit。脚本中可以通过skynet.memlimit(bytes) 来设置memlimit,必须在启动脚本中设置且只能设置一次。

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");
    luaL_requiref(L, "skynet.codecache", codecache , 0);
    lua_pop(L,1);

    const char *path = optstring(ctx, "lua_path","./lualib/?.lua;./lualib/?/init.lua");
    lua_pushstring(L, path);
    lua_setglobal(L, "LUA_PATH");
    const char *cpath = optstring(ctx, "lua_cpath","./luaclib/?.so");
    lua_pushstring(L, cpath);
    lua_setglobal(L, "LUA_CPATH");
    const char *service = optstring(ctx, "luaservice", "./service/?.lua");
    lua_pushstring(L, service);
    lua_setglobal(L, "LUA_SERVICE");
    const char *preload = skynet_command(ctx, "GETENV", "preload");
    lua_pushstring(L, preload);
    lua_setglobal(L, "LUA_PRELOAD");

    lua_pushcfunction(L, traceback);
    assert(lua_gettop(L) == 1);

    const char * loader = optstring(ctx, "lualoader", "./lualib/loader.lua");

    int r = luaL_loadfile(L,loader);
    if (r != LUA_OK) {
        skynet_error(ctx, "Can't load %s : %s", loader, lua_tostring(L, -1));
        report_launcher_error(ctx);
        return 1;
    }
    lua_pushlstring(L, args, sz);
    r = lua_pcall(L,1,0,1);
    if (r != LUA_OK) {
        skynet_error(ctx, "lua loader error : %s", lua_tostring(L, -1));
        report_launcher_error(ctx);
        return 1;
    }
    lua_settop(L,0);
    if (lua_getfield(L, LUA_REGISTRYINDEX, "memlimit") == LUA_TNUMBER) {
        size_t limit = lua_tointeger(L, -1);
        l->mem_limit = limit;
        skynet_error(ctx, "Set memory limit to %.2f M", (float)limit / (1024 * 1024));
        lua_pushnil(L);
        lua_setfield(L, LUA_REGISTRYINDEX, "memlimit");
    }
    lua_pop(L, 1);

    lua_gc(L, LUA_GCRESTART, 0);

    return 0;
}

function skynet.memlimit(bytes)
    debug.getregistry().memlimit = bytes
    skynet.memlimit = nil    -- set only once
end


首先获取参数和要启动的服务名,然后在LUA_SERVICE路径中搜索 server_name.lua文件。搜索到则加载文件。然后如果有LUA_PRELOAD预处理lua文件,先执行预处理文件,在执行server_name.lua 文件。

//loader.lua
local args = {}
for word in string.gmatch(..., "%S+") do
    table.insert(args, word)
end

SERVICE_NAME = args[1]

local main, pattern

local err = {}
for pat in string.gmatch(LUA_SERVICE, "([^;]+);*") do
    local filename = string.gsub(pat, "?", SERVICE_NAME)
    local f, msg = loadfile(filename)
    if not f then
        table.insert(err, msg)
    else
        pattern = pat
        main = f
        break
    end
end

if not main then
    error(table.concat(err, "\n"))
end

LUA_SERVICE = nil
package.path , LUA_PATH = LUA_PATH
package.cpath , LUA_CPATH = LUA_CPATH

local service_path = string.match(pattern, "(.*/)[^/?]+$")

if service_path then
    service_path = string.gsub(service_path, "?", args[1])
    package.path = service_path .. "?.lua;" .. package.path
    SERVICE_PATH = service_path
else
    local p = string.match(pattern, "(.*/).+$")
    SERVICE_PATH = p
end

if LUA_PRELOAD then
    local f = assert(loadfile(LUA_PRELOAD))
    f(table.unpack(args))
    LUA_PRELOAD = nil
end

main(select(2, table.unpack(args)))

lua服务的回调函数是怎么绑定的呢??
我们可以注意到所有的lua服务都会执行skynet.start(),在skynet.start中,c.callback(skynet.dispatch_message) 设置回调函数为skynet.dispatch_message,然后启动了一个0秒定时器绑定定时器的回调函数为start_func,前面说过0秒的定时器就是直接给这个服务发送一条消息。所以start_func 会立刻执行。

function skynet.start(start_func)
    c.callback(skynet.dispatch_message)
    init_thread = skynet.timeout(0, function()
        skynet.init_service(start_func)
        init_thread = nil
    end)
end


c.callback 最终会执行C 代码中的lcallback,lcallback的upvalue是调用服务的context,然后设置栈顶为1,这时服务的lua虚拟机的栈就只有一个dispatch_message 函数,设置服务的回调函数为_cb/forward_cb。当服务处理消息时会执行_cb/forward_cb。此时栈顶只有一个dispatch_message函数,_cb/forward_cb压入消息参数,调用skynet.dispatch_message函数就可以在lua层来分发处理消息了。

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;
}

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_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);
    ......
}

每个lua的服务都要require "skynet" 加载 skynet.lua 文件,而在skynet.lua 中会执行 local c = require "skynet.core" ,加载C库。可以看到在导出函数时,lua_getfield(L, LUA_REGISTRYINDEX, "skynet_context"); luaL_setfuncs(L,l,1); 将skynet_context服务的context作为导出函数的upvalue,所以我们lcallback中才可以通过upvalue获取到skynet_context。

LUAMOD_API int
luaopen_skynet_core(lua_State *L) {
    luaL_checkversion(L);

    luaL_Reg l[] = {di
    { "callback", lcallback },
        ...
    };
    ...
    lua_createtable(L, 0, sizeof(l)/sizeof(l[0]) + sizeof(l2)/sizeof(l2[0]) -2);
    lua_getfield(L, LUA_REGISTRYINDEX, "skynet_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);

    luaL_setfuncs(L,l2,0);

    return 1;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值