skynet源码分析之snlua服务的启动流程(二)(转)
通过前一篇文章(http://www.cnblogs.com/RainRill/p/8485024.html)了解了skynet启动snlua服务的整体流程,这篇文章补充上一篇未介绍的内容。
1. 消息类型
skynet定义了多个不同的消息类型,每种类型的处理方式不一样,在服务启动流程中需注册用到的消息类型的消息处理协议。否则,收到对应类型的消息时会因没有消息处理协议而报错。
1 -- lualib/skynet.lua
2 local skynet = {
3 -- read skynet.h
4 PTYPE_TEXT = 0,
5 PTYPE_RESPONSE = 1,
6 PTYPE_MULTICAST = 2,
7 PTYPE_CLIENT = 3,
8 PTYPE_SYSTEM = 4,
9 PTYPE_HARBOR = 5,
10 PTYPE_SOCKET = 6,
11 PTYPE_ERROR = 7,
12 PTYPE_QUEUE = 8, -- used in deprecated mqueue, use skynet.queue instead
13 PTYPE_DEBUG = 9,
14 PTYPE_LUA = 10,
15 PTYPE_SNAX = 11,
16 }
一个消息处理协议一般包含:
name 字符串,协议类型名称,比如Lua类型,其值为"Lua"
id 对应的消息类型,如上
pack 发送消息时,对消息包进行打包的函数
unpack 收到消息时,对消息包进行解包的函数,再传给dispatch函数 ,实现消息回调
dispatch 消息回调函数,通常由用户自己指定
在require "skynet"中,指定了Lua服务三种必须指定的消息处理协议:“lua”通用的Lua服务消息处理类型,dispatch由用户自己指定;"response"请求的回应消息,不用指定dispatch,是因为收到回应消息时,重启对应的co即可;“error”发生异常时的消息,调用_error_dispatch
1 // lualib/skynet.lua
2 do
3 local REG = skynet.register_protocol
4
5 REG {
6 name = "lua",
7 id = skynet.PTYPE_LUA,
8 pack = skynet.pack,
9 unpack = skynet.unpack,
10 }
11
12 REG {
13 name = "response",
14 id = skynet.PTYPE_RESPONSE,
15 }
16
17 REG {
18 name = "error",
19 id = skynet.PTYPE_ERROR,
20 unpack = function(...) return ... end,
21 dispatch = _error_dispatch,
22 }
23 end
通常在服务的入口脚本里指定"lua"协议的dispatch函数(skynet.dispatch),以及需要的其他额外协议类型(skynet.register_protocol),例如:
1 -- service/launcher.lua
2 skynet.register_protocol {
3 name = "text",
4 id = skynet.PTYPE_TEXT,
5 unpack = skynet.tostring,
6 dispatch = function(session, address , cmd)
7 if cmd == "" then
8 command.LAUNCHOK(address)
9 elseif cmd == "ERROR" then
10 command.ERROR(address)
11 else
12 error ("Invalid text command " .. cmd)
13 end
14 end,
15 }
16
17 skynet.dispatch("lua", function(session, address, cmd , ...)
18 cmd = string.upper(cmd)
19 local f = command[cmd]
20 if f then
21 local ret = f(address, ...)
22 if ret ~= NORET then
23 skynet.ret(skynet.pack(ret))
24 end
25 else
26 skynet.ret(skynet.pack {"Unknown command"} )
27 end
28 end)
2. 在Lua服务里创建另一个Lua服务
通常需要在一个Lua服务创建另一个Lua服务,比如在A服务里创建B服务,一般是用skynet.newservice,即给".launcher"服务发送一个"lua"类型的消息
1 -- lualib/skynet.lua
2 function skynet.newservice(name, ...)
3 return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...)
4 end
“.launcher”服务收到消息后,调用command.LAUNCH,然后调用skynet.launch(第4行),然后调用C层的command(第23行),
1 -- service/launcher.lua
2 local function launch_service(service, ...)
3 local param = table.concat({...}, " ")
4 local inst = skynet.launch(service, param)
5 local response = skynet.response()
6 if inst then
7 services[inst] = service .. " " .. param
8 instance[inst] = response
9 else
10 response(false)
11 return
12 end
13 return inst
14 end
15
16 function command.LAUNCH(_, service, ...)
17 launch_service(service, ...)
18 return NORET
19 end
20
21 -- lualib/skynet/manager.lua
22 function skynet.launch(...)
23 local addr = c.command("LAUNCH", table.concat({...}," "))
24 if addr then
25 return tonumber("0x" .. string.sub(addr , 2))
26 end
27 end
最后调用cmd_launch,创建一个ctx(第10行)。
1 // skynet-src/skynet_server.c
2 static const char *
3 cmd_launch(struct skynet_context * context, const char * param) {//启动一个新ctx
4 size_t sz = strlen(param);
5 char tmp[sz+1];
6 strcpy(tmp,param);
7 char * args = tmp;
8 char * mod = strsep(&args, " \t\r\n");
9 args = strsep(&args, "\r\n");
10 struct skynet_context * inst = skynet_context_new(mod,args);
11 if (inst == NULL) {
12 return NULL;
13 } else {
14 id_to_hex(context->result, inst->handle);
15 return context->result;
16 }
17 }
之后回到launcher.lua,调用skynet.response暂停当前co,调用suspend(类型是"RESPONSE),resume重启co(第24行),返回一个Lua函数response
local response = skynet.response()
1 // lualib/skynet.lua
2 function skynet.response(pack)
3 pack = pack or skynet.pack
4 return coroutine_yield("RESPONSE", pack)
5 end
6
7 function suspend(co, result, command, param, size)
8 ...
9 elseif command == "RESPONSE" then
10 local co_session = session_coroutine_id[co]
11 local co_address = session_coroutine_address[co]
12 if session_response[co] then
13 error(debug.traceback(co))
14 end
15 local f = param
16 local function response(ok, ...)
17 ...
18 end
19 watching_service[co_address] = watching_service[co_address] + 1
20 session_response[co] = true
21 unresponse[response] = true
22 return suspend(co, coroutine_resume(co, response))
23 end
在launcher.lua里,以ctx的handle id为key,Lua函数response为value保存在instance里,
instance[inst] = response
新创建的B服务的入口脚本最后会调用skynet.start,在下一帧会调用skynet.init_service,在成功执行完start_func后(第3行,如何执行参考http://www.cnblogs.com/RainRill/p/8466368.html),最后会向".launcher"服务发送一条消息(第9行)
1 -- lualib/skynet.lua
2 function skynet.init_service(start)
3 local ok, err = skynet.pcall(start)
4 if not ok then
5 skynet.error("init service failed: " .. tostring(err))
6 skynet.send(".launcher","lua", "ERROR")
7 skynet.exit()
8 else
9 skynet.send(".launcher","lua", "LAUNCHOK")
10 end
11 end
12
13 function skynet.start(start_func)
14 c.callback(skynet.dispatch_message) -- 上一篇介绍了
15 skynet.timeout(0, function()
16 skynet.init_service(start_func)
17 end)
18 end
".launcher"服务收到消息后,调用command.LAUNCHOK,找到之前保存的response函数,然后执行它
1 -- service/launcher.lua 2 function command.LAUNCHOK(address) 3 -- init notice 4 local response = instance[address] 5 if response then 6 response(true, address) 7 instance[address] = nil 8 end 9 10 return NORET 11 end
在response函数里,向co_address(此时是A服务)发送一条"RESPONSE"类型消息,消息包是打包B服务地址后的数据(f(...)),即告诉A服务新创建的B服务的地址。
1 -- lualib/skynet.lua 2 local function response(ok, ...) 3 ... 4 ret = c.send(co_address, skynet.PTYPE_RESPONSE, co_session, f(...)) ~= nil 5 return ret 6 end
这就是snlua服务启动的整个流程。接下来会介绍skynet里的Lua层消息处理机制,从而理解skynet如何实现高并发。

浙公网安备 33010602011771号