Django WebSocket 系列(二):使用 Django Channels 搭建 WebSocket 服务

1、Channels

Django本身不支持WebSocket,但可以通过集成Channels框架来实现WebSocket

Channels是针对Django项目的一个增强框架,可以使Django不仅支持HTTP协议,还能支持WebSocketMQTT等多种协议,同时Channels还整合了Djangoauth以及session系统方便进行用户管理及认证。

2、Channels实现websocket的方案

  1. channels运行于ASGI协议上,所以需要ASGI服务器运行django,如daphne
  2. 定义websocket路由
  3. 创建webscoket consumer ,在这里实现创建、断开连接,发送、接收消息等。

3、步骤

3.1 依赖

  1. channels:3.0.5 , 要求你的redis版本大于等于5.0
  2. daphne:3.0.2,Daphne是一个用于运行Django Channels应用程序的异步Web服务器。
  3. channels-redis:4.2.0
  4. django:4.1.6
  5. python:3.11

3.2 配置settings

  1. ASGI配置:指定ASGI的路由地址,即asgi.py

    # ASGI 配置 ASGI_APPLICATION = ('xx.asgi.application')

  2. INSTALLED_APPS添加channels

  3. django启动时,在INSTALLED_APPS检测到channels,它就会要求使用ASGI服务器运行。

    # channels 配置 INSTALLED_APPS = [ 'daphne', …… 'channels', …… ]

  4. CHANNEL_LAYERS配置:

    CHANNEL_LAYERS = { 'default': { 'BACKEND': 'channels_redis.core.RedisChannelLayer', 'CONFIG': { 'hosts': [('127.0.0.1', 6379)], }, }, }

3.3 修改asgi.py

配置 ASGI应用程序的路由和协议处理器,分别配置http协议的路由和websocekt协议的路由。

import os

from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from xxx.websocket.routing import websocket_urlpatterns

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'system.settings')

application = ProtocolTypeRouter(
    {
        'http': get_asgi_application(),
        'websocket': AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
    }
)
  1. ProtocolTypeRouter:一个用于根据协议类型路由请求的路由器。在这个示例中,它会根据请求的协议类型来选择相应的处理程序。
  2. get_asgi_application()get_asgi_application()Django 的函数,它返回一个 ASGI应用程序,用于处理HTTP请求。通常情况下,这个处理程序就是你的 Django 应用程序,它会处理传统的 HTTP 请求。
  3. AuthMiddlewareStack(URLRouter(websocket_urlpatterns)):这里指定了WebSocket 协议的处理程序。
    • AuthMiddlewareStack 是一个中间件,用于处理 WebSocket 连接的认证。
    • WebSocket 连接建立时,AuthMiddlewareStack 会检查用户是否已经通过身份验证。如果用户已经通过身份验证,那么 WebSocket 连接就会被允许建立;否则,连接会被拒绝。
    • `URLRouter 是一个路由器,用于根据 WebSocket 请求的 URL 路由到相应的处理程序。
    • websocket_urlpatterns 是一个列表,包含了定义WebSocket路由和处理程序的URL模式。

3.4 创建websocket消费者

app下创建websocket文件夹,在该文件夹下创建consumers.py

一个简单的消费者的例子:

import json

from channels.generic.websocket import AsyncWebsocketConsumer


class TestConsumer(AsyncWebsocketConsumer):

    async def connect(self):
        await self.accept()

    async def disconnect(self, code):
        pass

    async def receive(self, text_data=None, bytes_data=None):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]
        # 设置ensure_ascii以支持中文
        await self.send(text_data=json.dumps({"message": message}, ensure_ascii=False))

    async def send_message(self, event):
        message = event['message']
        await self.send(text_data=json.dumps(message))

这就是一个消费者的模板,首先要继承AsyncWebsocketConsumer类,然后可以重写connectdisconnectreceive方法来满足我们的需要。

  • connect:当 WebSocket 连接建立时会调用该方法
  • disconnect:当 WebSocket 连接关闭时会调用该方法。 注意前端正常断开连接时会正确调用该方法;如果因为后端挂了而导致连接断开的话,不会调用该方法。
  • receive:连接建立后收到消息时会调用方法

  • send_message有些不同,它是我自己写的一个方法,最终调用的是父类的send方法来推送消息。

3.4 创建websocket路由

consumers.py同级下创建routing.py

from django.urls import path
from .consumers import TestConsumer

websocket_urlpatterns = [
    path('ws/test/', TestConsumer.as_asgi()),
]

可以把websocket就看做django的路由和视图

现在前端可以连接这个路由,建立websocket连接了。

  • ws://127.0.0.1:8000/ws/test/:注意websocket是以ws开头。

3.5 路由参数可选

路由参数可选为了复用消费者,没必要创建多个消费者。

这种情况下需要创建两个或者多个路由,来分别匹配不同数量的参数。

from django.urls import path
from function.socket import consumers

websocket_urlpatterns = [
    path('ws/datasend/<str:id>/', consumers.TestConsumer.as_asgi()),
    path('ws/datasend/<str:id>/<str:name>/', consumers.TestConsumer.as_asgi())
]

接下来就可以在TestConsumer这个消费者里做判断了

async def connect(self):
    self.id = self.scope['url_route']['kwargs'].get('id', None)
    self.name = self.scope['url_route']['kwargs'].get('name', None)
    if self.name:
        # name传递时的逻辑
    else:
        # name不传递时的逻辑
    await self.accept()

4 self.send 方法详解

self.send 方法用于将数据发送到与当前 WebSocket 连接关联的客户端。该方法是异步的,需要使用 await 关键字调用,以确保不会阻塞事件循环。

await self.send(text_data=None, bytes_data=None, close=False)
  • text_data:要发送的文本数据。必须是字符串类型。如果设置了此参数,则不能设置 bytes_data
  • bytes_data:要发送的二进制数据。必须是字节类型。如果设置了此参数,则不能设置 text_data
  • close:如果为 True,表示在发送完消息后关闭 WebSocket 连接。

5、channels-redis

channels-redisDjango Channels 框架的一个插件,用于在基于 Django Channels 的应用程序中实现分布式消息传递和状态保持。它提供了一个基于 Redis 的通道层,用于在多个异步进程之间传递消息和维护连接状态。

在使用 Django Channels构建实时应用程序时,通常需要处理多个进程或服务器之间的通信和状态同步。channels-redis 正是为了解决这个问题而设计的。它通过 Redis 数据库作为后端存储来实现通信和状态同步,Redis提供了高性能和可靠的分布式数据存储,适合用于构建实时应用程序。

channels-redis 的主要功能包括:

  1. 分布式消息传递:允许在多个异步进程或服务器之间传递消息,例如 WebSocket 消息、后台任务消息等。
  2. 状态保持:维护连接状态和客户端会话信息,使得在分布式环境中实现实时应用程序变得更加容易。
  3. 后台任务队列:允许在多个工作者之间分配和执行后台任务,例如发送电子邮件、处理图像等。

通过使用 channels-redis,开发者可以轻松地构建具有高可扩展性和高可靠性的实时应用程序,而无需担心多进程之间的通信和状态同步问题。

6、参考

  1. Django使用Channels实现WebSocket--上篇
  2. django3实现Websocket最简单demo
  3. Django websocket之web端实时查看日志实践案例

发表评论

评论列表,共 0 条评论

    暂无评论