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服务。

相关推荐
Warren983 小时前
Pytest Fixture 作用域与接口测试 Token 污染问题实战解析
功能测试·面试·单元测试·集成测试·pytest·postman·模块测试
知行合一。。。3 小时前
程序中的log4j、stderr、stdout日志
python·单元测试·log4j
测试秃头怪17 小时前
面试大厂就靠这份软件测试八股文了【含答案】
自动化测试·软件测试·python·功能测试·面试·职场和发展·单元测试
测试大圣17 小时前
软件测试基础知识总结(超全的)
软件测试·python·功能测试·测试工具·职场和发展·单元测试·测试用例
CodeCraft Studio1 天前
【Parasoft案例分享】在 DO-178C 标准下,如何实现航空嵌入式软件测试自动化
单元测试·自动化·静态分析·代码覆盖率·parasoft·do-178c·软件自动化测试
懒羊羊大王&2 天前
软件测试之博客系统项目实战(补充和解析部分)
selenium·单元测试·测试用例·集成测试
真智AI2 天前
用 LLM 辅助生成可跑的 Python 单元测试:pytest + coverage 覆盖率报告(含运行指令与排坑)
python·单元测试·pytest
独处东汉2 天前
freertos开发空气检测仪之串口驱动与单元测试实践
单元测试·log4j
Warren982 天前
Allure 常用装饰器:实战用法 + 最佳实践(接口自动化)
运维·服务器·git·python·单元测试·自动化·pytest
小王不爱笑1323 天前
Postman 使用教程
测试工具·lua·postman