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

相关推荐
程序员小羊!14 小时前
深入理解接口测试:实用指南与最佳实践5.0(三)
开发语言·lua
花下的晚风21 小时前
单元测试时报错找不到@SpringBootConfiguration
java·开发语言·单元测试
王夏奇21 小时前
C代码—单元测试中的覆盖率—学习笔记
笔记·学习·单元测试
袁庭新1 天前
LuaRocks如何安装数据库驱动?
java·数据库·redis·lua·luarocks·袁庭新
qq_312920111 天前
安装lua-nginx-module实现WAF功能
nginx·junit·lua
单音GG1 天前
推荐一个基于协程的C++(lua)游戏服务器
服务器·c++·游戏·lua
测试小小怪下士1 天前
单元测试、集成测试、系统测试、验收测试、压力测试、性能测试、安全性测试、兼容性测试、回归测试(超详细的分类介绍及教学)
功能测试·单元测试·测试用例·集成测试·压力测试·模块测试·安全性测试
无熵~1 天前
C# 软件测试
单元测试
袁庭新1 天前
安装luasocket模块时提示“sudo: luarocks:找不到命令“问题,该如何解决?
java·人工智能·ai·aigc·lua·luarocks·袁庭新
Dreams°1231 天前
【大数据测试HBase数据库 — 详细教程(含实例与监控调优)】
大数据·功能测试·单元测试