给skynet增加websocket模块

 最近迷上了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(),底层会帮你调用。

  1. local skynet = require "skynet"  
  2. local string = require "string"  
  3. local crypt = require "crypt"  
  4. local socket = require "socket"  
  5. local httpd = require "http.httpd"  
  6. local sockethelper = require "http.sockethelper"  
  7. local urllib = require "http.url"  
  8.   
  9. local ws = {}  
  10. local ws_mt = { __index = ws }  
  11.   
  12. local function response(id, ...)  
  13.     return httpd.write_response(sockethelper.writefunc(id), ...)  
  14. end  
  15.   
  16.   
  17. local function write(id, data)  
  18.     socket.write(id, data)  
  19. end  
  20.   
  21. local function read(id, sz)  
  22.     return socket.read(id, sz)  
  23. end  
  24.   
  25.   
  26. local function challenge_response(key, protocol)  
  27.     local accept = crypt.base64encode(crypt.sha1(key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))  
  28.     return string.format("HTTP/1.1 101 Switching Protocols\r\n" ..  
  29.                         "Upgrade: websocket\r\n" ..  
  30.                         "Connection: Upgrade\r\n" ..  
  31.                         "Sec-WebSocket-Accept: %s\r\n" ..  
  32.                         "%s\r\n", accept, protocol or "")  
  33.       
  34. end  
  35.   
  36. local function accept_connection(header, check_origin, check_origin_ok)  
  37.     -- Upgrade header should be present and should be equal to WebSocket  
  38.     if not header["upgrade"or header["upgrade"]:lower() ~= "websocket" then  
  39.         return 400"Can \"Upgrade\" only to \"WebSocket\"."  
  40.     end  
  41.   
  42.     -- Connection header should be upgrade. Some proxy servers/load balancers  
  43.     -- might mess with it.  
  44.     if not header["connection"or not header["connection"]:lower():find("upgrade"1,true) then  
  45.         return 400"\"Connection\" must be \"Upgrade\"."  
  46.     end  
  47.   
  48.     -- Handle WebSocket Origin naming convention differences  
  49.     -- The difference between version 8 and 13 is that in 8 the  
  50.     -- client sends a "Sec-Websocket-Origin" header and in 13 it's  
  51.     -- simply "Origin".  
  52.     local origin = header["origin"or header["sec-websocket-origin"]  
  53.     if origin and check_origin and not check_origin_ok(origin, header["host"]) then  
  54.         return 403"Cross origin websockets not allowed"  
  55.     end  
  56.   
  57.     if not header["sec-websocket-version"or header["sec-websocket-version"] ~= "13" then  
  58.         return 400"HTTP/1.1 Upgrade Required\r\nSec-WebSocket-Version: 13\r\n\r\n"  
  59.     end  
  60.   
  61.     local key = header["sec-websocket-key"]  
  62.     if not key then  
  63.         return 400"\"Sec-WebSocket-Key\" must not be  nil."  
  64.     end  
  65.   
  66.     local protocol = header["sec-websocket-protocol"]   
  67.     if protocol then  
  68.         local i = protocol:find(","1, true)  
  69.         protocol = "Sec-WebSocket-Protocol: " .. protocol:sub(1, i or i-1)  
  70.     end  
  71.   
  72.     return nil, challenge_response(key, protocol)  
  73.   
  74. end  
  75.   
  76. local H = {}  
  77.   
  78. function H.check_origin_ok(origin, host)  
  79.     return urllib.parse(origin) == host  
  80. end  
  81.   
  82. function H.on_open(ws)  
  83.       
  84. end  
  85.   
  86. function H.on_message(ws, message)  
  87.      
  88. end  
  89.   
  90. function H.on_close(ws, code, reason)  
  91.       
  92. end  
  93.   
  94. function H.on_pong(ws, data)  
  95.     -- Invoked when the response to a ping frame is received.  
  96.   
  97. end  
  98.   
  99.   
  100. function ws.new(id, header, handler, conf)  
  101.     local conf = conf or {}  
  102.     local handler = handler or {}  
  103.   
  104.     setmetatable(handler, { __index = H })  
  105.   
  106.     local code, result = accept_connection(header, conf.check_origin, handler.check_origin_ok)  
  107.   
  108.     if code then  
  109.         response(id, code, result)  
  110.         socket.close(id)  
  111.     else  
  112.         write(id, result)  
  113.     end  
  114.   
  115.     local self = {  
  116.         id = id,  
  117.         handler = handler,  
  118.         mask_outgoing = conf.mask_outgoing,  
  119.         check_origin = conf.check_origin  
  120.     }  
  121.   
  122.     self.handler.on_open(self)  
  123.    
  124.     return setmetatable(self, ws_mt)  
  125. end  
  126.   
  127.   
  128. function ws:send_frame(fin, opcode, data)  
  129.     if fin then  
  130.         finbit = 0x80  
  131.     else  
  132.         finbit = 0  
  133.     end  
  134.   
  135.     frame = string.pack("B", finbit | opcode)  
  136.     l = #data  
  137.   
  138.     if self.mask_outgoing then  
  139.         mask_bit = 0x80  
  140.     else  
  141.         mask_bit = 0  
  142.     end  
  143.   
  144.     if l < 126 then  
  145.         frame = frame .. string.pack("B", l | mask_bit)  
  146.     elseif l < 0xFFFF then  
  147.         frame = frame .. string.pack("!BH"126 | mask_bit, l)  
  148.     else   
  149.         frame = frame .. string.pack("!BL"127 | mask_bit, l)  
  150.     end  
  151.   
  152.     if self.mask_outgoing then  
  153.     end  
  154.   
  155.     frame = frame .. data  
  156.   
  157.     write(self.id, frame)  
  158.       
  159. end  
  160.   
  161. function ws:send_text(data)  
  162.     self:send_frame(true, 0x1, data)  
  163. end  
  164.   
  165. function ws:send_binary(data)  
  166.     self:send_frame(true, 0x2, data)  
  167. end  
  168.   
  169.   
  170. function ws:send_ping(data)  
  171.     self:send_frame(true, 0x9, data)  
  172. end  
  173.   
  174.   
  175. function ws:send_pong(data)  
  176.     self:send_frame(true, 0xA, data)  
  177. end  
  178.   
  179. function ws:close(code, reason)  
  180.     -- 1000  "normal closure" status code  
  181.     if code == nil and reason ~= nil then  
  182.         code = 1000  
  183.     end  
  184.     local data = ""  
  185.     if code ~= nil then  
  186.         data = string.pack(">H", code)  
  187.     end  
  188.     if reason ~= nil then  
  189.         data = data .. reason  
  190.     end  
  191.     self:send_frame(true, 0x8, data)  
  192. end  
  193.   
  194. function ws:recv()  
  195.     local data = ""  
  196.     while true do  
  197.         local success, final, message = self:recv_frame()  
  198.         if not success then  
  199.             return success, message  
  200.         end  
  201.         if final then  
  202.             data = data .. message  
  203.             break  
  204.         else  
  205.             data = data .. message  
  206.         end  
  207.     end  
  208.     self.handler.on_message(self, data)  
  209.     return data  
  210. end  
  211.   
  212. local function websocket_mask(mask, data, length)  
  213.     umasked = {}  
  214.     for i=1, length do  
  215.         umasked[i] = string.char(string.byte(data, i) ~ string.byte(mask, (i-1)%4 + 1))  
  216.     end  
  217.     return table.concat(umasked)  
  218. end  
  219.   
  220. function ws:recv_frame()  
  221.     local data, err = read(self.id, 2)  
  222.   
  223.     if not data then  
  224.         return false, nil, "Read first 2 byte error: " .. err  
  225.     end  
  226.   
  227.     local header, payloadlen = string.unpack("BB", data)  
  228.     local final_frame = header & 0x80 ~= 0  
  229.     local reserved_bits = header & 0x70 ~= 0  
  230.     local frame_opcode = header & 0xf  
  231.     local frame_opcode_is_control = frame_opcode & 0x8 ~= 0  
  232.   
  233.     if reserved_bits then  
  234.         -- client is using as-yet-undefined extensions  
  235.         return false, nil, "Reserved_bits show using undefined extensions"  
  236.     end  
  237.   
  238.     local mask_frame = payloadlen & 0x80 ~= 0  
  239.     payloadlen = payloadlen & 0x7f  
  240.   
  241.     if frame_opcode_is_control and payloadlen >= 126 then  
  242.         -- control frames must have payload < 126  
  243.         return false, nil, "Control frame payload overload"  
  244.     end  
  245.   
  246.     if frame_opcode_is_control and not final_frame then  
  247.         return false, nil, "Control frame must not be fragmented"  
  248.     end  
  249.   
  250.     local frame_length, frame_mask  
  251.   
  252.     if payloadlen < 126 then  
  253.         frame_length = payloadlen  
  254.   
  255.     elseif payloadlen == 126 then  
  256.         local h_data, err = read(self.id, 2)  
  257.         if not h_data then  
  258.             return false, nil, "Payloadlen 126 read true length error:" .. err  
  259.         end  
  260.         frame_length = string.pack("!H", h_data)  
  261.   
  262.     else --payloadlen == 127  
  263.         local l_data, err = read(self.id, 8)  
  264.         if not l_data then  
  265.             return false, nil, "Payloadlen 127 read true length error:" .. err  
  266.         end  
  267.         frame_length = string.pack("!L", l_data)  
  268.     end  
  269.   
  270.   
  271.     if mask_frame then  
  272.         local mask, err = read(self.id, 4)  
  273.         if not mask then  
  274.             return false, nil, "Masking Key read error:" .. err  
  275.         end  
  276.         frame_mask = mask  
  277.     end  
  278.   
  279.     --print('final_frame:', final_frame, "frame_opcode:", frame_opcode, "mask_frame:", mask_frame, "frame_length:", frame_length)  
  280.   
  281.     local  frame_data = ""  
  282.     if frame_length > 0 then  
  283.         local fdata, err = read(self.id, frame_length)  
  284.         if not fdata then  
  285.             return false, nil, "Payload data read error:" .. err  
  286.         end  
  287.         frame_data = fdata  
  288.     end  
  289.   
  290.     if mask_frame and frame_length > 0 then  
  291.         frame_data = websocket_mask(frame_mask, frame_data, frame_length)  
  292.     end  
  293.   
  294.   
  295.     if not final_frame then  
  296.         return true, false, frame_data  
  297.     else  
  298.         if frame_opcode  == 0x1 then -- text  
  299.             return true, true, frame_data  
  300.         elseif frame_opcode == 0x2 then -- binary  
  301.             return true, true, frame_data  
  302.         elseif frame_opcode == 0x8 then -- close  
  303.             local code, reason  
  304.             if #frame_data >= 2 then  
  305.                 code = string.unpack(">H", frame_data:sub(1,2))  
  306.             end  
  307.             if #frame_data > 2 then  
  308.                 reason = frame_data:sub(3)  
  309.             end  
  310.             self:close()  
  311.             socket.close(self.id)  
  312.             self.handler.on_close(self, code, reason)  
  313.         elseif frame_opcode == 0x9 then --Ping  
  314.             self:send_pong()  
  315.         elseif frame_opcode == 0xA then -- Pong  
  316.             self.handler.on_pong(self, frame_data)  
  317.         end  
  318.   
  319.         return true, true, nil  
  320.     end  
  321.   
  322. end  
  323.   
  324. function ws:start()  
  325.     while true do  
  326.         local message, err = self:recv()  
  327.         if not message then  
  328.             --print('recv eror:', message, err)  
  329.             socket.close(self.id)  
  330.         end  
  331.     end  
  332. end  
  333.   
  334.   
  335. return ws  
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[i] = 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

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

  1. local skynet = require "skynet"  
  2. local socket = require "socket"  
  3. local string = require "string"  
  4. local websocket = require "websocket"  
  5. local httpd = require "http.httpd"  
  6. local urllib = require "http.url"  
  7. local sockethelper = require "http.sockethelper"  
  8.   
  9.   
  10. local handler = {}  
  11. function handler.on_open(ws)  
  12.     print(string.format("%d::open", ws.id))  
  13. end  
  14.   
  15. function handler.on_message(ws, message)  
  16.     print(string.format("%d receive:%s", ws.id, message))  
  17.     ws:send_text(message .. "from server")  
  18. end  
  19.   
  20. function handler.on_close(ws, code, reason)  
  21.     print(string.format("%d close:%d  %s", ws.id, code, reason))  
  22. end  
  23.   
  24. local function handle_socket(id)  
  25.     -- limit request body size to 8192 (you can pass nil to unlimit)  
  26.     local code, url, method, header, body = httpd.read_request(sockethelper.readfunc(id), 8192)  
  27.     if code then  
  28.           
  29.         if url == "/ws" then  
  30.             local ws = websocket.new(id, header, handler)  
  31.             ws:start()  
  32.         end  
  33.     end  
  34.   
  35.   
  36. end  
  37.   
  38. skynet.start(function()  
  39.     local address = "0.0.0.0:8001"  
  40.     skynet.error("Listening "..address)  
  41.     local id = assert(socket.listen(address))  
  42.     socket.start(id , function(id, addr)  
  43.        socket.start(id)  
  44.        pcall(handle_socket, id)  
  45.     end)  
  46. end)  
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)

详细信息请到  https://github.com/Skycrab/skynet_websocket
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值