skynet 配置中lua服务创建流程

众所周知,skynet必须配置启动脚本,比如说如下配置

c 复制代码
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。

c 复制代码
void 
skynet_start(struct skynet_config * config) {
	...
	bootstrap(ctx, config->bootstrap);
	...
}

由默认配置可知, config->bootstrap = "snlua bootstrap"

c 复制代码
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(...)做了什么

c 复制代码
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 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)
	int r = skynet_module_instance_init(mod, inst, ctx, param);
	...
}
  1. skynet_module_instance_create(mod) 会调用到 service_snlua.c中的 snlua_create()创建一个 snlua对象,这个对象上有一个 Luastate对象
c 复制代码
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;
}
  1. skynet_module_instance_init(mod, inst, ctx, param) 会调用到 service_snlua.c中的 snlua_init()函数,然后将 launch_cb 设置为回调,并向自己发了一条消息,工作线程检测到有消息时,会触发到这个回调
c 复制代码
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;
}
  1. 看看这个回调如何处理,调用到 init_cb函数,准备去调用lua脚本
c 复制代码
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.lua
	int err = init_cb(l, context, msg, sz);
	if (err) {
		skynet_command(context, "EXIT", NULL);
	}

	return 0;
}
  1. init_cb 将诸多变量设置到全局,并编译执行 loader.lua脚本
c 复制代码
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.lua
	int 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

  1. 看看 loader.lua脚本怎么调用到 bootstrap.lua 的
lua 复制代码
local args = {}
for word in string.gmatch(..., "%S+") do
	table.insert(args, word)
	print("word:", 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

...

main(select(2, table.unpack(args)))

此时可以知道,会调用到 bootstrap.lua中

  1. bootstrap.lua 中会创建多个 lua服务,包括 main,创建完毕后将自己退出
lua 复制代码
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 接口

lua 复制代码
function skynet.launch(...)
	local addr = c.command("LAUNCH", table.concat({...}," "))
	if addr then
		return tonumber("0x" .. string.sub(addr , 2))
	end
end
c 复制代码
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服务通过如下方式调用

lua 复制代码
skynet.newservice,skynet.getenv "start" or "main"

function skynet.newservice(name, ...)
	return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...)
end


function skynet.call(addr, typename, ...)
	...
	local p = proto[typename]
	local session = c.send(addr, p.id , nil , p.pack(...))
	if session == nil then
		error("call to invalid address " .. skynet.address(addr))
	end
	return p.unpack(yield_call(addr, session))
end

上述代码可知,向 launcher服务发送一个消息,消息类型为 PTYPE_LUA,然后会被 launcher服务接收到,然后调用到 launcher.lua 中的 command.LAUNCH 接口,然后以同时的 skynet.launch(service, param)接口 创建 main.lua服务。

相关推荐
程序员二黑7 小时前
接口测试全流程实战:从工具到架构的深度解析
单元测试·测试·ab测试
步行cgn9 小时前
JUnit 单元测试详细使用指南
junit·sqlserver·单元测试
Knight_AL9 小时前
Java 单元测试全攻略:JUnit 生命周期、覆盖率提升、自动化框架与 Mock 技术
java·junit·单元测试
yunmi_2 天前
安全框架 SpringSecurity 入门(超详细,IDEA2024)
java·spring boot·spring·junit·maven·mybatis·spring security
许长安2 天前
Redis(二)——Redis协议与异步方式
数据库·redis·junit
小熊出擊2 天前
【pytest】fixture 内省(Introspection)测试上下文
python·单元测试·pytest
玩转C语言和数据结构2 天前
Lua下载和安装教程(附安装包)
lua·lua下载·lua安装教程·lua下载和安装教程·lua安装包
Arva .3 天前
HTTP Client
网络协议·http·lua
小熊出擊3 天前
【pytest】finalizer 执行顺序:FILO 原则
python·测试工具·单元测试·pytest
爱吃小胖橘3 天前
Lua语法(2)
开发语言·unity·lua