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

posted on 2022-02-28 13:54  &大飞  阅读(39)  评论(0编辑  收藏  举报

导航