8.1 Multicast介紹
local mc = require "skynet.multicast"
引入 multicast 模塊後,你可以使用 skynet 的組播方案。你可以自由創建一個頻道,並可以向其中投遞任意消息。頻道的訂閱者可以收到投遞的消息。
你可以通過 new 函數來創建一個頻道對象。你可以創建一個新頻道,也可以利用已知的頻道 id 綁定一個已有頻道。
local channel = mc.new() -- 創建一個頻道,成功創建後,channel.channel 是這個頻道的 id 。
local channel2 = mc.new {
channel = channel.channel, -- 綁定上一個頻道
dispatch = function (channel, source, ...) end, -- 設置這個頻道的消息處理函數
}
如上面的例子,new 函數可以接收一個參數表。channel 是頻道 id ,dispatch 是訂閱消息的回調函數。如果你不給出 channel id ,則新創建出一個頻道來。
通常,由一個服務創建出頻道,再將 .channel 這個 id 通知別的地方。獲得這個 id 的位置,都可以綁定這個頻道。
channel:publish(...)
可以向一個頻道發佈消息。消息可以是任意數量合法的 lua 值。
綁定到一個頻道後,默認並不接收這個頻道上的消息(也許你只想向這個頻道發佈消息)。你需要先調用 channel:subscribe()
訂閱它。
如果不再想收到該頻道的消息,調用 channel:unsubscribe
。
當一個頻道不再使用,你可以調用 channel:delete()
讓系統回收它。注:多次調用 channel:delete
是無害的,因爲 channel id 不會重複使用。在頻道被銷燬後再調用 subscribe 或 publish 等也不會引起異常,但訂閱是不起作用的,消息也不再廣播。
8.2 應用組播技術
示例代碼推送端:multicastpub.lua
local skynet = require "skynet"
local mc = require "skynet.multicast"
local channel
function task()
local i = 0
while(i < 100) do
skynet.sleep(100)
channel:publish("data"..i) --推送數據
i = i + 1
end
channel:delete()
skynet.exit()
end
skynet.start(function()
channel = mc.new() -- 創建一個頻道,成功創建後,channel.channel 是這個頻道的 id 。
skynet.error("new channel ID", channel.channel)
skynet.fork(task)
end)
示例代碼接受端:multicastsub.lua
local skynet = require "skynet"
local mc = require "skynet.multicast"
local channel
local channelID = ... --從啓動參數獲取channelID
channelID = tonumber(channelID)
local function recvChannel(channel, source, msg, ...)
skynet.error("channel ID:",channel, "source:", skynet.address(source), "msg:",msg)
end
skynet.start(function()
channel = mc.new {
channel = channelID, -- 綁定上一個頻道
dispatch = recvChannel, -- 設置這個頻道的消息處理函數
}
channel:subscribe()
skynet.timeout(500, function() channel:unsubscribe() end) --5秒鐘後取消訂閱
end)
運行結果:
$ ./skynet examples/config multicastpub #終端輸入 [:01000010] LAUNCH snlua multicastpub [:01000012] LAUNCH snlua multicastd [:01000010] new channel ID 1 #channel ID爲1 multicastsub 1 #終端輸入 [:01000019] LAUNCH snlua multicastsub 1 [:01000019] channel ID: [Multicast:1] source: :01000010 msg: data5 [:01000019] channel ID: [Multicast:1] source: :01000010 msg: data6 [:01000019] channel ID: [Multicast:1] source: :01000010 msg: data7 [:01000019] channel ID: [Multicast:1] source: :01000010 msg: data8 [:01000019] channel ID: [Multicast:1] source: :01000010 msg: data9 #5秒鐘後不再訂閱
8.3 組播原理
當只考慮一個進程時,由於同一進程共享地址空間。當發佈消息時,由同一節點內的一個 multicastd 服務接收這條消息,並打包成一個 C 數據結構(包括消息指針、長度、引用計數),並把這個結構指針分別發送給同一節點的接收者。
雖然這並沒有減少消息的數量、但每個接受者只需要接收一個數據指針。當組播的消息較大時,可以節省內部的帶寬。引用計數會在每個接收者收到消息後減一、最終由最後一個接收者銷燬。注:如果一個訂閱者在收到消息、但沒有機會處理它時就退出了。則有可能引起內存泄露。少量的內存泄露影響不大,所以 skynet 沒有特別處理這種情況。
當有多個節點時,每個節點內都會啓動一個 multicastd 服務。它們通過 DataCenter 相互瞭解。multicastd 管理了本地節點創建的所有頻道。在訂閱階段,如果訂閱了一個遠程的頻道,當前節點的 multicastd 會通知遠程頻道的管理方,在該頻道有發佈消息時,把消息內容轉發過來。涉及遠程組播,不能直接共享指針。這時,multicastd 會將消息完整的發送到接收方所屬節點上的同僚,由它來做節點內的組播。