Django实现websocket

Django实现websocket

WebSocket功能

WebSocket长连接一般用于实现实时功能,例如web端的消息通知、会话等场景。

使用 WebSocket 向 Django 项目添加实时功能,一般有两个选择:

  1. 使用 Django Channels,这是一个向 Django 添加 WebSocket 等功能的项目。 Django 完全支持这种方法。 但是,它需要切换到新的部署架构;

  2. 在 Django 项目旁边部署一个单独的 WebSocket 服务。

方法2的实现一般可以借助 django-sesame 去管理会话的鉴权,本文不做过多阐述,可以点击参考链接,下面组要介绍方法1中Channels在Django中的使用。

关于Channels:

使用介绍

安装

pip install -U 'channels[daphne]'

需要使用daphne,是为了后续部署用于启动ASGI[1](#1)服务。当然,若是你有其他的部署方式你可以仅安装channels。

配置

安装完后,需要在INSTALLED_APPS配置中添加:

python 复制代码
INSTALLED_APPS = (
    "daphne",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.sites",
    ...
)

ASGI_APPLICATION = "mysite.asgi.application"

注意:ASGI_APPLICATION 配置后正常可通过python manage.py runserver启动服务,低版本Django不行,Django 5.x 版本可行。

添加文件 myproject/asgi.py

python 复制代码
from django.core.asgi import get_asgi_application
from django.urls import re_path

# Initialize Django ASGI application early to ensure the AppRegistry
# is populated before importing code that may import ORM models.
django_asgi_app = get_asgi_application()

from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidator

application = ProtocolTypeRouter({
	# 同时能支持原有http协议的接口
    "http": django_asgi_app,
    # 增加websocket协议的支持
    "websocket": AllowedHostsOriginValidator(
        AuthMiddlewareStack(
        	# 接口路由
            URLRouter([
                re_path(r"^chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi()),
            ])
        )
    ),
})

WebSocket接口--消息接收者的实现

接口实现可以继承WebsocketConsumer / AsyncConsumer / AsyncConsumer,此处介绍WebsocketConsumer,代码示例:

python 复制代码
class ChatConsumer(WebsocketConsumer):

    def connect(self):
    	# django框架会自动处理websocket链接带上的headers信息进行鉴权认证,初始化成用户对象
    	self.user = self.scope['user']
        self.accept()
        self.send(text_data="[Welcome %s!]" % self.user.username)

    def receive(self, *, text_data):
        if text_data.startswith("/name"):
            username = text_data[5:].strip()
            self.send(text_data="[set your username to %s]" % username)
        else:
            self.send(text_data=username + ": " + text_data)

    def disconnect(self, message):
        pass

值的一提的是,Django框架和Channels结合很好,Auth体系完全兼容,Django http中的header这里也能识别成User用户。

scope

Consumer接受到client端发来的websocket请求后,会将请求信息初始化成scope,请求相关信息可以从self.scope中获取,例如:

  • scope["client"] client来源ip和port, eg: ['127.0.0.1', 6533]
  • scope["user"] 根据headers等信息初始化的User用户
  • scope["path"] 请求接口path路径
  • scope["query_string"] 连接上的query参数
  • scope["headers"] 请求headers
  • scope["url_route"] 请求path定义路由解析出的参数,eg: {"args": {}, "kwargs": {"pk": 1}}

以上的介绍算是基本实现了一个简单的websoket服务。

若是实现较为复杂的群聊、或者广播通知该如何实现?下面 channel layer

通道层 channel layer

安装pip install channels_redis

通道层是一种通信系统。 它允许多个消费者实例相互通信以及与 Django 的其他部分通信。

通道层提供以下抽象:

  • 通道是一个可以发送消息的邮箱。 每个频道都有一个名称。 知道频道名称的任何人都可以向该频道发送消息。
  • 组是一组相关的通道。 一个团体有一个名字。 任何拥有群组名称的人都可以通过名称向群组添加/删除频道,并向群组中的所有频道发送消息。 不可能枚举特定组中有哪些频道。

每个消费者实例都有一个自动生成的唯一通道名称,因此可以通过通道层进行通信。

需要在Django settings.py 文件中添加配置

python 复制代码
# Channels
ASGI_APPLICATION = "mysite.asgi.application"
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("127.0.0.1", 6379)],
        },
    },
}

接口代码实现

python 复制代码
# chat/consumers.py
import json

from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer


class ChatConsumer(WebsocketConsumer):
    def connect(self):
    	# 这里是将群名room_name定义在了path中,通过正则提取
        self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
        self.room_group_name = f"chat_{self.room_name}"

        # Join room group
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name, self.channel_name
        )

        self.accept()

    def disconnect(self, close_code):
        # Leave room group
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name, self.channel_name
        )

    # Receive message from WebSocket
    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]

        # Send message to room group
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name, {"type": "chat.message", "message": message}
        )

    # Receive message from room group
    def chat_message(self, event):
        message = event["message"]

        # Send message to WebSocket
        self.send(text_data=json.dumps({"message": message}))

部署

服务启动 daphne -b 0.0.0.0 -p 8000 myproject.asgi:application

其他参数可以查看 daphne文档

websocket协议nginx代理示例配置如下:

复制代码
upstream channels-backend {
    server localhost:8000;
}
...
server {
    ...
    location / {
        try_files $uri @proxy_to_app;
    }
    ...
    location @proxy_to_app {
        proxy_pass http://channels-backend;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host $server_name;
    }
    ...
}

Web客户端连接

前端JS可以通过 WebSocket 实现连接,注意浏览器兼容问题即可。

javascript 复制代码
// Create WebSocket connection.
const socket = new WebSocket("ws://localhost:8000/chat/room/");

// Connection opened
socket.addEventListener("open", function (event) {
  socket.send("Hello Server!");
});

// Listen for messages
socket.addEventListener("message", function (event) {
  console.log("Message from server ", event.data);
});

Mac客户端

借助工具 websocat , 可通过 brew install websocat 安装

shell 复制代码
websocat ws://localhost:8000/chat/room/
# 加header
websocat -H="Authorization: Token xxxx" ws://localhost:8000/chat/room/

需要注意的是,-H=""中间等号不能用空格替代,否则无法生效,希望作者在之后的版本中个可以兼容这个问题。


  1. 可以关注下Django框架中提供的WSGI和ASGI的区别; ↩︎
相关推荐
Yu_Lijing2 小时前
【个人项目】C++基于websocket的多用户网页五子棋(下)
网络·websocket·网络协议
两个人的幸福online12 小时前
cocos 使用 WebSocket(goEasy版)
网络·websocket·网络协议
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ17 小时前
aspect实现请求校验,但是WebSocket 端点类不能被 AOP 代理解决方案
网络·websocket·网络协议
倔强青铜三1 天前
Django 6.0来袭!这些新特性,真的令人振奋!
人工智能·python·django
闲人编程1 天前
后台任务与WebSocket实时应用
websocket·web·实时·codecapsule·后台协议·实时应用
教练、我想打篮球1 天前
118 http 协议中如何实现流式的交互数据
websocket·http·流式数据交互
Java水解2 天前
Django实现接口token检测的实现方案
后端·django
飞Link2 天前
【Django】Django 调用外部 Python 程序的完整指南
后端·python·django·sqlite
码界奇点2 天前
基于Django与Vue.js的RBAC权限管理系统设计与实现
vue.js·python·车载系统·django·毕业设计·源代码管理
2501_921649492 天前
亚太股票数据API:日股、韩股、新加坡股票、印尼股票市场实时行情,实时数据API-python
开发语言·后端·python·websocket·金融