上一篇分析之后,本来第二部分分析是想分析start之后的逻辑的,这样会让人比较快速的理解skynet框架。但想想还是顺着代码启动的思路写下去会比较好,我觉得这样我自己更容易理解。
bootstrap是引导程序的意思,在skynet中,的确也是做了服务器工作的前置任务。
再skynet_start.c中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 //启动logger服务 struct skynet_context *ctx = skynet_context_new(config->logservice, config->logger); if (ctx == NULL) { fprintf(stderr, "Can't launch %s service\n", config->logservice); exit(1); } skynet_handle_namehandle(skynet_context_handle(ctx), "logger"); //启动配置中的bootstrap服务 bootstrap(ctx, config->bootstrap); //调用start传入配置线程数量 start(config->thread);
查询配置
可以得知,传入的参数是”snlua bootsrtap”,再看到函数的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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); //name = snlua, args = bootstrap 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); } }
接下来看到skynet_context_new方法,在skynet_server.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 struct skynet_context * skynet_context_new(const char * name, const char *param) { //先查询snlua模块,这是一个C写的服务,在skynet_start.c中 //skynet_statc方法,skynet_module_init(config->module_path)进行初始化 struct skynet_module * mod = skynet_module_query(name); if (mod == NULL) return NULL; //分析1 //这里创建一个新的lua虚拟机,所以会有各个服务的隔离 void *inst = skynet_module_instance_create(mod); if (inst == NULL) return NULL; struct skynet_context * ctx = skynet_malloc(sizeof(*ctx)); CHECKCALLING_INIT(ctx) ctx->mod = mod; ctx->instance = inst; ctx->ref = 2; ctx->cb = NULL; ctx->cb_ud = NULL; ctx->session_id = 0; ctx->logfile = NULL; ctx->init = false; ctx->endless = false; ctx->cpu_cost = 0; ctx->cpu_start = 0; ctx->message_count = 0; ctx->profile = G_NODE.profile; // Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handle ctx->handle = 0; ctx->handle = skynet_handle_register(ctx); struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle); // init function maybe use ctx->handle, so it must init at last context_inc(); CHECKCALLING_BEGIN(ctx) //分析2 //这里使用snlua,启动传入的bootstrap.lua int r = skynet_module_instance_init(mod, inst, ctx, param); CHECKCALLING_END(ctx) if (r == 0) { struct skynet_context * ret = skynet_context_release(ctx); if (ret) { ctx->init = true; } skynet_globalmq_push(queue); if (ret) { skynet_error(ret, "LAUNCH %s %s", name, param ? param : ""); } return ret; } else { skynet_error(ctx, "FAILED launch %s", name); uint32_t handle = ctx->handle; skynet_context_release(ctx); skynet_handle_retire(handle); struct drop_t d = { handle }; skynet_mq_release(queue, drop_message, &d); return NULL; } }
按照分析1、分析2的顺序。先看到skynet_module_instance_create,分析1
1 2 3 4 5 6 7 8 void * skynet_module_instance_create(struct skynet_module *m) { if (m->create) { return m->create(); // 调用c模块的create } else { return (void *)(intptr_t)(~0); } }
看到snlua的create,再service_snlua.c,构建新的lua虚拟机,所以有服务之间的隔离。
1 2 3 4 5 6 7 8 9 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); // 这里构建了新的lua虚拟机 return l; }
创建完虚拟机后,看到分析2,到skynet_module_instance_init,在skynet_module.c中,可以看到,其实就是上面代码注释说的,使用snlua的init去启动bootstarp
1 2 3 4 int skynet_module_instance_init(struct skynet_module *m, void * inst, struct skynet_context *ctx, const char * parm) { return m->init(inst, ctx, parm); }
接下来我们到service_snlua.lua中看是怎么使用snlua去启动一个lua脚本的,skynet中,C模块的定义,必须要有一些方法,比如init,上面的m->init,最后会调用到下面的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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); //给需要初始化的服务设置回调函数为上面的luanch_cb 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 // 给服务发送消息触发上面的launch_cb // 这里先知道触发上面,具体的消息处理我们的分析会说明 // 给服务发送消息之后,具体的服务是怎么去处理的 skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz); return 0; }
发送消息后,调用到luanch_cb
1 2 3 4 5 6 7 8 9 10 11 12 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; }
最后调用init_cb,下面是精简了的代码,只是为了说明调用流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 static int init_cb(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) { ...... //这里,就是使用loader.lua去加载我们的bootstrap 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层面。 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; }
启动了bootstrap又怎样呢,往下看bootstrap.lua
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 skynet.start(function() local sharestring = tonumber(skynet.getenv "sharestring" or 4096) memory.ssexpand(sharestring) local standalone = skynet.getenv "standalone" // 这里又用snlua去启动了launcher.lua,启动过程和bootstrap一样 // 这个launcher服务先记住,待会儿就知道干嘛的了 local launcher = assert(skynet.launch("snlua","launcher")) skynet.name(".launcher", launcher) // 这下面还有一些是bootstrap这个前置任务做的 // 这里skynet.newservice是啥? ...... skynet.newservice "service_mgr" pcall(skynet.newservice,skynet.getenv "start" or "main") skynet.exit()// 启动完上面的,完成了任务,这个服务就退出了 end)
看到skynet.newservice,这个在skynet中,就是lua层用来启动新服务的。在skynet.lua中,可以看到,call .launcher就是使用上面启动的launcher.lua去启动一个服务
1 2 3 4 5 function skynet.newservice(name, ...) // 这个参数 "LAUNCH", "snlua", name, ...特别注意一下 // 最后也是用调用snlua去启动一个lua服务 return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...) end
在看到launcher中的LAUNCH,在launcher.lua中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 require "skynet.manager" -- import manager apis local function launch_service(service, ...) local param = table.concat({...}, " ") local inst = skynet.launch(service, param) local session = skynet.context() local response = skynet.response() if inst then services[inst] = service .. " " .. param instance[inst] = response launch_session[inst] = session else response(false) return end return inst end function command.LAUNCH(_, service, ...) launch_service(service, ...) return NORET end
下面看到skyne.launch,在manager.lua
1 2 3 4 5 6 7 8 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
调用到c.command,
这里的skynet.core是一个C语言模块,至此,我们将进入C语言实现部分,调用skynet.core.command(“LAUNCH”, “snlua …”)。
我们先总结一下lua部分的内容:
newservice–>skynet.call .launcher–>.launcher=skynet.launch(“snlua”, “launcher”)–>skynet.core.command(“LAUNCH”, “snlua …”)
skynet.core其实是在lua_skynet.c中定义的,其command对应于lcommand函数。 这时的参数其实都压进了lua_State中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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); } // 这里就是调用skynet_server.c result = skynet_command(context, cmd, parm); if (result) { lua_pushstring(L, result); return 1; } return 0; }
也就最后调用到skynet_server.c中skynet_command
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 static struct command_func cmd_funcs[] = { { "TIMEOUT", cmd_timeout }, { "REG", cmd_reg }, { "QUERY", cmd_query }, { "NAME", cmd_name }, { "EXIT", cmd_exit }, { "KILL", cmd_kill }, { "LAUNCH", cmd_launch }, //LAUNCH对应这个 { "GETENV", cmd_getenv }, { "SETENV", cmd_setenv }, { "STARTTIME", cmd_starttime }, { "ABORT", cmd_abort }, { "MONITOR", cmd_monitor }, { "STAT", cmd_stat }, { "LOGON", cmd_logon }, { "LOGOFF", cmd_logoff }, { "SIGNAL", cmd_signal }, { 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; }
再看到cmd_launch,这里就十分熟悉了,接入回上面的bootstrap的分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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, " \t\r\n"); args = strsep(&args, "\r\n"); // 这里这几行是不是特别熟悉 // 没错,就是和上面的bootstrap的启动一模一样 // 就是使用snlua去启动另外的服务 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; } }
可以看到,之后我们在lua层使用的skynet.newservice都是通过launcher.lua去启动新服务器的了。
最后bootstrap做的事情中一个重要就是,启动了launcher.lua服务,之后框架中skynet.newservice就是调用launcher去启动lua服务。
所以叫他服务器的前置任务。之后的启动新服务之后,回调函数怎么挂钩之类的,之后在分析。
下一篇分享bootstrap后skynet_start做了什么