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

相关推荐
surfirst7 小时前
举例说明 .Net Core 单元测试中 xUnit 的 [Theory] 属性的用法
单元测试·.netcore·xunit
回眸&啤酒鸭19 小时前
【回眸】Tessy 单元测试软件使用指南(四)常见报错及解决方案与批量初始化的经验
单元测试·tessy
Iam傅红雪2 天前
mock数据,不使用springboot的单元测试
spring boot·后端·单元测试
程序员陆通2 天前
如何使用ChatGPT API及Bito插件
开发语言·chatgpt·lua
月光code2 天前
SLF4J报错log4j又报错
单元测试·log4j
谷隐凡二2 天前
windows环境下luarocks下载包的使用
开发语言·lua
编程经验分享3 天前
Spring Boot 基于 Mockito 单元测试
spring boot·后端·单元测试
神即道 道法自然 如来3 天前
测试面试题:请你分别介绍一下单元测试、集成测试、系统测试、验收测试、回归测试
单元测试·集成测试
程序那点事儿3 天前
open-resty 服务安装kafka插件
linux·分布式·nginx·kafka·lua·运维开发·openresty
友恒3 天前
C#单元测试(一):用 NUnit 和 .NET Core 进行单元测试
单元测试·c#·.netcore