Socket通信協議Sproto
在和客戶端通訊時,需要制訂一套通訊協議。 skynet 並沒有規定任何通訊協議,所以你可以自由選擇。
sproto 是一套由 skynet 自身提供的協議,並沒有特別推薦使用,只是一個選項。sproto 有一個獨立項目存在 。同時也複製了一份在 skynet 的源碼庫中。
在同一個目錄建立5個文件(config,proto.lua,main.lua,socket1.lua,client1.lua)
config文件參考第一節內容
proto.lua是定義通信協議,代碼:
local sprotoparser = require "sprotoparser"
local proto = {}
proto.c2s = sprotoparser.parse [[
.package {
type 0 : integer
session 1 : integer
}
handshake 1 {
response {
msg 0 : string
}
}
say 2 {
request {
name 0 : string
msg 1 : string
}
}
quit 3 {}
]]
proto.s2c = sprotoparser.parse [[
.package {
type 0 : integer
session 1 : integer
}
heartbeat 1 {}
]]
return proto
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
main.lua代碼:
local skynet = require "skynet"
-- 啓動服務(啓動函數)
skynet.start(function()
-- 啓動函數裏調用Skynet API開發各種服務
print("======Server start=======")
-- skynet.newservice(name, ...)啓動一個新的 Lua 服務(服務腳本文件名)
skynet.newservice("socket1")
-- 退出當前的服務
skynet.exit()
end)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
socket1.lua服務端:
local skynet = require "skynet"
require "skynet.manager" -- import skynet.register
local socket = require "socket"
local proto = require "proto"
local sproto = require "sproto"
local host
local REQUEST = {}
function REQUEST:say()
print("say", self.name, self.msg)
end
function REQUEST:handshake()
print("handshake")
end
function REQUEST:quit()
print("quit")
end
local function request(name, args, response)
local f = assert(REQUEST[name])
local r = f(args)
if response then
-- 生成迴應包(response是一個用於生成迴應包的函數。)
-- 處理session對應問題
-- return response(r)
end
end
local function send_package(fd,pack)
-- 協議與客戶端對應(兩字節長度包頭+內容)
local package = string.pack(">s2", pack)
socket.write(fd, package)
end
local function accept(id)
-- 每當 accept 函數獲得一個新的 socket id 後,並不會立即收到這個 socket 上的數據。這是因爲,我們有時會希望把這個 socket 的操作權轉讓給別的服務去處理。
-- 任何一個服務只有在調用 socket.start(id) 之後,纔可以收到這個 socket 上的數據。
socket.start(id)
host = sproto.new(proto.c2s):host "package"
-- request = host:attach(sproto.new(proto.c2s))
while true do
local str = socket.read(id)
if str then
local type,str2,str3,str4 = host:dispatch(str)
if type=="REQUEST" then
-- REQUEST : 第一個返回值爲 "REQUEST" 時,表示這是一個遠程請求。如果請求包中沒有 session 字段,表示該請求不需要回應。這時,第 2 和第 3 個返回值分別爲消息類型名(即在 sproto 定義中提到的某個以 . 開頭的類型名),以及消息內容(通常是一個 table );如果請求包中有 session 字段,那麼還會有第 4 個返回值:一個用於生成迴應包的函數。
local ok, result = pcall(request, str2,str3,str4)
if ok then
if result then
socket.write(id, "收到了")
-- 暫時不使用迴應包迴應
-- print("response:"..result)
-- send_package(id,result)
end
else
skynet.error(result)
end
end
if type=="RESPONSE" then
-- RESPONSE :第一個返回值爲 "RESPONSE" 時,第 2 和 第 3 個返回值分別爲 session 和消息內容。消息內容通常是一個 table ,但也可能不存在內容(僅僅是一個迴應確認)。
-- 暫時不處理客戶端的迴應
print("client response")
end
else
socket.close(id)
return
end
end
end
skynet.start(function()
print("==========Socket Start=========")
local id = socket.listen("127.0.0.1", 8888)
print("Listen socket :", "127.0.0.1", 8888)
socket.start(id , function(id, addr)
-- 接收到客戶端連接或發送消息()
print("connect from " .. addr .. " " .. id)
-- 處理接收到的消息
accept(id)
end)
--可以爲自己註冊一個別名。(別名必須在 32 個字符以內)
skynet.register "SOCKET1"
end)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
client1.lua客戶端:
package.cpath = "luaclib/?.so"
package.path = "lualib/?.lua;myexample/e5/?.lua"
if _VERSION ~= "Lua 5.3" then
error "Use lua 5.3"
end
local socket = require "clientsocket"
-- 通信協議
local proto = require "proto"
local sproto = require "sproto"
local host = sproto.new(proto.s2c):host "package"
local request = host:attach(sproto.new(proto.c2s))
local fd = assert(socket.connect("127.0.0.1", 8888))
local session = 0
local function send_request(name, args)
session = session + 1
local str = request(name, args, session)
-- 解包測試
-- local host2 = sproto.new(proto.c2s):host "package"
-- local type,str2 = host2:dispatch(str)
-- print(type)
-- print(str2)
socket.send(fd, str)
print("Request:", session)
end
send_request("handshake")
send_request("say", { name = "soul", msg = "hello world" })
while true do
-- 接收服務器返回消息
local str = socket.recv(fd)
-- print(str)
if str~=nil and str~="" then
print("server says: "..str)
-- socket.close(fd)
-- break;
end
-- 讀取用戶輸入消息
local readstr = socket.readstdin()
if readstr then
if readstr == "quit" then
send_request("quit")
-- socket.close(fd)
-- break;
else
-- 把用戶輸入消息發送給服務器
send_request("say", { name = "soul", msg = readstr })
end
else
socket.usleep(100)
end
end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
代碼說明:
由於客戶端沒有做解包處理,所以服務端暫不做封包處理,直接發送:收到了
這樣簡化了流程,也方便同學們理解。
服務端的封包很簡單,只需要REQUEST各個方法根據協議返回對象就可以了,request的response會進行封包處理。當然,如果是服務端單獨發送消息給客戶端(如心跳包),這樣就需要和客戶端一樣調用sproto進行封包。(具體可參考examples/agent)
運行服務端:
./skynet ./myexample/e5/config
運行客戶端:
./3rd/lua/lua ./myexample/e5/client1.lua
最終運行效果
服務端:
客戶端:
項目源碼:http://download.csdn.net/detail/uisoul/9792098
參考文檔:
https://github.com/cloudwu/skynet/wiki/Sproto
https://github.com/cloudwu/sproto