点击上方蓝色文字 “后端技术小黑屋” 关注微信公众号吧
框架是由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