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"),或者更为直接的是
与启动服务相关的还有service_mgr服务,他也是在bootstrap中启动的。他的作用是启动一个唯一,或者等待服务启动,官网api的描述为:
- uniqueservice(name, ...) 启动一个唯一服务,如果服务该服务已经启动,则返回已启动的服务地址。
- queryservice(name) 查询一个由 uniqueservice 启动的唯一服务的地址,若该服务尚未启动则等待。
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的。