您的位置:首页 > 运维架构

利用openresty写一个简易的基于websocket的即时通讯(im)聊天

2017-12-07 00:00 976 查看
摘要: lua-resty-websocket是基于ngx_lua模块的Lua Websocket实现,实现了Websocket服务端和客户端,采用了ngx_lua的cosocket API ,是非阻塞的。

1.配置文件

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的订阅/发布来实现消息的接收与发送

--[[

- @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.页面

<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>  
<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.运行结果:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  redis openresty websocket
相关文章推荐