很久都没更新文章了,最近恰好在做了一个服务端主动推送消息给客户端的需求,因为是第一次使用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.还望大佬们轻拍.