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