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如何实现高并发。