首页 > 其他分享 >launcher的创建

launcher的创建

时间:2022-12-08 15:36:29浏览次数:54  
标签:skynet lua launcher 创建 char LUA context

新入门skynet系列视频b站网址 https://www.bilibili.com/video/BV19d4y1678X

# launcher的创建

目录

launcher服务是一个snlua服务。

当调用skynet.newservice请求创建某个服务的时候,实际上会把创建请求发送到launcher服务。launcher来真正启动一个目标服务。launcher本身又是怎么启动的?他是在bootstrap服务里面启动的。看看代码 11行。

bootstrap服务也是一个snlua服务

--bootstrap.lua

local skynet = require "skynet"
local harbor = require "skynet.harbor"
local service = require "skynet.service"
require "skynet.manager"	-- import skynet.launch, ...

skynet.start(function()
	local standalone = skynet.getenv "standalone"
	--skynet.launch 用于启动一个 C 模块的服务。主要意思是 第一个参数是 c 模块的名字 可以是snlua logger...
	local launcher = assert(skynet.launch("snlua","launcher"))
	skynet.name(".launcher", launcher) --给服务取一个名字叫做.launcher

	local harbor_id = tonumber(skynet.getenv "harbor" or 0)
	if harbor_id == 0 then
		assert(standalone ==  nil)
		standalone = true
		skynet.setenv("standalone", "true")
            
	end

	skynet.newservice "service_mgr"

	pcall(skynet.newservice,skynet.getenv "start" or "main")
	skynet.exit()
end)

也就是说 skynet.launch("snlua","launcher") 创建了 launcher服务。这两个参数的意思是 我们是要创建一个snlua服务,对应的脚本文件是launcher.lua。这个调用不会挂起我们当前协程,但不是在当前工作线程完成所有创建任务的。

当前工作线程完成步骤1

当前线程主要是push了 第一个消息 给我们创建的服务。我们跟踪一下


function skynet.launch(...)
	local addr = c.command("LAUNCH", table.concat({...}," "))--next
	if addr then
		return tonumber(string.sub(addr , 2), 16)--把8位16进制数转变成lua表示的十进制数 /这里把开头的冒号去掉了
	end
end

跟踪到c层。

static int
lcommand(lua_State *L) {
	struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));
	const char * cmd = luaL_checkstring(L,1);
	const char * result;
	const char * parm = NULL;
	if (lua_gettop(L) == 2) {
		parm = luaL_checkstring(L,2);
	}

	result = skynet_command(context, cmd, parm);//cmd是"LAUNCH" parm是 "snlua launcher"
	if (result) {
		lua_pushstring(L, result);
		return 1;
	}
	return 0;
}

11行

static struct command_func cmd_funcs[] = {

	{ "LAUNCH", cmd_launch },//这里

	{ NULL, NULL },
};

const char * 
skynet_command(struct skynet_context * context, const char * cmd , const char * param) {
	struct command_func * method = &cmd_funcs[0];
	while(method->name) {
		if (strcmp(cmd, method->name) == 0) {
			return method->func(context, param);//这里调用 cmd_launch
		}
		++method;
	}

	return NULL;
}

继续跟踪 cmd_launch

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);//mod是 "snlua"  args是 "launcher"
	if (inst == NULL) {
		return NULL;
	} else {
		id_to_hex(context->result, inst->handle);//把8位16进制的handle用字符串表示 注意是以冒号开头
		return context->result;
	}
}

当我们看到 9行,就感觉有点熟悉了。因为我们的logger服务当初也是这样创建的。回顾logger服务。再次看看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);//根据模块创建对应实例

	struct skynet_context * ctx = skynet_malloc(sizeof(*ctx));

	ctx->handle = skynet_handle_register(ctx);//获取一个handle
    
	struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle);//创建服务的队列


	int r = skynet_module_instance_init(mod, inst, ctx, param);//初始化模块实例 param是 launcher
    
	if (r == 0) {
		struct skynet_context * ret = skynet_context_release(ctx);
		if (ret) {
			ctx->init = true;
		}
		skynet_globalmq_push(queue);//服务队列加入全局队列

		return ret;
	} 
}

skynet_context_new 是创建一个服务,snlua服务跟logger服务创建过程大致类似。主要不同点在于他们属于不同的模块,所以对应模块的创建函数和初始化函数是不同的。我们主要看snlua模块的初始化实例的函数 snlua_init

