skynet的啟動時需帶個配置文件,這個文件其實是作為lua全局變量用的,見
int main(int argc, char *argv[]) { const char * config_file = NULL ; if (argc > 1) { config_file = argv[1]; } else { fprintf(stderr, "Need a config file. Please read skynet wiki : https://github.com/cloudwu/skynet/wiki/Config\n" "usage: skynet configfilename\n"); return 1; } skynet_globalinit(); skynet_env_init(); sigign(); struct skynet_config config; struct lua_State *L = lua_newstate(skynet_lalloc, NULL); luaL_openlibs(L); // link lua lib int err = luaL_loadstring(L, load_config); assert(err == LUA_OK); lua_pushstring(L, config_file); err = lua_pcall(L, 1, 1, 0); if (err) { fprintf(stderr,"%s\n",lua_tostring(L,-1)); lua_close(L); return 1; } _init_env(L); config.thread = optint("thread",8); config.module_path = optstring("cpath","./cservice/?.so"); config.harbor = optint("harbor", 1); config.bootstrap = optstring("bootstrap","snlua bootstrap"); config.daemon = optstring("daemon", NULL); config.logger = optstring("logger", NULL); lua_close(L); skynet_start(&config); skynet_globalexit(); return 0; }
配置了一些基本的環境變量后,轉到skynet_start方法,開始啟動skynet,在skynet_start方法中初始化一些變量后,系統啟動的第一個服務是logger:
struct skynet_context *ctx = skynet_context_new("logger", config->logger); if (ctx == NULL) { fprintf(stderr, "Can't launch logger service\n"); exit(1); }
skynet通過skynet_context_new函數來實例化一個服務:先是從logger.so文件把模塊加載進來;
struct skynet_module * mod = skynet_module_query(name); if (mod == NULL) return NULL;
讓模塊自生成一個新的實例;
void *inst = skynet_module_instance_create(mod);
分配一個新的handle;
ctx->handle = skynet_handle_register(ctx);
初始化一個消息隊列;
struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle);
調用這個模塊的初始化方法
int r = skynet_module_instance_init(mod, inst, ctx, param);
最后是把自己的消息隊列加入到全局消息隊列中,把有加入到全局的消息隊列后,才能收到消息回調
skynet_globalmq_push(queue);
啟動完成logger服務后,系統接下來要啟動的服務是bootstrap,但先要加載snlua模塊,所有的lua服務都屬於snlua模塊的實例。
static void bootstrap(struct skynet_context * logger, const char * cmdline) { int sz = strlen(cmdline); char name[sz+1]; char args[sz+1]; sscanf(cmdline, "%s %s", name, args); struct skynet_context *ctx = skynet_context_new(name, args); if (ctx == NULL) { skynet_error(NULL, "Bootstrap error : %s\n", cmdline); skynet_context_dispatchall(logger); exit(1); } }
其中參數cmdline是在config配置里的
bootstrap = "snlua bootstrap"
和加載logger服務類似,先是把snlua.so文件作為模塊加載進來,調用模塊自身的_create函數產生一個snlua實例,在service_snlua.c文件中。
struct snlua * snlua_create(void) { struct snlua * l = skynet_malloc(sizeof(*l)); memset(l,0,sizeof(*l)); l->L = lua_newstate(skynet_lalloc, NULL); return l; }
在方法中啟動了新生成了一個lua VM,出就是lua沙盒環境,這一點也比較重要,因為所有的lua服務都是是一個獨立的VM中運行的,這也是雲風的設計初衷。
接下來就會調用了service_snlua.c中的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); 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; }
來初始化服務,在這個方法中做了兩件事情;
注冊了一個回調函數,當有消息到來時,這個函數會被調用
skynet_callback(ctx, l , _launch);
向自己發送了一條消息,並附帶了一個參數,這個參數就是bootstrap。當把消息隊列加入到全局隊列后,收到的第一條消息就是這條消息。
收到第一條消息后,調用到callback函數,也就是service_snlua.c里的_launch方法
static int _launch(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(l, context, msg, sz); if (err) { skynet_command(context, "EXIT", NULL); } return 0; }
這個方法里把自己的回調函數給注銷了,使它不再接收消息,為的是在lua層重新注冊它,把消息通過lua接口來接收。緊接着執行_init方法。在_init方法里設置了一些虛擬機環境變量后,就加載執行了loader.lua文件,同時要把真正要加載的文件(這個時候是bootstrap)作為參數傳給它, 控制權就開始轉到lua層。
loader.lua是用來加載lua文件的,在loader.lua中會判斷是否需要preload,最終會加載執行bootstrap.lua文件:
local skynet = require "skynet" local harbor = require "skynet.harbor" skynet.start(function() local standalone = skynet.getenv "standalone" local launcher = assert(skynet.launch("snlua","launcher")) skynet.name(".launcher", launcher) local harbor_id = tonumber(skynet.getenv "harbor") if harbor_id == 0 then assert(standalone == nil) standalone = true skynet.setenv("standalone", "true") local ok, slave = pcall(skynet.newservice, "cdummy") if not ok then skynet.abort() end skynet.name(".cslave", slave) else if standalone then if not pcall(skynet.newservice,"cmaster") then skynet.abort() end end local ok, slave = pcall(skynet.newservice, "cslave") if not ok then skynet.abort() end skynet.name(".cslave", slave) end if standalone then local datacenter = skynet.newservice "datacenterd" skynet.name("DATACENTER", datacenter) end skynet.newservice "service_mgr" pcall(skynet.newservice,skynet.getenv "start" or "main") skynet.exit() end)
在這個文件里啟動了其它一些服務,這些暫不看,在這個文件里調用了服務啟動的接口skynet.start。這也是所有lua服務的標准啟動入口,參數是一個回調方法,服務啟動完成后會調到這個方法。做一些初始化的工作。
skynet.lua文件的start方法:
function skynet.start(start_func) c.callback(dispatch_message) skynet.timeout(0, function() init_service(start_func) end) end
通過
c.callback(dispatch_message)
重新注冊了callback函數,這樣就能在lua接收消息了。收到消息時,通過dispatch_message方法來分發。
c.callback調用的是一個c函數,在lua-skynet.c文件的_callback方法。
static int _callback(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; }
在這個方法中可以看到,重新調用了skynet_callback來注冊服務的回調函數。
到此,一個lua編寫的服務就啟動起來了。