吾知网

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 6370|回复: 0
打印 上一主题 下一主题

给skynet增加websocket模块

[复制链接]
跳转到指定楼层
楼主
发表于 2019-1-13 19:50:20 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

 最近迷上了skynet,代码质量很高,算开源游戏服务器框架中的佼佼者,不管是Python的firefly,C++/Python的kbengine,C#的scut,还是nodejs的pomelo,skynet在并发上和商业应用都有很大的优势,根据http://thislinux.com/blog/5_panic.html描述,skynet能支持单机3w在线用户,性能很是给力。


      最近做的都是一些h5小游戏,用tornado/django基本上也都绰绰有余,下个小游戏打算试试skynet,skynet没有带websocket库,于是就很happy的去造轮子去了,虽然有lua-resty-websocket这个nginx扩展库,有2个原因我不喜欢。


     1.lua-resty-websocket实在太老了,现在已经是lua53的时代了


     2.还是喜欢tornado websocket的基于回调的方式,当然我写的既可使用回调方式,也可使用lua-resty-websocket


基于直接recv的方式


    其实解析websocket还是比较简单的,比较复杂点的是websocket 的close操作。和握手一样,close也是需要客户端-服务器


端确认的。


    当客户端->close ->服务端,服务端接收到opcode为8的close事件,服务端发送close frame,然后关闭客户端socket


    当服务端->close ->客户端,服务器发送close frame,此时客户端得到close事件,客户端接着会主动发送close frame给服务端,服务端接收到


opcode为8的close事件,关闭客户端socket。


这里需要注意,如果用js 的话,var ws = new WebSocket('XXXX'),在onclose事件中不需要主动调用ws.close(),底层会帮你调用。

local skynet = require "skynet"

local string = require "string"

local crypt = require "crypt"

local socket = require "socket"

local httpd = require "http.httpd"

local sockethelper = require "http.sockethelper"

local urllib = require "http.url"

 

local ws = {}

local ws_mt = { __index = ws }

 

local function response(id, ...)

    return httpd.write_response(sockethelper.writefunc(id), ...)

end

 

 

local function write(id, data)

    socket.write(id, data)

end

 

local function read(id, sz)

    return socket.read(id, sz)

end

 

 

local function challenge_response(key, protocol)

    local accept = crypt.base64encode(crypt.sha1(key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))

    return string.format("HTTP/1.1 101 Switching Protocols\r\n" ..

                        "Upgrade: websocket\r\n" ..

                        "Connection: Upgrade\r\n" ..

                        "Sec-WebSocket-Accept: %s\r\n" ..

                        "%s\r\n", accept, protocol or "")

    

end

 

local function accept_connection(header, check_origin, check_origin_ok)

    -- Upgrade header should be present and should be equal to WebSocket

    if not header["upgrade"] or header["upgrade"]:lower() ~= "websocket" then

        return 400, "Can \"Upgrade\" only to \"WebSocket\"."

    end

 

    -- Connection header should be upgrade. Some proxy servers/load balancers

    -- might mess with it.

    if not header["connection"] or not header["connection"]:lower():find("upgrade", 1,true) then

        return 400, "\"Connection\" must be \"Upgrade\"."

    end

 

    -- Handle WebSocket Origin naming convention differences

    -- The difference between version 8 and 13 is that in 8 the

    -- client sends a "Sec-Websocket-Origin" header and in 13 it's

    -- simply "Origin".

    local origin = header["origin"] or header["sec-websocket-origin"]

    if origin and check_origin and not check_origin_ok(origin, header["host"]) then

        return 403, "Cross origin websockets not allowed"

    end

    if not header["sec-websocket-version"] or header["sec-websocket-version"] ~= "13" then

        return 400, "HTTP/1.1 Upgrade Required\r\nSec-WebSocket-Version: 13\r\n\r\n"

    end

    local key = header["sec-websocket-key"]

    if not key then

        return 400, "\"Sec-WebSocket-Key\" must not be  nil."

    end

    local protocol = header["sec-websocket-protocol"] 

    if protocol then

        local i = protocol:find(",", 1, true)

        protocol = "Sec-WebSocket-Protocol: " .. protocol:sub(1, i or i-1)

    end

    return nil, challenge_response(key, protocol)