static const char *
cmd_reg(struct skynet_context * context, const char * param) {
	if (param == NULL || param[0] == '\0') {
		sprintf(context->result, ":%x", context->handle);//%x 表示无符号十六进制整数
		return context->result;//开头是冒号
	} 
}


int
snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) {
	int sz = strlen(args);//此时 args 是 "launcher"
	char * tmp = skynet_malloc(sz);
	memcpy(tmp, args, sz);
	skynet_callback(ctx, l , launch_cb);//服务队列里面第一个消息的处理 会调用launch_cb
	const char * self = skynet_command(ctx, "REG", NULL);//最终调用 cmd_reg
	uint32_t handle_id = strtoul(self+1, NULL, 16);//将字符串转换成unsigned long(无符号长整型数)
	// it must be first message
	skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);//倒数第三个参数为0 表示 session
	return 0;

主要做了两件事。

  • skynet_callback 设置服务的回调函数 ,也就是处理队列中消息时使用的函数。这里设置的是 launch_cb
  • skynet_send 往自己的队列中push了一个消息。

到这里 skynet_context_new 的处理完成。

我们再次回到调用 skynet_context_new 的地方

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);//mod是 "snlua"  args是 "launcher"

    id_to_hex(context->result, inst->handle);//把8位16进制的handle用字符串表示 注意是以冒号开头
    return context->result;//里面存的是这种":123123bb"
	
}

我们看 12行 最终返回了 新服务的地址。再回到lua层的起点

function skynet.launch(...)
	local addr = c.command("LAUNCH", table.concat({...}," "))--next
	if addr then
		return tonumber(string.sub(addr , 2), 16)--把8位16进制数转变成lua表示的十进制数 /这里把开头的冒号去掉了
	end
end

所以我们调用skynet.launch 最终返回了一个 新服务地址。总结下此时我们所在的bootstarp服务创建了 launcher服务,launcher给它自己的队列里push了第一个消息。launcher队列将来被工作线程执行到时,就会开始处理这个消息。

image-20220609103022289

可能另一个工作线程完成步骤2

现在假设 有一个工作线程来驱动launcher工作了。那么第一个消息的处理会交给 launch_cb

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);//这里取消了回调函数 但是最终lua服务的回调函数的设置 是通过lua服务的skynet.start()函数 
	
    int err = init_cb(l, context, msg, sz);//next

	return 0;
}

上面取消了回调函数。然后调用init_cb。这个函数你暂时需要关注两点。1.代表当前服务的context被保存在注册表中了。以后在需要context时,可以方便的获取。2. 我们最终执行了一个lua文件。这个lua文件就是launcher.lua服务代表的文件。

