skynet 閱讀筆記

https://github.com/cloudwu/skynetgit

skynet 總體代碼分爲3個部分:

1: skynet-src 核心網絡層

2: service-src 服務層

3: service 使用lua實現的服務

skynet 實現是一套Actor模式的網絡架構,Actor 中核心的概念包括:

Worker 工做執行器

Actor  封裝了上下文的相關服務實現

master 調度分配器

在skynet的核心網絡層中,主要包含:

worker的實現

module的加載, 用於支持actor的實現

網絡分發和監聽 以及相關worker的調度

module加載採用dlsym 的字符串加載方式,優勢便於配置,缺點閱讀代碼查找相關實現比較麻煩。

module加載的service_snlua 服務將用於 加載執行lua代碼,並將上下文環境保存到lua虛擬機中,從而構成一個Actorgithub

在skynet_start.c 中的 thread_worker 是工做執行器的實現,

經過從消息隊列中獲取消息,並將消息分發出去交給service來執行。bootstrap

當沒有消息的時候 worker線程將會等在一個條件變量上。服務器

分配器的實現

Skynet 存在一個全局消息隊列的隊列 global_queue,每一個worker 從 global_queue 中獲取一個消息隊列,接着執行當前消息隊列的上下文中的回調函數。網絡

skynet_context_new 函數用於分配一個新的上下文,而且生成一個新的消息隊列,而每一個上下文至關於一個Actor, 而上下文對應的消息隊列 至關於一個 mailbox, 這也就解釋了 在 thread_worker 線程中爲何依次去 取不一樣的消息隊列的消息,防止某個Actor被餓死。session

在skynet 存在一個 command_func cmd_funcs 裏面包含了全部的服務器命令,總共18條,分別爲架構

{ "TIMEOUT", cmd_timeout }
    { "REG", cmd_reg },
    { "QUERY", cmd_query },
    { "NAME", cmd_name },
    { "NOW", cmd_now },
{ "EXIT", cmd_exit },
{ "KILL", cmd_kill },
{ "LAUNCH", cmd_launch },
{ "GETENV", cmd_getenv },
{ "SETENV", cmd_setenv },
{ "STARTTIME", cmd_starttime },
{ "ENDLESS", cmd_endless },
{ "ABORT", cmd_abort },
{ "MONITOR", cmd_monitor },
{ "MQLEN", cmd_mqlen },
{ "LOGON", cmd_logon },
{ "LOGOFF", cmd_logoff },
{ "SIGNAL", cmd_signal },

當某處代碼調用LAUNCH 命令的時候就會新建一個上下文,同時分配一個新的消息隊列給該上下文,這樣Worker就能夠執行消息的分發了。less

而skynet_send 以及 skynet_sendname 函數實現了向消息隊列投遞消息;socket

這樣整個調度基本流程就清晰了:函數

  1. 首先經過LAUNCH 命令啓動一個上下文,建立一個消息隊列
  2. 接着經過skynet_send 向消息隊列中投遞消息
  3. Worker線程在合適的時機將消息分發給對應上下文的 處理函數

啓動流程

在skynet_start 啓動函數中, 經過執行 snlua bootstrap 命令,執行系統初始化

snlua bootstrap表示執行 bootstrap.lua 腳原本具體初始化。

bootstrap 中將會註冊須要的一些 lua服務。

而調用 skynet_socket_init 將會啓動socket服務器

消息的分類

skynet中消息分爲兩類:

  1. 服務器內部各個Actor之間互相投遞消息
  2. 經過socket 消息投遞

thread_socket 爲socket線程主循環,主要函數是skynet_socket_poll ,

存在兩個方向數據:即從socket接受到的網絡數據,和從內部向外發送的網絡數據。

  1. 從網絡接受的數據經過 forward_message 推送給合適的消息隊列,經過 skynet_context_push 函數。
  2. 發送給網絡的數據 skynet_socket_send 接口來發送

skynet_socket是一個基礎,在之上構建的服務實例也是一個Actor, 例如service_gated 服務, 以及lua的 gateserver 服務。

這樣當內部服務須要經過socket向外部發送消息的時候,首先將message 投遞給gate,接着由gate來調用實際的socket接口向外發送消息。

