点击上方蓝色文字 “后端技术小黑屋” 关注微信公众号吧

框架是由C语言写成的,而的服务可以完全用lua语言来编写,灵活性很高。那是如何启动一个lua语言编写的服务的的呢?服务,在框架中,具体是以什么形式存在的呢?

带着这些问题,我们来一起看看的源码。

lua部分

我们先看一个中启动agent服务的例子:

local function new_agent()
    return skynet.newservice "agent"
end

.的实现也很简单:

function skynet.newservice(name, ...)
    return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...)
end

这里,.是一个服务的名字,后面都是通过.call传给.的参数。

我们先来看看这个.是个什么鬼。

local launcher = assert(skynet.launch("snlua","launcher"))
skynet.name(".launcher", launcher)

通过wiki我们知道,.lua是服务的启动入口,在这里,调用了.,启动了一个服务,并将其命名为.。

.的实现如下:

local c = require "skynet.core"

function skynet.launch(...)    local addr = c.command("LAUNCH", table.concat({...}," "))
   if addr then        return tonumber("0x" .. string.sub(addr , 2))    
   end
end

这里的.core是一个C语言模块,至此,我们将进入C语言实现部分,调用mand(“”, “snlua ”)。不要晕,我们先总结一下lua部分的内容:

→.call .→.=.(“snlua”, “”)→mand(“”, “snlua ”)

接下来我们看看.core中是如何实现的。

C部分

.core其实是在.c中定义的,其对应于函数。 这时的参数其实都压进了中。

static int
lcommand(lua_State *L) {    
   struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));
   const char * cmd = luaL_checkstring(L,1);
   const char * result;
   const char * parm = NULL;
   if (lua_gettop(L) == 2) {        parm = luaL_checkstring(L,2);    }    result = skynet_command(context, cmd, parm);
   if (result) {        lua_pushstring(L, result);
       return 1;    }    
   return 0; }

在中,cmd应该是,parm应该是 snlua 。暂时按下不表,来看看的实现。

static struct command_func cmd_funcs[] = {
    { "TIMEOUT", cmd_timeout },
    ...
    { "LAUNCH", cmd_launch },
    ...
    { NULL, NULL },
};

const char * skynet_command(struct skynet_context * context, const char * cmd , const char * param) {
   struct command_func * method = &cmd_funcs[0];
   while(method->name) {
       if (strcmp(cmd, method->name) == 0) {
           return method->func(context, param);        }        ++method;    }
   return NULL; }

所以,这里会转发到函数。

static const char *
cmd_launch(struct skynet_context * context, const char * param) {
    size_t sz = strlen(param);
   char tmp[sz+1];
   strcpy(tmp,param);
   char * args = tmp;
   char * mod = strsep(&args, " trn");    args = strsep(&args, "rn");
   struct skynet_context * inst = skynet_context_new(mod,args);
   if (inst == NULL) {
       return NULL;    } else {        id_to_hex(context->result, inst->handle);
       return context->result;    } }

在中,mod是snlua,args是“snlua ”,会根据这两个参数,重新构造一个出来。

的函数体比较长,其中重要的步骤包括:

根据参数实例化一个模块(这里是snlua)

创建此服务的消息队列

根据参数,初始化前面创建的模块(snlua)

在第1步中,加载模块(snlua)并调用了模块的函数。

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

这里,新创建了一个。因此,正如wiki中所说,snlua是lua的一个沙盒服务,保证了各个lua服务之间是隔离的。

而第3步,其实是调用了snlua模块的init函数。

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

这里,设置了当前模块的为,因此之后消息,将由处理。

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

这里,重置了服务的(因为snlua只用负责在沙盒中启动lua服务,其他消息应由lua程序处理),之后,调用了。

中除了设置各种路径、栈数据之外,和我们关心的lua程序有关的,是这样的一行:

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

这里,就又通过C语言的lua接口,调用回了lua层面。

总结一下,C语言层面的处理流程是这样的:

mand→→→→→→→.lua

回到lua

.lua的功能也很简单,就是在沙盒snlua中,加载并执行lua程序,这里也就是.lua。

在.lua中,通过.和.,设置了服务对各种消息的处理函数。而在.start的调用中:

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

这里又重新设置了服务的。所以,所谓启动一个服务,其实就是将用lua编写的若干回调函数,挂载到对消息队列的处理中去。具体到这里的服务,其实就是在lua层面,将.等lua函数,挂接到消息队列中的等消息的回调函数。

还没完

到这里,我们看到了.lua这个服务,也就是.这个服务是如何建立的了。但是最开始,我们要考察的是agent.lua服务啊。回过头来,我们再看看agent服务是如何创建的:

local function new_agent()
    return skynet.newservice "agent"
end

function skynet.newservice(name, ...)
    return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...)
end

现在,我们知道.从何而来,那就直接看看消息是如何处理的吧:

function command.LAUNCH(_, service, ...)
    launch_service(service, ...)
   return NORET
end

在中,调用到了.。在最开始就看到了,.的创建,也是通过调用.实现的。这样,通过.创建一个服务,和创建.的服务的流程,就统一起来了。

.是一个用于创建其他lua服务的服务。

最初的最初

作为创建服务的服务.,它自己又是被谁创建的呢?前面我们看到.skynet,它是在.lua中创建出来的。那么.lua又是什么时候启动的呢?

这里,我们需要看一下启动时的第一个函数入口,main函数。

main函数隐藏在.c中,当其解析完传入的文件之后,就转到了。

在函数中,调用了(ctx, ->),其中,就像前面讲到的流程一样.skynet,直接走到了这一步。

一般默认,->项就是snlua 。那这里其实就是启动调用.lua,,其中会启动服务。有了服务,之后的服务启动,就都可以交由服务来进行了。


限时特惠:
本站持续每日更新海量各大内部创业课程,一年会员仅需要98元,全站资源免费下载
点击查看详情

站长微信:Jiucxh

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注