static int
init_cb(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {
	lua_State *L = l->L;
	l->ctx = ctx;
	lua_gc(L, LUA_GCSTOP, 0);
	lua_pushboolean(L, 1);  /* signal for libraries to ignore env. vars. */
	lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");
	luaL_openlibs(L);
	luaL_requiref(L, "skynet.profile", init_profile, 0);//当你在lua层require "skynet.profile" 的时候会调用c函数 init_profile ,最终返回一张表

	int profile_lib = lua_gettop(L);
	// replace coroutine.resume / coroutine.wrap
	lua_getglobal(L, "coroutine");
	lua_getfield(L, profile_lib, "resume");
	lua_setfield(L, -2, "resume");
	lua_getfield(L, profile_lib, "wrap");
	lua_setfield(L, -2, "wrap");

	lua_settop(L, profile_lib-1);

	lua_pushlightuserdata(L, ctx);
	lua_setfield(L, LUA_REGISTRYINDEX, "skynet_context");//把ctx设置到当前服务对应的 lua_State 的全局注册表中 key的名字是 "skynet_context"
	luaL_requiref(L, "skynet.codecache", codecache , 0);//当你在lua层require "skynet.codecache" 的时候会调用c函数 codecache ,最终返回一张表
	lua_pop(L,1);

	lua_gc(L, LUA_GCGEN, 0, 0);

	const char *path = optstring(ctx, "lua_path","./lualib/?.lua;./lualib/?/init.lua");
	lua_pushstring(L, path);
	lua_setglobal(L, "LUA_PATH");
	const char *cpath = optstring(ctx, "lua_cpath","./luaclib/?.so");
	lua_pushstring(L, cpath);
	lua_setglobal(L, "LUA_CPATH");
	const char *service = optstring(ctx, "luaservice", "./service/?.lua");
	lua_pushstring(L, service);
	lua_setglobal(L, "LUA_SERVICE");
	const char *preload = skynet_command(ctx, "GETENV", "preload");
	lua_pushstring(L, preload);
	lua_setglobal(L, "LUA_PRELOAD");

	lua_pushcfunction(L, traceback);
	assert(lua_gettop(L) == 1);

	const char * loader = optstring(ctx, "lualoader", "./lualib/loader.lua");//我们真正的lua服务文件是被loader.lua文件加载执行的

	int r = luaL_loadfile(L,loader);

	lua_pushlstring(L, args, sz);//这里的arges是 "launcher"
	r = lua_pcall(L,1,0,1);//开始执行lua文件了 这个文件是 loader.lua

	lua_settop(L,0);
	if (lua_getfield(L, LUA_REGISTRYINDEX, "memlimit") == LUA_TNUMBER) {
		size_t limit = lua_tointeger(L, -1);
		l->mem_limit = limit;
		skynet_error(ctx, "Set memory limit to %.2f M", (float)limit / (1024 * 1024));
		lua_pushnil(L);
		lua_setfield(L, LUA_REGISTRYINDEX, "memlimit");
	}
	lua_pop(L, 1);

	lua_gc(L, LUA_GCRESTART, 0);

	return 0;
}

  • 每个snlua服务都有一个 lua_State成员。lua_State代表一个lua环境,没有lua环境,后面是不能把消息交给lua处理的。

  • 配置文件具体是怎么读取的呢 可以看这里 skynet启动时读取配置文件

我们看上面的lua文件是怎么被执行到的。看 49行。实际上我们第一个执行到的文件是 loader.lua。在这个文件里面,才运行了launcher.lua文件。

-- loader.lua

local args = {}
for word in string.gmatch(..., "%S+") do 
	table.insert(args, word)
end

SERVICE_NAME = args[1] --lua服务指定的文件名字 比如 launcher.lua 对应的名字是 launcher

local main, pattern

local err = {}
for pat in string.gmatch(LUA_SERVICE, "([^;]+);*") do --LUA_SERVICE 是存放lua服务对应文件的所有路径 类似 "./service/?.lua;./skynet-1.5.0/test/?.lua;" 
	local filename = string.gsub(pat, "?", SERVICE_NAME) ---用 launcher替换掉路径配置中的 "?"
	local f, msg = loadfile(filename)
	if not f then
		table.insert(err, msg)
	else
		pattern = pat
		main = f
		break --这里表示从其中一条路径找到一个文件就够了
	end
end

-- LUA_PATH LUA_CPATH 这些都是启动skynet时在配置文件里设置的 不过配置文件中使用小写 比如lua_path
LUA_SERVICE = nil
package.path , LUA_PATH = LUA_PATH 		--等价于 package.path = LUA_PATH ;LUA_PATH = nil
package.cpath , LUA_CPATH = LUA_CPATH 	--等价于 package.cpath = LUA_CPATH ;LUA_CPATH = nil

--如果pattern类似 ./aaa/bbb/?/main.lua 那么下面返回值service_path是 ./aaa/bbb/?/
--一般情况下pattern类似 ./aaa/bbb/?.lua 那么下面的返回值service_path是 nil
local service_path = string.match(pattern, "(.*/)[^/?]+$") 

if service_path then
	service_path = string.gsub(service_path, "?", args[1])
	package.path = service_path .. "?.lua;" .. package.path
	SERVICE_PATH = service_path
else
	local p = string.match(pattern, "(.*/).+$")
	SERVICE_PATH = p
end


_G.require = (require "skynet.require").require --注意这里首先加载了 skynet.require 然后替换了lua原本的require

main(select(2, table.unpack(args))) --从这里开始执行我们指定的 lua服务文件

lua的模式匹配可以看看这里 lua模式匹配 ;另外 package.path , LUA_PATH = LUA_PATH 这句可以理解为 package.path , LUA_PATH = func(),当前func函数只有一个返回值,但是左边却有两个变量,所以LUA_PATH 只能是nil了

44行 最后开始执行我们的 launcher.lua文件。我们当前只关注下面的代码。

-- launcher.lua

skynet.dispatch("lua", function(session, address, cmd , ...)
	cmd = string.upper(cmd)
	local f = command[cmd]
	if f then
		local ret = f(address, ...)
		if ret ~= NORET then
			skynet.ret(skynet.pack(ret))
		end
	else
		skynet.ret(skynet.pack {"Unknown command"} )
	end
end)

skynet.start(function() end)

也就是说执行launcher.lua文件,主要是调用了上面两个函数。skynet.dispatch注册了 lua类型消息的回调函数。也就是收到lua类型消息时,都交给这个函数处理。另外一个是skynet.start函数。skynet.start主要做了两件事。1. 注册回调函数 2.注册一个定时器

function skynet.start(start_func)
	c.callback(skynet.dispatch_message)--注册c层处理消息的回调函数,最终会把消息转交给lua层的函数
	init_thread = skynet.timeout(0, function()--注册一个定时器
		skynet.init_service(start_func)
		init_thread = nil
	end)
end

第2行是注册回调函数 我们看代码

static int
lcallback(lua_State *L) {
	struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));
	int forward = lua_toboolean(L, 2);
	luaL_checktype(L,1,LUA_TFUNCTION);
	lua_settop(L,1);
	lua_rawsetp(L, LUA_REGISTRYINDEX, _cb);//相当于 注册表[_cb] = fun

	lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD);
	lua_State *gL = lua_tothread(L,-1);//获取我们的主协程 也就是当前lua层调用skynet.start所在的协程


	skynet_callback(context, gL, _cb);//lua服务注册回调函数 在回调函数里面会调用lua应用层的消息分发函数skynet.dispatch_message
	

	return 0;
}