一個例子以下:

  1. 首先啓動gate
  2. 客戶端鏈接gate gate 根據消息建立對應的 Actor Agent 來處理
  3. Agent 能夠經過gate 來向對應客戶端投遞消息, 客戶端消息也能夠經過gate 傳遞給agent

Actor 的實現

lua層中經過實例化一個服務來構建一個新的lua狀態機,從而構建一個獨立的Actor。 lua中的接口爲 skynet.newservice 最終調用skynet_context_new 來構建新的Actor。

每當socket 須要接受或者發送數據,或者timer定時器時間到了,都會調用wakeup, 這樣worker線程就會去獲取消息隊列數據. 系統存在一個Monitor線程, 用於監控全部其它線程,包括socket線程,timer線程,worker線程。

Timer 實現

skynet_timer 有一個 skynet_timeout API, 在skynet_server 中存在一個 TIMEOUT命令,當接受到TIME_OUT命令的時候,就會增長一個新的timer。

lua層接口爲 skynet.timeout。

Timer主要應用: 某些Actor須要按期執行,經過skynet.timeout 建立一個協程按期調用函數。

lua協程

skynet lua接口中有一個 co_create 用於建立協程。在如下幾種狀況下建立:

  1. 增長一個timeout 定時器, 定時後執行協程
  2. fork 一個函數,用於建立一個新的 協程,將協程加入到fork_queue中,在下次分發消息的時候執行該協程
  3. raw_dispatch_message 當分發消息的時候將會將對應的消息處理函數放入協程中

協程能夠等在特定的事件上,當事件發生的時候,喚醒對應協程的執行,這個經過一個suspend函數實現的,將session, 事件以及對應的協程保存起來,當事件發生,找到對應的協程,接着執行協程便可。

skynet的協議以及投遞消息尋址方式

skynet 中發送消息的接口主要兩個 skynet_send 和skynet_sendname.

lua層接口 skynet.send

發送消息的時候通常須要提供,當前發送服務的上下文,消息投遞來源,消息目標服務接受者,消息類型,消息會話id,消息數據和消息數據大小。

參考樣例:watchdog.lua 和agent.lua

在watchdog.lua中,close_agent 調用skynet.send(目標服務對象agent, 協議類型lua, 數據內容disconnect)

這樣就經過lua打包協議,將disconnect 字符串發送給了 一個agent服務對象。

在agent.lua中,skynet.start( skynet.dispatch("lua" ))

對經過lua協議分發來的消息,註冊相關的處理函數。在skynet.lua 的 raw_dispatch_message 函數中,當收到新的消息,則根據協議類型lua 找處處理函數.

而agent.lua 中調用skynet.ret()含義爲,將函數執行的結果按原路yield返回給請求方。

而調用agent.lua 的分發處理函數的 raw_dispatch_message 中以協程方式調用該函數,所以在suspend 函數中將會收到,RETURN 命令,而將函數調用結果返回給watchdog.lua

在watchdog.lua 的socket.OPEN 函數中,調用了skynet.call 這個接口和skynet.send不一樣在於call 須要等待請求的服務方的返回。

skynet.call實現使用了 yield_call函數,將會將目標服務和session匹配起來,接着yield一個call命令, 當對方服務的RESPONSE 發送回來的時候,會帶上相關的會話id,接着在raw_dispatch_message 中檢測到協議爲 RESPONSE, 根據會話id 去除watchdog.lua 的掛起的協程,繼續執行watchdog的協程。

skynet 的網絡模型

skynet 底層只有skynet_send 標準接口用於向目標投遞消息,不一樣的通信模型創建在這個基礎之上。

  1. request response lua層skynet.call 將啓動一個會話,並將lua協程等在該會話上,當response發送回來以後則重啓協程執行

  2. request lua 層skynet.send 直接調用c接口,發送數據包

  3. push 每當skynet raw_dispatch_message 時,都會啓動一個協程來處理對應的消息, 也就是每一個消息對應一個協程, 在同一個Actor中能夠同時存在多個協程

skynet 消息類型 協議類型

相關文章
相關標籤/搜索