本文主要是介绍skynet 配置中lua服务创建流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
众所周知,skynet必须配置启动脚本,比如说如下配置
thread=8
logger=nil
harbor=0
start="main"
lua_path="./skynet/lualib/?.lua;./skynet/lualib/?/init.lua;"
luaservice="./skynet/service/?.lua;./app/?.lua;"
lualoader="./skynet/lualib/loader.lua"
cpath="./skynet/cservice/?.so"
lua_cpath="./skynet/luaclib/?.so"
那么 start=“main” 就是skynet启动的第一个业务脚本。那么这个脚本启动的流程是什么样的呢?
首先需要通过 c服务 snlua去加载 loader.lua 脚本,传递给 loader.lua脚本的参数为 bootstrap。
void
skynet_start(struct skynet_config * config) {...bootstrap(ctx, config->bootstrap);...
}
由默认配置可知, config->bootstrap = “snlua bootstrap”
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);...
}
这里 name = “snlua",args = “bootstrap”,snlua就是一个c服务,编译成了 snlua.so,可在cservice文件夹中查看到,具体实现在 service_snlua.c中。下面来看看skynet_context_new(…)做了什么
struct skynet_context *
skynet_context_new(const char * name, const char *param) {struct skynet_module * mod = skynet_module_query(name);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;// Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handlectx->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 lastcontext_inc();CHECKCALLING_BEGIN(ctx)int r = skynet_module_instance_init(mod, inst, ctx, param);...
}
- skynet_module_instance_create(mod) 会调用到 service_snlua.c中的 snlua_create()创建一个 snlua对象,这个对象上有一个 Luastate对象
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;
}
- skynet_module_instance_init(mod, inst, ctx, param) 会调用到 service_snlua.c中的 snlua_init()函数,然后将 launch_cb 设置为回调,并向自己发了一条消息,工作线程检测到有消息时,会触发到这个回调
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);//注册回调函数为launch_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// 给自己发送一条消息,内容为args字符串skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);return 0;
}
- 看看这个回调如何处理,调用到 init_cb函数,准备去调用lua脚本
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);//设置各项资源路径参数,并加载loader.luaint err = init_cb(l, context, msg, sz);if (err) {skynet_command(context, "EXIT", NULL);}return 0;
}
- init_cb 将诸多变量设置到全局,并编译执行 loader.lua脚本
static int //初始化 cb
init_cb(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {...const char * loader = optstring(ctx, "lualoader", "./lualib/loader.lua");//编译 loader.luaint r = luaL_loadfile(L,loader);...lua_pushlstring(L, args, sz);//执行 loader.lua 并将参数 bootstrap 传入r = lua_pcall(L,1,0,1);...return 0;
}
由上面代码可以,首先会编译 loader.lua脚本,然后执行,并传入 bootstrap
5. 看看 loader.lua脚本怎么调用到 bootstrap.lua 的
local args = {}
for word in string.gmatch(..., "%S+") dotable.insert(args, word)print("word:", word)
endSERVICE_NAME = args[1]
local main, patternlocal err = {}
for pat in string.gmatch(LUA_SERVICE, "([^;]+);*") dolocal filename = string.gsub(pat, "?", SERVICE_NAME)local f, msg = loadfile(filename)if not f thentable.insert(err, msg)elsepattern = patmain = fbreakend
end...main(select(2, table.unpack(args)))
此时可以知道,会调用到 bootstrap.lua中
6. bootstrap.lua 中会创建多个 lua服务,包括 main,创建完毕后将自己退出
local skynet = require "skynet"
local harbor = require "skynet.harbor"
require "skynet.manager" -- import skynet.launch, ...
local memory = require "skynet.memory"skynet.start(function()...local launcher = assert(skynet.launch("snlua","launcher"))skynet.name(".launcher", launcher)skynet.newservice "service_mgr"pcall(skynet.newservice,skynet.getenv "start" or "main")skynet.exit()
end)
到这里可以看出,先启动了一个 launcher服务,然后会让这个 launcher服务启动第一个业务服务,也就是配置中的 start服务
具体流程如下:
launcher 服务的创建,在文件 manager.lua中可以看到 调用到底层接口,追下去可知,调用了 cmd_launch 接口
function skynet.launch(...)local addr = c.command("LAUNCH", table.concat({...}," "))if addr thenreturn tonumber("0x" .. string.sub(addr , 2))end
end
static struct command_func cmd_funcs[] = {...{ "LAUNCH", cmd_launch },...{ NULL, 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, " \t\r\n");args = strsep(&args, "\r\n");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;}
}
由上述可知,创建了一个 actor,同样依靠 loader.lua脚本,参数为 launcher,调用 launcher.lua脚本。
而创建 main服务通过如下方式调用
skynet.newservice,skynet.getenv "start" or "main"function skynet.newservice(name, ...)return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...)
endfunction skynet.call(addr, typename, ...)...local p = proto[typename]local session = c.send(addr, p.id , nil , p.pack(...))if session == nil thenerror("call to invalid address " .. skynet.address(addr))endreturn p.unpack(yield_call(addr, session))
end
上述代码可知,向 launcher服务发送一个消息,消息类型为 PTYPE_LUA,然后会被 launcher服务接收到,然后调用到 launcher.lua 中的 command.LAUNCH 接口,然后以同时的 skynet.launch(service, param)接口 创建 main.lua服务。
这篇关于skynet 配置中lua服务创建流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!