Django使用Websocket,Channel初体验

6 minute read

很久都没更新文章了,最近恰好在做了一个服务端主动推送消息给客户端的需求,因为是第一次使用Channels模块,踩了不少坑,在这里记录一下。

什么是Websocket

websocket的介绍 WebSocket-阮一峰老师 通俗的来说Websocket就是一个双向通道,客户端可以主动给服务端发消息,服务端也可以主动给客户端发送消息。

Channels是Django里面可以构建websocket的模块,官方文档里说了很清晰了,就是一个以处理WebSocket,聊天协议,IoT协议的模块,基于称为ASGI的Python规范构建。它可以以异步的方式进行。

安装配置

 1python2: pip install -U channels
 2python3: pip3 install -U channels
 3
 4# 添加到yourproject.settings
 5INSTALLED_APPS = (
 6    'django.contrib.auth',
 7    'django.contrib.contenttypes',
 8    'django.contrib.sessions',
 9    'django.contrib.sites',
10    ...
11    'channels',
12)
13
14# yourproject/routing.py
15from channels.routing import ProtocolTypeRouter
16
17application = ProtocolTypeRouter({
18    # Empty for now (http->django views is added by default)
19})
20
21# 修改settings文件,yourproject/settings,添加下面一行内容
22
23ASGI_APPLICATION = "yourproject.routing.application"

启用之后,通道就会将自己集成到Django中,并控制runserver命令

1# 启动之后命令行变成 ASGI/channels
2Starting ASGI/Channels version 2.4.0 development server at https://127.0.0.1:8000/

下面是在实际中编写的一个模块,主要用websocket连接通道,然后服务端主动推送消息.聊天室的例子大家可以在网上搜一下有很多,我在这里就不多做描写了。

  1# 目录结构
  2yourproject/
  3 - yourproject/
  4    - asgi.py
  5    - routing.py
  6    - wsgi.py
  7    - settings.py
  8    - urls.py
  9 - chat/
 10    - routing.py
 11    - consumers.py
 12    - controller.py
 13 - manage.py
 14
 15# youproject/routing.py
 16from channels.routing import ProtocolTypeRouter, URLRouter
 17from django.conf.urls import url
 18from chat import routing
 19application = ProtocolTypeRouter({
 20    # 没有做权限校验
 21    "websocket": URLRouter(
 22      chat.routing.websocket_urlpatterns
 23    )
 24})
 25
 26# chat/routing.py
 27from django.conf.urls import re_path
 28from monitor.consumers import AsyncConsumer
 29
 30websocket_urlpatterns = [
 31    # api router setting
 32    re_path(r'ws/chat/', AsyncConsumer)
 33]
 34
 35# 官方文档一般建议使用AsyncWebsocketConsumer
 36# 原文默认情况下编写SyncConsumers,并且仅在以下情况下使用AsyncConsumers:通过异步处理来改善的事情
 37# 如果想要在其他模块主动推送消息给客户端,必须使用通道,通道分为Redis和内存通道(生产中不建议使用)
 38# 配置通道层
 39# yourproject/settings.py
 40CHANNEL_LAYERS = {
 41    "default": {
 42        "BACKEND": "channels_redis.core.RedisChannelLayer",
 43        "CONFIG": {
 44            "hosts": [("127.0.0.1", 6379)],
 45        },
 46    },
 47}
 48# chat/consumers.py
 49from channels.generic.websocket import AsyncWebsocketConsumer
 50from monitor import CHANNEL_NAME
 51from redis_cache import REDIS_CONN
 52import json
 53
 54class AsyncConsumer(AsyncWebsocketConsumer):
 55    async def connect(self):
 56        """
 57        连接时触发 并且分配一个self.channel_name名称可以存数据库
 58        这里我存入redis 主要方便
 59        """
 60        REDIS_CONN.lpush(CHANNEL_NAME, self.channel_name)
 61        await self.accept()
 62
 63    # Receive message from WebSocket
 64    async def receive(self, text_data=None, bytes_data=None):
 65
 66        # text_data_json = json.loads(text_data)
 67        # message = text_data_json['message']
 68        # 接受信息并回复
 69        await self.send(text_data = json.dumps(text_data))
 70
 71    async def disconnect(self, close_code):
 72        # 将关闭的连接从群组中移除
 73        results = REDIS_CONN.lrange(CHANNEL_NAME, 0, REDIS_CONN.llen(CHANNEL_NAME))
 74        for index, item in enumerate(results):
 75            if self.channel_name == item:
 76                REDIS_CONN.lrem(CHANNEL_NAME, index, item)
 77        await self.close()
 78
 79    # 主动推送的事件处理方法
 80    async def push_message(self, event):
 81        data = json.dumps(event["text"])
 82        await self.send(text_data = data)
 83
 84# chat/controller.py
 85from channels.layers import get_channel_layer
 86from asgiref.sync import async_to_sync
 87from monitor import CHANNEL_NAME
 88from redis_cache import REDIS_CONN
 89# 官方例子,异步推送消息
 90channel_layer = get_channel_layer()
 91await channel_layer.send("channel_name", {
 92    "type": "chat.message",
 93    "text": "Hello there!",
 94})
 95# 同步推送消息,调用函数即可推送
 96def push_chat_message(data):
 97    """
 98    从redis拿到已经连接的通道名称
 99    """
100    channel_layer = get_channel_layer()
101    # 循环redis中的channel_name,并且推送消息
102    results = REDIS_CONN.lrange(CHANNEL_NAME, 0, REDIS_CONN.llen(CHANNEL_NAME))
103    for channel in results:
104        async_to_sync(channel_layer.send)(channel,
105            {
106                # consumer中的事件处理方法,建议为事件类型加上前缀如`push.`避免冲突.
107                "type": "push.message",
108                "text": data,
109            }
110        )

以上就是单通道推送消息的例子,第一次使用也有很多地方不清楚,这块只是做个记录,根据网上看到的一些资料,再总结一下。如果想要了解聊天室的例子,大家可以去网上搜一下,有很多,官方文档也有chan_examplte.还望大佬们轻拍.