我從開源項目(https://github.com/lipp/lua-websockets,這里我們簡稱LWS)中抽出了websocket的部分處理,步驟如下:
1)首先是解決LWS的幾個依賴問題。LWS在握手階段的base64編解碼使用了luasocket中的mime,因此在3rd文件夾中建立mime文件夾,將mime.h和mime.c文件放進去,修改skynet的Makefile文件,作為一個單獨的動態庫編譯:
1 LUA_CLIB = skynet \ 2 client \ 3 bson md5 sproto lpeg mime
1 $(LUA_CLIB_PATH)/mime.so : 3rd/mime/mime.c | $(LUA_CLIB_PATH) 2 $(CC) $(CFLAGS) $(SHARED) -I3rd/mime $^ -o $@
在tools.lua中引用了mime,由於我們只用了C文件,因此修改require "mime" 為: require "mime.core" 。
對於luabitop,skynet自帶的lua5.3源碼已經支持(在lbitlib.c文件中),但是默認是關閉的,修改lua5.3的Makefile,增加LUA_COMPAT_BITLIB宏並重新編譯:
1 MYCFLAGS=-I../../skynet-src -g -DLUA_COMPAT_BITLIB
這樣在bit.lua中對bit32就能正確引用了。
2)下一步則是在skynet端偵聽tcp連接:
1 local skynet = require "skynet" 2 local socket = require "skynet.socket" 3 4 skynet.start(function() 5 local id = socket.listen("0.0.0.0", 8001) 6 skynet.error("web server listen on web port 8001") 7 8 socket.start(id, function(id, addr) 9 local agent = skynet.newservice("wsagent") 10 skynet.error(string.format("%s connected, pass it to agent :%08x", addr, agent)) 11 skynet.send(agent, "lua", id, "start") 12 end) 13 end)
這里每次偵聽到一個新連接后就建立wsagent,后續的握手及數據傳輸一並交給其處理。
3)在lualib下建立websocket文件夾,放入handshake.lua(握手協議)、frame.lua(幀數據格式解析)、bit.lua、tools.lua。在wsagent.lua中實現握手、數據傳輸、關閉連接如下:
1 local skynet = require "skynet" 2 local socket = require "skynet.socket" 4 local frame = require "websocket.frame" 5 local handshake = require "websocket.handshake" 6 local sockethelper = require "http.sockethelper" 7 8 local agent = { } 9 local REQUEST = { } 10 local FD, read, write 11 12 local function _send(message) 13 local encoded = frame.encode(message, frame.TEXT) 14 write(encoded) 15 end 16 17 local function _handshake(fd) 18 FD = fd 19 read = sockethelper.readfunc(fd) 20 write = sockethelper.writefunc(fd) 21 22 local header = "" 23 while true do 24 local bytes = read() 25 header = header .. bytes 26 if #header > 8192 then 27 skynet.error("<websocket.handshake>error: header size > 8192") 28 return 29 end 30 31 local _, to = header:find("\r\n\r\n", -#bytes-3, true) 32 if to then 33 header = header:sub(1, to) 34 break 35 end 36 end 37 38 print("accept handshake http request:" .. header) 39 40 local protocols = { } -- todo: how to set protocols? 41 local response, protocol = handshake.accept_upgrade(header, protocols) 42 if not response then 43 skynet.error("<websocket.handshake>error: handshake parse header fault") 44 return 45 end 46 47 print("send handshake http response:" .. response) 48 49 write(response) 50 skynet.error(string.format("<websocket.handshake>web socket %q connection established", fd)) 51 52 return true 53 end 54 55 local function _close() 56 local encoded = frame.encode_close(1000, 'force close') 57 encoded = frame.encode(encoded, frame.CLOSE) 58 59 print("force close:" .. encoded) 60 61 write(encoded) 62 socket.close(FD) 63 end 64 65 local function _dispatch(text, opcode) 66 print(string.format("<websocket>opcode:%q message:%q", opcode, text)) 67 68 local TEXT = assert(frame.TEXT) 69 local CLOSE = assert(frame.CLOSE) 70 assert(opcode == TEXT or opcode == CLOSE, opcode) 71 72 if opcode == TEXT then 81 -- todo: logic 82 return true 83 end 84 85 if opcode == CLOSE then 86 local code, reason = frame.decode_close(message) 87 print(string.format("<websocket>CLOSE code:%q reason:%q", code, reason)) 88 local encoded = frame.encode_close(code) 89 encoded = frame.encode(encoded, frame.CLOSE) 90 91 local ok, err = pcall(write, encoded) 92 if not ok then 93 -- remote endpoint may has closed tcp-connection already 94 skynet.error("write close protocol failure:" .. tostring(err)) 95 end 96 socket.close(assert(FD)) 97 end 98 end 99 100 local function _recv() 101 local last 102 local frames = {} 103 local first_opcode 104 105 while true do 106 -- skynet will report error and close socket if socket error (see socket.lua) 107 local encoded = read() 108 if last then 109 encoded = last .. encoded 110 last = nil 111 end 112 113 repeat 114 local decoded, fin, opcode, rest = frame.decode(encoded) 115 if decoded then 116 if not first_opcode then 117 first_opcode = opcode 118 end 119 table.insert(frames, decoded) 120 encoded = rest 121 if fin == true then 122 if not _dispatch(table.concat(frames), first_opcode) then 123 -- socket closed in [_dispatch] 124 return 125 end 126 frames = { } 127 first_opcode = nil 128 end 129 end 130 until (not decoded) 131 132 if #encoded > 0 then 133 last = encoded 134 end 135 end 136 end 137 138 function agent.start(fd) 139 socket.start(fd) 140 141 skynet.error("<websocket>start handshake") 142 if not _handshake(fd) then 143 socket.close(fd) 144 skynet.exit() 145 return 146 end 147 148 skynet.error("<websocket>receive and dispatch") 149 _recv() 150 151 skynet.error("<websocket>exit") 152 skynet.exit() 153 end 154 155 skynet.start(function() 156 skynet.dispatch("lua", function (_, _, fd, method, ...) 157 local f = assert(agent[method]) 158 skynet.retpack(f(fd, ...)) 159 end) 160 end)
代碼我已放到git上:https://github.com/zhoujijian/skynet-websocket