skynet框架 源码分析 三

今天我们来读service_lua.c文件。

       这个文件很重要,它是模块snlua的源文件,也是各个lua服务节点的制造者。比如:agent服务节点,watchdog服务节点,launch服务节点等。

       让我们来看看这个制造者是如何运作的。

       拿agent举例说:

              gate节点在服务端与新到的客户端连接建立成功之后,会向watchdog服务节点发送一条文本消息,消息内容含有open指令,意在生成两个新的服务节点,分别是client和agent服务节点。

watchdog收到消息后会运行command:open接口:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. function command:open(parm)  
  2.     local fd,addr = string.match(parm,"(%d+) ([^%s]+)")  
  3.     fd = tonumber(fd)  
  4.     print("agent open", self, string.format("%d %d %s", self, fd, addr))  
  5.     local client = skynet.launch("client", fd)  
  6.     local agent = skynet.launch("snlua","agent",skynet.address(client))  
  7.     if agent then  
  8.         agent_all[self] = { agent , client }  
  9.         skynet.send(gate, "text", "forward" , self, skynet.address(agent) , skynet.address(client))  
  10.     end  
  11. end  

       执行该接口时,watchdog会执行两条指令skynet.launch(client, ...)和skynet.launch(snalu, agent, ....)分别载入client节点,和agent节点。agent节点是由模块snlua制造的。让我们看看接下来会发生什么。
       watchdog通过脚本调用执行skynet.so库文件中的skynet_command接口载入节点agent。而后,初始化节点agent的上下文,并调用模块snlua的snlua_create接口,生成一个节点agent基础的基础数据,调用snlua_init初始化agent节点的数据并发送了一条消息(执行脚本agent.lua,当然现在还未执行)。此时,最基础的一个agent服务节点就已经产生了,当然,它收到的第一条消息就是读取agent.lua文件,并执行。
       新的worker线程,执行到agent节点时,首先,执行agent.lua脚本文件。里面包括,引入skynet.lua文件,设置client的handle值。skynet.lua比较复杂。skynet.lua中会引入skynet.so
库文件并调用luaopen_skynet_c接口,向当前lua状态机上的添加一张表,而后将表的地址赋给本地变量c。本地变量c表中记录了很多函数指针。当然经常导C++对象或者函数指针到lua中的人对这一块肯定不陌生。不过,我要说的是接下来要发生的逻辑。

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. local skynet = require "skynet"  
  2. local client = ...  
  3.   
  4. skynet.register_protocol {  
  5.     name = "client",  
  6.     id = 3,  
  7.     pack = function(...) return ... end,  
  8.     unpack = skynet.tostring,  
  9.     dispatch = function (session, address, text)  
  10.         -- It's client, there is no session  
  11.         skynet.send("LOG", "text", "client message :" .. text)  
  12.         local result = skynet.call("SIMPLEDB", "text", text)  
  13.         skynet.ret(result)  
  14.     end  
  15. }  
  16.   
  17. skynet.start(function()  
  18.     skynet.send(client,"text","Welcome to skynet")  
  19. end)  

       首先通过skynet.register_protocol注册一个客户端消息处理函数,里面包含发送内容到log服务,调用simpledb执行指令,最后返回结果到客户端。

       接下来是skynet.start接口,这个接口很重要。我们看看里面写了什么。

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. function skynet.start(start_func)     
  2.     c.callback(dispatch_message)  
  3.     trace_handle = assert(c.stat "trace")  
  4.     skynet.timeout(0, function()  
  5.         init_service(start_func)  
  6.     end)  
  7. end  

       首先,执行c.callback,调用skynet.so库文件的接口_callback,设置agent节点的回调函数,这样每次有消息发送到agent节点,都会用函数dispatch_message来处理。而后,才会执行传进来的闭包 start_func。

       接下来dispatch_message通过pcall调用了接口raw_dispatch_message,而dispatch_message本事并没有太多逻辑。我们看看raw_dispatch_message接口是在干什么。

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. local function raw_dispatch_message(prototype, msg, sz, session, source, ...)  
  2.     -- PTYPE_RESPONSE = 1, read skynet.h  
  3.     if prototype == 1 then  
  4.         local co = session_id_coroutine[session]  
  5.         if co == "BREAK" then  
  6.             session_id_coroutine[session] = nil  
  7.         elseif co == nil then  
  8.             unknown_response(session, source, msg, sz)  
  9.         else  
  10.             c.trace_switch(trace_handle, session)  
  11.             session_id_coroutine[session] = nil  
  12.             suspend(co, coroutine.resume(co, msg, sz))  
  13.         end  
  14.     else  
  15.         local p = assert(proto[prototype], prototype)  
  16.         local f = p.dispatch  
  17.         if f then  
  18.             local co = co_create(f)  
  19.             session_coroutine_id[co] = session  
  20.             session_coroutine_address[co] = source  
  21.             suspend(co, coroutine.resume(co, session, source, p.unpack(msg,sz, ...)))  
  22.         else  
  23.             print("Unknown request :" , p.unpack(msg,sz))  
  24.             error(string.format("Can't dispatch type %s : ", p.name))  
  25.         end  
  26.     end  
  27. end  

       若消息类型值为1,则是返回消息,比方说向simpledb发送了一条文本消息,而后收到其返回结果。当然,不管raw_dispatch_message中走哪条分支,最终基本上都会调用suspend接口,并调用coroutine.resume接口,也就是用协程的方式运行某个lua闭包。解释一下lua协程。按我的理解lua协程其实只开辟了一个线程栈,但没有像操作系统的线程那样,成为一个调度单位。只是在lua主线程执行到某一处时,通过coroutine.resume切换到协程中执行,当执行完之后,通过coroutine.yield返回到主线程栈上继续执行。也就是说协程只是新建了一个空间来执行逻辑,执行完将执行结果返回给主线程,或者执行到一半,切换到主线程,等待下次被唤起继续执行。而之前注册的消息处理函数,就是在协程中执行的。如果已经理解了协程的概念,继续看其中的函数就不会太麻烦。比方说看skynet.call接口时,看到接口skynet.call时:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. return p.unpack(coroutine_yield("CALL", assert(session, "call to invalid address")))  
   有一个coroutine_yield,这时候,就是将"CALL", session返回给lua状态机的主线程,主线程是在suspend接口中调用该协程的,在收到返回数据后,继续执行suspend接口。
