Django + Websocket

又遇到服务端向浏览器发消息的需求,之前用过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()
在这里插入图片描述
然后手动发消息的这个函数改了很多版,最后又改回来了,居然能用

在这里插入图片描述

Django中使用WebSocket实现系统消息通知可以通过以下步骤实现: 1. 安装Django Channels和asgiref ```bash pip install channels asgiref ``` 2. 创建一个Django应用程序 ```bash python manage.py startapp notifications ``` 3. 创建一个WebSocket路由 在`notifications`应用程序中创建一个`routing.py`文件,添加以下内容: ```python from django.urls import re_path from . import consumers websocket_urlpatterns = [ re_path(r'ws/notifications/$', consumers.NotificationConsumer.as_asgi()), ] ``` 4. 创建一个WebSocket消费者 在`notifications`应用程序中创建一个`consumers.py`文件,添加以下内容: ```python import asyncio import json from channels.consumer import AsyncConsumer from channels.db import database_sync_to_async from django.contrib.auth.models import User class NotificationConsumer(AsyncConsumer): async def websocket_connect(self, event): await self.send({ "type": "websocket.accept" }) user = self.scope["user"] if user.is_authenticated: await self.channel_layer.group_add( f"user_{user.id}", self.channel_name ) async def websocket_receive(self, event): user = self.scope["user"] if user.is_authenticated: data = json.loads(event["text"]) message = data["message"] await self.create_message(user, message) await self.channel_layer.group_send( f"user_{user.id}", { "type": "user.message", "message": message } ) async def websocket_disconnect(self, event): user = self.scope["user"] if user.is_authenticated: await self.channel_layer.group_discard( f"user_{user.id}", self.channel_name ) @database_sync_to_async def create_message(self, user, message): user = User.objects.get(id=user.id) user.notifications.create(message=message) ``` 5. 配置WebSocket路由 在`settings.py`文件中添加以下内容: ```python ASGI_APPLICATION = 'project_name.routing.application' CHANNEL_LAYERS = { "default": { "BACKEND": "channels.layers.InMemoryChannelLayer" } } ROOT_URLCONF = 'project_name.urls' INSTALLED_APPS = [ ... 'channels', 'notifications', ] ``` 6. 创建一个JavaScript文件 在`static/js/notifications.js`文件中添加以下内容: ```javascript var socket = new WebSocket("ws://" + window.location.host + "/ws/notifications/"); socket.onmessage = function(event) { var message = JSON.parse(event.data)["message"]; alert(message); } ``` 7. 在模板中引入JavaScript文件 在需要使用WebSocket的模板中添加以下内容: ```html {% load static %} <script src="{% static 'js/notifications.js' %}"></script> ``` 现在,当用户登录并连接到WebSocket时,他们将加入名为`user_<user_id>`的组。当用户收到新消息时,消息将保存到数据库中,并通过WebSocket发送到所有连接到该组的用户。在前端,我们使用JavaScript来处理接收到的消息,这里简单地使用了一个警报框来显示消息,你可以改为使用其他的UI库。 希望这个教程能够帮助你实现DjangoWebSocket的消息通知功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小鹅卵石

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值