skynet源码分析之snlua服务的启动流程(一)(转)
skynet绝大部分服务类型是snlua,它是运行Lua脚本的服务,在用skynet框架上开发游戏服务器时,大部分逻辑都是snlua服务,90%以上只需写Lua代码即可,所以很有必要了解snlua服务相关内容。由于篇幅较多,打算分三篇文章介绍,都写完后再一起发布出去。本篇主要介绍snlua服务的启动流程,相关代码主要在service-src/service_snlua.c,lualib-src/lua-skynet.c,lualib/skynet.lua,lualib/loader.lua。bootstrap服务是skynet启动时创建的第一个snlua服务,以bootstrap为例说明snlua服务的启动流程。
1 // skynet-src/skynet_start.c
2 static void
3 bootstrap(struct skynet_context * logger, const char * cmdline) {
4 ...
5 struct skynet_context *ctx = skynet_context_new(name, args); // name="snlua" args="bootstrap"
6 ...
7 }
创建一个snlua类型的ctx,会调用snlua_init,注册消息回调函数launch_cb(第7行),然后给自己发第一条消息(第11行),至此ctx创建完成,但snlua服务的初始流程还未完成。
1 // service-src/service_snlua.c
2 int
3 snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) {
4 int sz = strlen(args);
5 char * tmp = skynet_malloc(sz);
6 memcpy(tmp, args, sz);
7 skynet_callback(ctx, l , launch_cb);
8 const char * self = skynet_command(ctx, "REG", NULL);
9 uint32_t handle_id = strtoul(self+1, NULL, 16);
10 // it must be first message
11 skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);
12 return 0;
13 }
服务收到第一条消息后,先把消息回调函数至为NULL(之前设置的回调函数已失效,之后在Lua层会重新设置),然后调用消息回调函数init_cb,
1 // service-src/service_snlua.c
2 static int
3 launch_cb(struct skynet_context * context, void *ud, int type, int session, uint32_t source , const void * msg, size_t sz) {
4 ...
5 skynet_callback(context, NULL, NULL);
6 int err = init_cb(l, context, msg, sz);
7 ...
8 }
在init_cb里进行Lua层的初始化,比如初始化LUA_PATH,LUA_CPATH,LUA_SERVICE等全局变量,主要有几个点:
1. 第7,8行,将ctx设置到LUA_REGISTRYINDEX里,以便在C与Lua的交互中可以获取到ctx
2. 10-12行,设置全局变量LUA_PRELOAD
3. 18行,加载loader.lua脚本
4. 25行,运行loader.lua,参数是“bootstrap”
1 // service-src/service_snlua.c
2 static int
3 init_cb(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {
4 lua_State *L = l->L;
5 l->ctx = ctx;
6 ...
7 lua_pushlightuserdata(L, ctx);
8 lua_setfield(L, LUA_REGISTRYINDEX, "skynet_context");
9 ...
10 const char *preload = skynet_command(ctx, "GETENV", "preload");
11 lua_pushstring(L, preload);
12 lua_setglobal(L, "LUA_PRELOAD");
13
14 lua_pushcfunction(L, traceback);
15 assert(lua_gettop(L) == 1);
16
17 const char * loader = optstring(ctx, "lualoader", "./lualib/loader.lua");
18 int r = luaL_loadfile(L,loader);
19 if (r != LUA_OK) {
20 skynet_error(ctx, "Can't load %s : %s", loader, lua_tostring(L, -1));
21 report_launcher_error(ctx);
22 return 1;
23 }
24 lua_pushlstring(L, args, sz);
25 r = lua_pcall(L,1,0,1);
26 if (r != LUA_OK) {
27 skynet_error(ctx, "lua loader error : %s", lua_tostring(L, -1));
28 report_launcher_error(ctx);
29 return 1;
30 }
31 ...
32 return 0;
33 }
在loader.lua里,主要做几点:
1. 第7行,设置全局变量SERVICE_NAME,因此在Lua层可以用SERVICE_NAME获取当前服务的名称
2. 11-22行,获取需启动的服务的Lua脚本(比如bootstrap.lua)的路径,并加载它(loadfile)
3. 24-28行,如果skynet启动配置里设置了LUA_PRELOAD,加载并运行它。每个snlua服务都加载了LUA_PRELOAD,所以经常把一个游戏里一些公用的配置放到LUA_PRELOAD里
4. 30行,运行Lua服务的入口脚本,比如bootstrap.lua,除第一个参数以外的所有参数(第一个参数是服务的名称)
1 -- lualib/loader.lua
2 local args = {}
3 for word in string.gmatch(..., "%S+") do
4 table.insert(args, word)
5 end
6
7 SERVICE_NAME = args[1]
8
9 local main, pattern
10
11 local err = {}
12 for pat in string.gmatch(LUA_SERVICE, "([^;]+);*") do
13 local filename = string.gsub(pat, "?", SERVICE_NAME)
14 local f, msg = loadfile(filename)
15 if not f then
16 table.insert(err, msg)
17 else
18 pattern = pat
19 main = f
20 break
21 end
22 end
23 ...
24 if LUA_PRELOAD then
25 local f = assert(loadfile(LUA_PRELOAD))
26 f(table.unpack(args))
27 LUA_PRELOAD = nil
28 end
29
30 main(select(2, table.unpack(args)))
Lua服务的入口脚本必须包含2点:1. require "skynet",这样才能使用skynet.lua里的接口;2. 调用skyne.start函数
1 local skynet = require "skynet" 2 skynet.start(function() 3 ... 4 end)
skynet.lua提供了很多api供Lua服务调用,第1行代码是local c = require "skynet.core",skynet.core是由C编写的so库,so库里提供很多api供Lua层调用(lualib-src/lua-skynet.c)。require "skynet"过程中还做了其他事情放在下一篇介绍。
第6行,提供了很多注册函数供Lua层调用
26-31行,从LUA_REGISTERINDEX表中获取ctx(在init_cb里设置的),这些注册函数共用ctx这个上值,在C api里通过lua_upvalueindex(1)获取这个ctx,然后对ctx进行相应处理。
1 // lualib-src/lua-skynet.c
2 LUAMOD_API int
3 luaopen_skynet_core(lua_State *L) {
4 uaL_checkversion(L);
5
6 luaL_Reg l[] = {
7 { "send" , lsend },
8 { "genid", lgenid },
9 { "redirect", lredirect },
10 { "command" , lcommand },
11 { "intcommand", lintcommand },
12 { "error", lerror },
13 { "tostring", ltostring },
14 { "harbor", lharbor },
15 { "pack", luaseri_pack },
16 { "unpack", luaseri_unpack },
17 { "packstring", lpackstring },
18 { "trash" , ltrash },
19 { "callback", lcallback },
20 { "now", lnow },
21 { NULL, NULL },
22 };
23
24 luaL_newlibtable(L, l);
25
26 lua_getfield(L, LUA_REGISTRYINDEX, "skynet_context");
27 struct skynet_context *ctx = lua_touserdata(L,-1);
28 if (ctx == NULL) {
29 return luaL_error(L, "Init skynet context first");
30 }
31 luaL_setfuncs(L,l,1);
32
33 return 1;
34 }
Lua服务入口的第二件事是调用skynet.start,重新设置消息回调函数(第3行,之前设置的launch_cb回调函数已经失效了)
1 -- lualib/skynet.lua 2 function skynet.start(start_func) 3 c.callback(skynet.dispatch_message) 4 ... 5 end
调用C层的lcallback,通过lua_upvalueindex获取函数的上值ctx,然后设置服务的消息回调函数为_cb,此时Lua堆栈上有且仅有一个元素lua函数(skynet.dispatch_message)
1 // lualib/lua-skynet.c
2 static int
3 lcallback(lua_State *L) {
4 struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));
5 int forward = lua_toboolean(L, 2);
6 luaL_checktype(L,1,LUA_TFUNCTION);
7 lua_settop(L,1);
8 lua_rawsetp(L, LUA_REGISTRYINDEX, _cb);
9
10 lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD);
11 lua_State *gL = lua_tothread(L,-1);
12
13 if (forward) {
14 skynet_callback(context, gL, forward_cb);
15 } else {
16 skynet_callback(context, gL, _cb);
17 }
18
19 return 0;
20 }
在_cb里,最终会调用Lua层的dispatch_message,参数依次是:type, msg, sz, session, source。所以,snlua类型的服务收到消息时最终会调用Lua层的消息回调函数skynet.dispatch_message。
1 // lualib/lua-skynet.c
2 static int
3 _cb(struct skynet_context * context, void * ud, int type, int session, uint32_t source, const void * msg, size_t sz) {
4 ...
5 lua_pushinteger(L, type);
6 lua_pushlightuserdata(L, (void *)msg);
7 lua_pushinteger(L,sz);
8 lua_pushinteger(L, session);
9 lua_pushinteger(L, source);
10
11 r = lua_pcall(L, 5, 0 , trace);
12 ...
13 end
这就是snlua服务的启动流程。除了以上介绍,剩余的一些事情放到下一篇介绍,比如require "skynet"过程中还处理了额外的事情。

浙公网安备 33010602011771号