利用openresty寫一個簡易的基於websocket的即時通信(im)聊天

1.配置文件javascript

server{

 listen 8009; 
 proxy_ignore_client_abort on; #不容許客戶端主動關閉鏈接


 location /test/websocket {
        lua_check_client_abort on; #是否監視client提早關閉請求的事件,若是打開監視,會調用ngx.on_abort()註冊的回調

        lua_socket_log_errors off; #當套接字發生錯誤時,底層ngx_lua模塊會進行錯誤記錄,若是你已經在本身的lua代碼中進行了適當的錯誤處理,那麼建議關閉lua_socket_log_errors指令來禁用此自動錯誤日誌記錄


        content_by_lua_file /Users/guanguan/study/2017/or-websocket/websocket.lua;
}





    access_log   /usr/local/openresty/nginx/logs/websocket_access.log  main;
    error_log    /usr/local/openresty/nginx/logs/websocket_error.log  debug;


}

2.websocket.lua 利用redis的訂閱/發佈來實現消息的接收與發送html

--[[

- @desc   用於調試  lua數據輸出

- @param  string   字符串 

- return  string

--]]
function dump(v)
    if not __dump then
        function __dump(v, t, p)
            local k = p or "";

            if type(v) ~= "table" then
                table.insert(t, k .. " : " .. tostring(v));
            else
                for key, value in pairs(v) do
                    __dump(value, t, k .. "[" .. key .. "]");
                end
            end
        end
    end
    local t = { '======== Lib:Dump Content ========' };
    __dump(v, t);
    print(table.concat(t, "\n"));
end


local server = require "resty.websocket.server"
local redis = require "resty.redis"
local cjson = require "cjson"
ngx.log(ngx.ERR, "HH")

local function exit()
    --獲取URL參數

    local _GET = ngx.req.get_uri_args()
    ngx.log(ngx.ERR, "用戶" .. _GET['rnd'] .. " 離開了房間  : ", err)
    --if is_ws == nil then ngx.eof() end 

    ngx.flush(true)
    ngx.exit(ngx.HTTP_OK)
    return nil
end

local ok, err = ngx.on_abort(exit) --註冊一個函數  當客戶端斷開鏈接時執行

ngx.log(ngx.ERR,ok)
if err then
    ngx.log(ngx.ERR,err)
    return exit()
end

--獲取聊天室id 

local channel_id = 800
local channel_name = "chat_" .. tostring(channel_id)
ngx.log(ngx.ERR, "channel_name=> ", channel_name)

--create connection

local wb, err = server:new {
    timeout = 5000,
    max_payload_len = 65535
}


ngx.log(ngx.ERR,"建立websocket服務成功")
if not wb then
    ngx.log(ngx.ERR, "failed to new websocket: ", err)
    return exit()
end
ngx.log(ngx.ERR,"建立了~~~")


--- -建立redis實例

local getRedis = function(key)
    if not key then
        return nil
    end

    if ngx.ctx[key] then
        return ngx.ctx[key]
    end

    --dump('-------------建立redis實例---------------------')

    local red = redis:new()
    --red:set_timeout(5000) -- 1 sec 設置鏈接超時1秒

    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
        ngx.log(ngx.ERR, "failed to connect redis: ", err)
    end
    ngx.ctx[key] = red
    return red
end


local pub = function()
    local red = getRedis('redisfn1')
    --redis 訂閱 

    local res, err = red:subscribe(channel_name)
    if not res then
        ngx.log(ngx.ERR, "failed to sub redis: ", err)
        wb:send_close()
        return exit()
    end

    -- 不斷讀取數據若是有值馬上發送給客戶端

    while true do
        local res, err = red:read_reply()

        if res then
            local bytes, err = wb:send_text(cjson.encode(res))
            if not bytes then
                wb:send_close()
                ngx.log(ngx.ERR, "failed to send text: ", err)
                return exit()
            end
        end
        ngx.sleep(0.5)
    end
end

local co = ngx.thread.spawn(pub)



--main loop 

while true do
    -- 獲取數據

    local data, typ, err = wb:recv_frame()
    -- 若是鏈接損壞 退出

    if wb.fatal then
        ngx.log(ngx.ERR, "failed to receive frame: ", err)
        return exit()
    end

    if not data then

        local bytes, err = wb:send_ping()
        if not bytes then
            ngx.log(ngx.ERR, "failed to send ping: ", err)
            return exit()
        end

    elseif typ == "close" then

        break

    elseif typ == "ping" then

        local bytes, err = wb:send_pong()
        if not bytes then
            ngx.log(ngx.ERR, "failed to send pong: ", err)
            return exit()
        end

    elseif typ == "pong" then

        --ngx.log(ngx.ERR, "client ponged")


    elseif typ == "text" then

        --接收消息寫入redis 

        local red = getRedis('redisfn2')
        local res, err = red:publish(channel_name, data)
        if not res then
            ngx.log(ngx.ERR, " 接收消息寫入redis錯誤 failed to publish redis: ", err)
        end

    else
        break
    end
    ngx.sleep(0.5)
end

getRedis('redisfn1'):set_keepalive(10000, 100)
getRedis('redisfn2'):set_keepalive(10000, 100)
wb:send_close()
ngx.thread.wait(co)

3.頁面java

<html>
<head>
    <title>lua_socket方式作聊天</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta content="width=device-width, initial-scale=1.0, user-scalable=no" name="viewport" />
</head>
<body>
<script type="text/javascript">
    var ws = null;

    function WebSocketConn() {
        if (ws != null && ws.readyState == 1) {
            log("已經在線");
            return
        }

        if ("WebSocket" in window) {

//            ws = new WebSocket("ws://www.im.cn:80/test/websocket?rnd="+(Math.ceil(Math.random()*1000)) );
            ws = new WebSocket("ws://127.0.0.1:8009/test/websocket?rnd="+(Math.ceil(Math.random()*1000)) );

            ws.onopen = function() {
                log('成功進入聊天室');
            };

            ws.onmessage = function(event) {
                log(event.data)
            };

            ws.onclose = function() {
                // websocket is closed.

                log("已經和服務器斷開");
            };

            ws.onerror = function(event) {
                console.log("error " + event.data);
            };
        } else {
            // The browser doesn't support WebSocket

            alert("你的瀏覽器不支持 WebSocket!");
        }
    }

    function SendMsg() {
        if (ws != null && ws.readyState == 1) {
            var msg = document.getElementById('msgtext').value;
            ws.send(msg);
        } else {
            log('請先進入聊天室');
        }
    }

    function WebSocketClose() {
        if (ws != null && ws.readyState == 1) {
            ws.close();
            log("發送斷開服務器請求");
        } else {
            log("當前沒有鏈接服務器")
        }
    }

    function log(text) {
        var li = document.createElement('li');
        li.appendChild(document.createTextNode(text));
        document.getElementById('log').appendChild(li);
        return false;
    }
</script>


<div id="sse">
    <code class="language-html hljs ">
        <a href="#" onclick="WebSocketConn();">進入聊天室</a> &nbsp;
        <a href="#" onclick="WebSocketClose();">離開聊天室</a>
    </code>

    <br />
    <br />

    <input id="msgtext" type="text" value=""/><br />
    <a  onclick="SendMsg();">發送信息</a><br />
    <ol id="log"></ol>
</div>
</body>
</html>

4.運行結果:nginx

相關文章
相關標籤/搜索