skynet 服务相关api

1 启动服务相关

我们都知道开启一个新服务的方式是skynet.newservice('name')。他的实现为:

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

所有首先要开启launcher服务。其实launcher服务首先会在bootstrap文件里被启用:

local launcher = assert(skynet.launch("snlua","launcher"))

skynet.name(".launcher", launcher)

skynet.launch是在skynet manager.lua实现的:

function skynet.launch(...)
	local addr = c.command("LAUNCH", table.concat({...}," "))
	if addr then
		return tonumber("0x" .. string.sub(addr , 2))
	end

end

也就是说skynet.newservice('name')最后调用为:

local addr = c.command("LAUNCH", "snlua launcher")

skynet.call(addr, "lua" , "LAUNCH", "snlua", 'name')

c.command在c层里实现:

static const char *
cmd_launch(struct skynet_context * context, const char * param) {
	size_t sz = strlen(param);
#ifdef _MSC_VER
	assert(sz <= 1024);
	char tmp[1024+1];
#else
	char tmp[sz+1];
#endif
	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;
	}
}

可以看到他是创建一个新的slua实例,然后返回全局实例句柄。这个新的实例会启用launcher.lua文件。他的回调函数执行时的调用为:

local function launch_service(service, ...)
	local param = table.concat({...}, " ")
	local inst = skynet.launch(service, param)
	local response = skynet.response()
	if inst then
		services[inst] = service .. " " .. param
		instance[inst] = response
	else
		response(false)
		return
	end
	return inst
end

function command.LAUNCH(_, service, ...)
	launch_service(service, ...)
	return NORET
end

可以看出他还是调用了skynet.launch(),也就是最后会调用c.command("LAUNCH", "snlua name")。最终启动那个lua文件。

我们看到新启动一个服务是call launcher服务来实现的。其实我们完全可以一步到位,直接skynet.launch("snlua", "xxx_name"),或者更为直接的是

c.command("LAUNCH", "snlua xxx_name"), 其中c为"skynet.core",为什么通过launcher服务来间接来启动一个服务呢?
我们从launcher服务一些命令诸如REMOVE, MEM, LIST, STAT可以看出,这么做的目的是为了集中管理,操纵新的服务。

与启动服务相关的还有service_mgr服务,他也是在bootstrap中启动的。他的作用是启动一个唯一,或者等待服务启动,官网api的描述为:

  • uniqueservice(name, ...) 启动一个唯一服务,如果服务该服务已经启动,则返回已启动的服务地址。
  • queryservice(name) 查询一个由 uniqueservice 启动的唯一服务的地址,若该服务尚未启动则等待。


扫描二维码关注公众号,回复: 1796548 查看本文章

2 命名服务相关

为本服务命名的函数是skynet.register('name'),他实质上是调用c层的cmd_reg:

static const char *
cmd_reg(struct skynet_context * context, const char * param) {
	if (param == NULL || param[0] == '\0') {
		sprintf(context->result, ":%x", context->handle);
		return context->result;
	} else if (param[0] == '.') {
		return skynet_handle_namehandle(context->handle, param + 1);
	} else {
		skynet_error(context, "Can't register global name %s in C", param);
		return NULL;
	}
}

可以看出给服务取名时前面要加.号,否则失败,或者干脆为空名则返回的是服务handle。正常情况下调用skynet_handle_namehandle,他的算法是在全局handle_storage中的handle_name字段中利用二分法依次添加名字。为了快速找到名字,handle_storage专门有个handle_name结构体的指针盛装名字和id,并且只有主动添加(即主动注册)才会在这个指针后依次添加对象,动态扩容,数量在handle_storage的name_count字段中。

相反的,根据服务名字获取hanle的函数是skynet.localname(name),他调用c层的c.command("QUERY", 'xxx_name')。算法是利用二分法查找上面注册过的名字,并取出包含handle的字符串,在lua层返回为16进制数。

所以要想获取本身服务handle的最好办法是直接调用c.command("REG"),第二个参数为空,所以会返回handle。实际上skynet有这个api,其实现就是这么干的:

local self_handle
function skynet.self()
	if self_handle then
		return self_handle
	end
	self_handle = string_to_handle(c.command("REG"))
	return self_handle
end

上面的skynet.register('name')给自己服务注册名字为'name',handle为context的handle。也可以让该服务的hanle不为context的handle。调用skynet.name('name', handle)。他是在manager里的一个辅助函数,其实现是调用c层的c.command("NAME", name .. " " .. skynet.address(handle)),最终和skynet.register不同的地方只是把参数context->handle换成了这个handle。

3 调用服务相关

调用服务skynet.call()第一个参数既可以是注册的字符串名字,也可以是服务的handle(可以通过调用skynet.self()得到),即一个整形。skynet.call实际上调用c.send,在c层,他会判断传过来的是字符串还是整形,如果是字符串(必须带.),会通过skynet_handle_findname()二分查找返回整形,最终都会调用那个整形参数的函数skynet_send(),毕竟从队列取出消息时是根据整形id来得到context的。




猜你喜欢

转载自blog.csdn.net/zxm342698145/article/details/80539054