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就是標明該消息是返回數據。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章