• 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, " 	
    ");
     9         args = strsep(&args, "
    ");
    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如何实现高并发。

  • 相关阅读:
    C# 异步锁
    C#异步编程基础入门总结
    C#异步编程基础入门总结
    C#与数据结构--图的遍历
    C#中IEumerable的简单了解
    C# prism 框架 MVVM框架 Prism系列之事件聚合器
    .NET Core 3 WPF MVVM框架 Prism系列之对话框服务
    C# prism 框架
    TaskAwaiter<TResult> 结构
    利用Eventlog Analyzer分析日志
  • 原文地址:https://www.cnblogs.com/RainRill/p/8485030.html
Copyright © 2020-2023  润新知