end

local H = {}

function H.check_origin_ok(origin, host)

    return urllib.parse(origin) == host

end

function H.on_open(ws)

    

end

function H.on_message(ws, message)

   

end

function H.on_close(ws, code, reason)

    

end

function H.on_pong(ws, data)

    -- Invoked when the response to a ping frame is received.

end

function ws.new(id, header, handler, conf)

    local conf = conf or {}

    local handler = handler or {}

    setmetatable(handler, { __index = H })

    local code, result = accept_connection(header, conf.check_origin, handler.check_origin_ok)

    if code then

        response(id, code, result)

        socket.close(id)

    else

        write(id, result)

    end

    local self = {

        id = id,

        handler = handler,

        mask_outgoing = conf.mask_outgoing,

        check_origin = conf.check_origin

    }

    self.handler.on_open(self)

 

    return setmetatable(self, ws_mt)

end

function ws:send_frame(fin, opcode, data)

    if fin then

        finbit = 0x80

    else

        finbit = 0

    end

    frame = string.pack("B", finbit | opcode)

    l = #data

    if self.mask_outgoing then

        mask_bit = 0x80

    else

        mask_bit = 0

    end

    if l < 126 then

        frame = frame .. string.pack("B", l | mask_bit)

    elseif l < 0xFFFF then

        frame = frame .. string.pack("!BH", 126 | mask_bit, l)

    else 

        frame = frame .. string.pack("!BL", 127 | mask_bit, l)

    end

    if self.mask_outgoing then

    end

    frame = frame .. data

    write(self.id, frame)

    

end

function ws:send_text(data)

    self:send_frame(true, 0x1, data)

end

function ws:send_binary(data)

    self:send_frame(true, 0x2, data)

end

function ws:send_ping(data)

    self:send_frame(true, 0x9, data)

end

function ws:send_pong(data)

    self:send_frame(true, 0xA, data)

end

function ws:close(code, reason)

    -- 1000  "normal closure" status code

    if code == nil and reason ~= nil then

        code = 1000

    end

    local data = ""

    if code ~= nil then

        data = string.pack(">H", code)

    end

    if reason ~= nil then

        data = data .. reason

    end

    self:send_frame(true, 0x8, data)

end

function ws:recv()

    local data = ""

    while true do

        local success, final, message = self:recv_frame()

        if not success then

            return success, message

        end

        if final then

            data = data .. message

            break

        else

            data = data .. message

        end

    end

    self.handler.on_message(self, data)

    return data

end

local function websocket_mask(mask, data, length)

    umasked = {}

    for i=1, length do

        umasked = string.char(string.byte(data, i) ~ string.byte(mask, (i-1)%4 + 1))

    end

    return table.concat(umasked)

end

