Skynet基礎入門例子詳解(5)

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章