注意上面的 13行,注册了服务的回调函数 _cb。我们看看当我们服务收到消息时,是怎么处理的。

static int
_cb(struct skynet_context * context, void * ud, int type, int session, uint32_t source, const void * msg, size_t sz) {
	lua_State *L = ud;//这里是主协程
	int trace = 1;
	int r;
	int top = lua_gettop(L);
	if (top == 0) {
		lua_pushcfunction(L, traceback);
		lua_rawgetp(L, LUA_REGISTRYINDEX, _cb);
	} else {
		assert(top == 2);
	}
	lua_pushvalue(L,2);

	lua_pushinteger(L, type);
	lua_pushlightuserdata(L, (void *)msg);
	lua_pushinteger(L,sz);
	lua_pushinteger(L, session);
	lua_pushinteger(L, source);

	r = lua_pcall(L, 5, 0 , trace);//调用lua层的skynet.dispatch_message 其参数是prototype, msg, sz, session, source

	if (r == LUA_OK) {
		return 0;
	}

}

注意 21行 最终把 消息的五个参数 传递给了lua层。接下来关注注册定时器的代码。关于定时器的使用,可以看看这里 初试定时器

function skynet.init_service(start)
	local function main()
		skynet_require.init_all()
		start() --这个就是我们的start_func函数。可以认为是lua层的main函数
	end
	local ok, err = xpcall(main, traceback)
	if not ok then
		skynet.error("init service failed: " .. tostring(err))
		skynet.send(".launcher","lua", "ERROR")
		skynet.exit()
	else
		skynet.send(".launcher","lua", "LAUNCHOK")--任何服务在完成start后 都会发送LAUNCHOK 消息给 launcher服务
	end
end

function skynet.start(start_func)
	c.callback(skynet.dispatch_message)--注册c层处理消息的回调函数,最终会把消息转交给lua层的函数
	init_thread = skynet.timeout(0, function()--注册一个定时器
		skynet.init_service(start_func)
		init_thread = nil
	end)
end

上面代码注册了一个定时器。定时器会获取一个协程,这个协程的任务函数是一个 匿名函数。当定时器触发时,匿名函数被执行,这个匿名函数内部会调用我们注册的启动函数 start_func ,然后会发送一个消息通知 launcher。回到我们 launcher.lua文件

-- launcher.lua

skynet.dispatch("lua", function(session, address, cmd , ...)
	cmd = string.upper(cmd)
	local f = command[cmd]
	if f then
		local ret = f(address, ...)
		if ret ~= NORET then
			skynet.ret(skynet.pack(ret))
		end
	else
		skynet.ret(skynet.pack {"Unknown command"} )
	end
end)

skynet.start(function() end)

我们发现 我们通过skynet.start注册的star_func函数没有做任何事情。最后定时器协程被回收了。此时认为launcher服务启动完成了。over

标签:skynet,lua,launcher,创建,char,LUA,context
From: https://www.cnblogs.com/waittingforyou/p/16966196.html

相关文章