function ws:recv_frame()

    local data, err = read(self.id, 2)

    if not data then

        return false, nil, "Read first 2 byte error: " .. err

    end

    local header, payloadlen = string.unpack("BB", data)

    local final_frame = header & 0x80 ~= 0

    local reserved_bits = header & 0x70 ~= 0

    local frame_opcode = header & 0xf

    local frame_opcode_is_control = frame_opcode & 0x8 ~= 0

    if reserved_bits then

        -- client is using as-yet-undefined extensions

        return false, nil, "Reserved_bits show using undefined extensions"

    end

    local mask_frame = payloadlen & 0x80 ~= 0

    payloadlen = payloadlen & 0x7f

    if frame_opcode_is_control and payloadlen >= 126 then

        -- control frames must have payload < 126

        return false, nil, "Control frame payload overload"

    end

    if frame_opcode_is_control and not final_frame then

        return false, nil, "Control frame must not be fragmented"

    end

    local frame_length, frame_mask

    if payloadlen < 126 then

        frame_length = payloadlen

    elseif payloadlen == 126 then

        local h_data, err = read(self.id, 2)

        if not h_data then

            return false, nil, "Payloadlen 126 read true length error:" .. err

        end

        frame_length = string.pack("!H", h_data)

    else --payloadlen == 127

        local l_data, err = read(self.id, 8)

        if not l_data then

            return false, nil, "Payloadlen 127 read true length error:" .. err

        end

        frame_length = string.pack("!L", l_data)

    end

    if mask_frame then

        local mask, err = read(self.id, 4)

        if not mask then

            return false, nil, "Masking Key read error:" .. err

        end

        frame_mask = mask

    end

    --print('final_frame:', final_frame, "frame_opcode:", frame_opcode, "mask_frame:", mask_frame, "frame_length:", frame_length)

    local  frame_data = ""

    if frame_length > 0 then

        local fdata, err = read(self.id, frame_length)

        if not fdata then

            return false, nil, "Payload data read error:" .. err

        end

        frame_data = fdata

    end

    if mask_frame and frame_length > 0 then

        frame_data = websocket_mask(frame_mask, frame_data, frame_length)

    end

    if not final_frame then

        return true, false, frame_data

    else

        if frame_opcode  == 0x1 then -- text

            return true, true, frame_data

        elseif frame_opcode == 0x2 then -- binary

            return true, true, frame_data

        elseif frame_opcode == 0x8 then -- close

            local code, reason

            if #frame_data >= 2 then

                code = string.unpack(">H", frame_data:sub(1,2))

            end

            if #frame_data > 2 then

                reason = frame_data:sub(3)

            end

            self:close()

            socket.close(self.id)

            self.handler.on_close(self, code, reason)

        elseif frame_opcode == 0x9 then --Ping

            self:send_pong()

        elseif frame_opcode == 0xA then -- Pong

            self.handler.on_pong(self, frame_data)

        end

        return true, true, nil

    end

end

function ws:start()

    while true do

        local message, err = self:recv()

        if not message then

            --print('recv eror:', message, err)

            socket.close(self.id)

        end

    end

end

return ws


是用方法也很简单,基于回调的方式用起来真是舒服


local skynet = require "skynet"

local socket = require "socket"

local string = require "string"

local websocket = require "websocket"

local httpd = require "http.httpd"

local urllib = require "http.url"

local sockethelper = require "http.sockethelper"

 

 

local handler = {}

function handler.on_open(ws)

    print(string.format("%d::open", ws.id))

end

 

function handler.on_message(ws, message)

    print(string.format("%d receive:%s", ws.id, message))

    ws:send_text(message .. "from server")

end

 

function handler.on_close(ws, code, reason)

    print(string.format("%d close:%d  %s", ws.id, code, reason))

end

 

local function handle_socket(id)

    -- limit request body size to 8192 (you can pass nil to unlimit)

    local code, url, method, header, body = httpd.read_request(sockethelper.readfunc(id), 8192)

    if code then

        

        if url == "/ws" then

            local ws = websocket.new(id, header, handler)

            ws:start()

        end

    end

 

 

end

 

skynet.start(function()

    local address = "0.0.0.0:8001"

    skynet.error("Listening "..address)

    local id = assert(socket.listen(address))

    socket.start(id , function(id, addr)

       socket.start(id)

       pcall(handle_socket, id)

    end)

end)


--------------------- 

作者:yueguanghaidao 

来源:CSDN 

原文:https://blog.csdn.net/yueguangha ... ails/45207059 


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|吾知网 ( 粤ICP备13013563号-1 )

GMT+8, 2024-4-19 23:11 , Processed in 1.171875 second(s), 8 queries , Memcache On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表