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,是为了后续部署用于启动ASGI1服务。当然,若是你有其他的部署方式你可以仅安装channels。

配置

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

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

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,代码示例:

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 文件中添加配置

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

接口代码实现

# 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 实现连接,注意浏览器兼容问题即可。

// 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 安装

websocat ws://localhost:8000/chat/room/
# 加header
websocat -H="Authorization: Token xxxx" ws://localhost:8000/chat/room/

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


  1. 可以关注下Django框架中提供的WSGI和ASGI的区别; ↩︎

  • 29
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值