之前玩物联网的时候经常用到MQTT,感觉也很好用,最近要做一个类似实时大屏幕的的系统,屏幕终端可能用很多种平台(Android、Linux、Mac、Windows)
正好之前用JS+HTML做过WS与服务器双向传输信息的东西【用的EMQX引擎】
效果很不错,网页也可以适应各个平台。
我就在想能不能不用MQTT用Redis,反正都是Pub/Sub模式
没想到一钻研就是一两天,也学到了许多东西,项目也做得不错了,在这里分享一下
我刚开始在百度搜了很久,如何实现将redis的订阅发布机制结合到websocket中,资料很少,看到几个用php+swoole这个方案的,但并不适合我的项目环境
所以我就想研究下
这两块,再结合一下可了吗,但远没有想象中这么简单啊
用redis模块试试
import redis
rc = redis.StrictRedis(host='****', port='6379', db=3, password='******')
ps = rc.pubsub()
while True:
msg = ps.parse_response()
print(msg)
这么一看还挺简单的对吧,不过有个死循环,再找找
import redis
rc = redis.StrictRedis(host='****', port='6379', db=3, password='******')
ps = rc.pubsub()
ps.subscribe('liao') #从liao订阅消息
for item in ps.listen(): #监听状态:有消息发布了就拿过来
if item['type'] == 'message':
print item['channel']
print item['data']
诶,怎么又有个用for的,查了一波,发现这个listen()方法就是封装了那个死循环
算了,将就用吧
调试也顺利调通,通过命令行向redis发布消息,python顺利print
网上找了个代码,用的websockets这个库
import websockets
import asyncio
USERS = set()
async def notify_users():
# 对注册列表内的客户端进行推送
if USERS: # asyncio.wait doesn't accept an empty list
message = input('please input:')
await asyncio.wait([user.send(message) for user in USERS])
async def register(websocket):
USERS.add(websocket)
await notify_users()
async def unregister(websocket):
USERS.remove(websocket)
await notify_users()
async def counter(websocket, path):
# register(websocket) sends user_event() to websocket
await register(websocket)
try:
# 处理客户端数据请求 (业务逻辑)
async for message in websocket:
print(message)
finally:
await unregister(websocket)
asyncio.get_event_loop().run_until_complete(
websockets.serve(counter, 'localhost', 6789))
asyncio.get_event_loop().run_forever()
# 原文链接:https://blog.csdn.net/qq_33961117/article/details/94442908
结合HTML代码
DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>websocket通信客户端title>
<script type="text/javascript">
var ws
function WebSocketTest() {
if ("WebSocket" in window) {
// 打开一个 web socket
ws = new WebSocket("ws://127.0.0.1:6789");
// 连接建立后的回调函数
ws.onopen = function () {
// Web Socket 已连接上,使用 send() 方法发送数据
//ws.send("admin:123456");
alert("连接成功");
document.getElementById("ok").innerHTML = "发送消息给服务器"
};
// 接收到服务器消息后的回调函数
ws.onmessage = function (evt) {
var received_msg = evt.data;
if (received_msg.indexOf("sorry") == -1) {
var node = document.createElement("LI");
var textnode = document.createTextNode(received_msg);
node.appendChild(textnode);
document.getElementById("myList").appendChild(node);
//alert("收到消息:" + received_msg);
}
};
// 连接关闭后的回调函数
ws.onclose = function () {
// 关闭 websocket
alert("连接已关闭...");
};
}
else {
// 浏览器不支持 WebSocket
alert("您的浏览器不支持 WebSocket!");
}
}
function sayHello() {
ws.send("你好server");
}
script>
head>
<button id="ok" onclick="sayHello()">尚未连接button>
<body onload="WebSocketTest()">
<ul id="myList">
<li>列表li>
ul>
body>
html>
嗯,也顺利可以跟多个客户端通信了
去看看官方文档websockets库官方文档
确实,调试成功,就是这个asyncio
库要再学习下
就在这卡了好久
第一次实验
import asyncio
import logging
import websockets
import redis
# 连接
rc = redis.StrictRedis(host='****', port='6379', db=3, password='******')
ps = rc.pubsub() # 订阅对象
ps.subscribe('liao') # 订阅
logging.basicConfig()
USERS = set()
async def notify_users():
# 对注册列表内的客户端进行推送
if USERS: # asyncio.wait doesn't accept an empty list
message = 'please input:'
await asyncio.wait([user.send(message) for user in USERS])
async def register(websocket):
USERS.add(websocket)
await notify_users()
async def unregister(websocket):
USERS.remove(websocket)
await notify_users()
async def counter(websocket, path):
print("一个")
await register(websocket)
print("注册完毕")
try:
for item in ps.listen(): # 阻塞监听
if item['type'] == 'message':
# print(item['channel'])
print(item['data'])
finally:
print("注销")
await unregister(websocket)
print("结束")
asyncio.get_event_loop().run_until_complete(websockets.serve(counter, 'localhost', 6789))
asyncio.get_event_loop().run_forever()
欸,可以了,不过第二个客户端咋连不进去了,一看原来是阻塞监听这死循环了,没有监听到第二个连接
害,再看看这个run_until_complete()
啥意思,就在这又研究了好久,发现怎么都还是绕不过这个死循环
后面又尝试了好多种方法,属于是乱试【急了,大家千万别学,磨刀不误砍柴工】
无奈搞不来,天色已晚,早点睡觉把
第二天起来清醒了一点,灵光一现
既然redis老是死循环,那我们能不能照葫芦画瓢,弄个异步的redis订阅?
又恶补了一下异步的知识,上网找答案
功夫不负有心人,找到个aioredis
异步redis库
咋实现异步订阅嘞?这次直接在Google上搜了一下
发现了老外的一篇文章
wok,这不就是我要的吗,赶紧copy一下试试
# producer.py
import asyncio
from aioredis import create_connection, Channel
import websockets
async def subscribe_to_redis(path):
conn = await create_connection(('localhost', 6379))
# Set up a subscribe channel
channel = Channel('lightlevel{}'.format(path), is_pattern=False)
await conn.execute_pubsub('subscribe', channel)
return channel, conn
async def browser_server(websocket, path):
channel, conn = await subscribe_to_redis(path)
try:
while True:
# Wait until data is published to this channel
message = await channel.get()
# Send unicode decoded data over to the websocket client
await websocket.send(message.decode('utf-8'))
except websockets.exceptions.ConnectionClosed:
# Free up channel if websocket goes down
await conn.execute_pubsub('unsubscribe', channel)
conn.close()
if __name__ == '__main__':
# Runs a server process on 8767. Just do 'python producer.py'
loop = asyncio.get_event_loop()
loop.set_debug(True)
ws_server = websockets.serve(browser_server, 'localhost', 8767)
loop.run_until_complete(ws_server)
loop.run_forever()
调试了一下,完美,多客户端也有了,redis订阅也有了。
按照异步逻辑await,每有一个ws客户端连接,python都会创建一个redis订阅连接,果不其然,
在redis命令行测试的时候发现确实如此,会不会占用过多资源,还有待考究
可以说是实现了
websocket连接 转换为 redis订阅连接
也大致满足了我的项目需求了
redis只需要订阅一次(一个订阅链接)就可以实现对多个客户端发送,以及客户端session的判别
异步IO又是一个大坑,不论是python的asyncio还是PHP的swoole,都还要学习,本文大概就到这里,记录一下研究过过程,有特别多的不足,目前项目需要跟进,有些内容暂时没法深入学习,还请见谅。