[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. -- suspend is local function  
  2. function suspend(co, result, command, param, size)  
  3.     if not result then  
  4.         trace_count()  
  5.         error(debug.traceback(co,command))  
  6.     end  
  7.     if command == "CALL" then  
  8.         c.trace_register(trace_handle, param)  
  9.         session_id_coroutine[param] = co  
  10.     elseif command == "SLEEP" then  
  11.         c.trace_register(trace_handle, param)  
  12.         session_id_coroutine[param] = co  
  13.         sleep_session[co] = param  
  14.     elseif command == "RETURN" then  
  15.         local co_session = session_coroutine_id[co]  
  16.         local co_address = session_coroutine_address[co]  
  17.         -- PTYPE_RESPONSE = 1 , see skynet.h  
  18.         if param == nil then  
  19.             trace_count()  
  20.             error(debug.traceback(co))  
  21.         end  
  22.         -- c.send maybe throw a error, so call trace_count first.  
  23.         -- The coroutine execute time after skynet.ret() will not be trace.  
  24.         trace_count()  
  25.         c.send(co_address, 1, co_session, param, size)  
  26.         return suspend(co, coroutine.resume(co))  
  27.     elseif command == "EXIT" then  
  28.         -- coroutine exit  
  29.         session_coroutine_id[co] = nil  
  30.         session_coroutine_address[co] = nil  
  31.     else  
  32.         trace_count()  
  33.         error("Unknown command : " .. command .. "\n" .. debug.traceback(co))  
  34.     end  
  35.     trace_count()  
  36.     dispatch_wakeup()  
  37. end  

        第一个co就不解释了,result是执行协程的结果,若成功返回true,否则返回false。command就是“CALL”,后面的是返回的参数。比方说现在param 就等于 session。此时,执行command == “call” 将 key = session,value = co保存到表session_id_coroutine表中。例如:当simpledb节点返回数据给agent节点时,首先,在simpledb中执行dispatch接口,执行skynet.ret接口,此时,协程会被挂起,且切换到simpledb主线程,而后返回一条“RETURN”command给suspend接口,suspend接口中从session_coroutine_id中取出session_id,从session_coroutine_address取出handle值。最后发送消息给该handle,也就是agent节点。如果要问为什么消息类型是返回类型,请看上段代码的:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. -- c.send maybe throw a error, so call trace_count first.  
  2. -- The coroutine execute time after skynet.ret() will not be trace.  
  3. trace_count()  
  4. c.send(co_address, 1, co_session, param, size)  
              c.send(xxx, 1 --- 此处1对应PTYPE_RESPONSE就是标明该消息是返回数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值