又遇到服务端向浏览器发消息的需求,之前用过channels+websocket,用过SSE(Server-Sent Events),但是怎么部署的一个都想不起来了。就记得
websocket复杂一些,但是用起来稳定,sse呢,部署很简单,不知道我的代码问题还是啥,他总死掉。
这次还继续 websocket吧。
正常视图是启用的 wsgi(Web Server Gateway Interface) 服务;
websocket 需要 asgi (Asynchronous Server Gateway Interface)
这个服务需要用 Daphne 承载
所以环境需要 channels,Daphne,最好再加个异步 Celery
需不需要redis看个人喜好吧
从之前的项目摘来相关代码
asgi.py 文件
import os
import django
# from django.core.asgi import get_asgi_application
from channels.routing import get_default_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'XXX.settings')
django.setup()
# application = get_asgi_application()
application = get_default_application()
settings.py 相关部分
# Channels setting
ASGI_APPLICATION = 'XXX.asgi.application'
CHANNEL_LAYERS = {
'default' : {
'BACKEND' : 'channels_redis.core.RedisChannelLayer',
'CONFIG' : {
'hosts' : [('127.0.0.1', 6379)],
},
},
}
或者
# settings.py
ASGI_APPLICATION = 'XXX.asgi.application'
# 配置CHANNEL_LAYERS使用channels_python作为backend
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels.layers.InMemoryChannelLayer',
},
}
routing.py的修改
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import app04.routing
application = ProtocolTypeRouter({
# (http->django views is added by default)
'websocket': AuthMiddlewareStack(
URLRouter(
app04.routing.websocket_urlpatterns
)
),
})
app下的 routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'^ws/(?P<room_id>\w+)/$', consumers.Consumer)
]
然后 app 中添加一 consumers.py
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
import logging
import json
logger = logging.getLogger("default")
class Consumer(AsyncWebsocketConsumer):
async def connect(self):
# 从打开到使用者的WebSocket连接的chat/routing.py中的URL路由中获取'room_name'参数。
self.room_name = self.scope['url_route']['kwargs']['room_id']
# 直接从用户指定的房间构造Channels组名称
self.room_group_name = 'ws_{}'.format(self.room_name)
# 加入房间
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
logger.info(f"{self.scope['client']} connect the {self.room_group_name} WebSocket room.")
# 接受websocket连接 如果不在connect()方法中调用accept(),则拒绝并关闭
await self.accept()
async def disconnect(self, close_code):
# 离开房间
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
logger.info(f"{self.scope['client']} disconnect the {self.room_group_name} WebSocket room.")
async def receive(self, text_data):
""" 从websocket接收消息 发送消息到房间 """
# 发送消息到房间
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'group_message',
'message': text_data
}
)
async def group_message(self, event):
""" 从房间接收消息 """
message = event['message']
# 发消息到websocket
await self.send(text_data=json.dumps({
'message': message
}))
def manual_send(text_data, room_name='log'):
""" 手动往房间里发消息, 用于调用 """
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
'ws_{}'.format(room_name),
{
"type": "group_message",
"message": text_data,
}
)
前端 js 部分
function initWebSocekt(ths) {
var roomName = dataRet.roomID;
// const ws = new WebSocket('ws://' + location.host + '/ws/' + roomName + '/');
const ws = new WebSocket('ws://192.168.20.14:8001/ws/' + roomName + '/');
ws.onopen = function open() {
console.log('WebSocket connection created.');
ws.send('ws init finished.');
setInterval(function () {
ws.send('ws is alive.');
}, 20 * 60 * 1000);
};
ws.onmessage = function (e) {
var data = JSON.parse(e.data);
var message = data['message'];
console.log(message);
ths.message += message;
if(message != 'ws init finished.' && message != 'ws is alive.'){
if(message.indexOf('log') != -1){
location.reload();
};
};
};
ws.onclose = function (e) {
console.error('WebSocket closed unexpectedly.');
// 尝试重新连接
// initWebSocekt(ths);
location.reload();
};
ws.readyState == WebSocket.OPEN ? ws.onopen() : '';
};
views 中的调用
import consumers
consumers.manual_send('log database: change')
启动 daphne
cd XXX
python daphne -b 192.168.20.14 -p 8001 XXX.asgi:application
不出意外,报错!!!
上面是常见的几个报错
最终发现是 channels版本问题,降到低版本可以链接成功了,但是手动发消息还有问题。
事件经历
这次代码不能跑主要问题是 channels 版本从 2.4 升级到 4.0了。摸索出需要有以下改动。
把daphne 放到 installed_apps 中 第一个就不用启动 daphne服务了。
routing 的 urlpatterns 中 类后面放 .as_asgi()
然后手动发消息的这个函数改了很多版,最后又改回来了,居然能用