启用Channels Layer
Layer是一种通信系统。它允许多个消费者实例相互交谈,以及与 Django 的其他部分交谈。借助Layer可以很方便的实现群聊功能。无需我们手动管理websocket连接。
配置channel layer
在settings.py文件中,加入下面的配置,即可在内存中由channels自动维护
代码语言:javascript复制CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer",
}
}
这个基于内存的通道层是不能在生产中使用的,因为内存通道层作为一个单独的层在每个进程中运行。这意味着不可能进行跨进程消息传递。在实际生产中,需要使用Redis来作为通道层。(所以,在Django中目前提供websocket支持确实非常麻烦,你自己基于Django3的ASGI实现websocket也很麻烦。)
安装channels_redis
代码语言:javascript复制pip3 install channels_redis
配置Redis Layer
代码语言:javascript复制CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
修改consumers.py的内容。
代码语言:javascript复制from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_name = self.scope['url_route']['kwargs']['group']
self.room_group_name = 'chat_%s' % 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):
message = text_data
# 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(message)
此处这段代码,是对官方的文档做了一个小小的修改,这里需要对一些东西解释一下。
- 首先是
async_to_sync
,这个包装器是将异步操作转为同步操作。这是因为channels layer的所有方法都是异步的。 由此可知,channels应该也是基于事件循环机制的。如果想在Python中使用异步,那么就需要将方法变成协程函数。此处我们仍旧使用了普通函数,因此需要async_to_sync
来将异步操作转换为同步操作。 scope
,这个东西是asgi规范规定的,scope具体的内容可以看scope,这里的url_route是channels自己添加的部分,URLRouter会将捕获的组从URL放入scope[“url_route”]中的kwargs,然后我们根据re_path()中的分组名来获取值。self.room_group_name
是channels layer的组名,我们是根据url参数来直接构造了一个组名。组名只能包含字母、数字、_和句点(.)。self.channel_layer.group_add
有两个参数,分别是组名和当前websocket连接的名称,作用是将当前的连接加入到名为xxx的组中。self.channel_name
是随机生成的,我们可以直接使用。self.channel_layer.group_discard
,的作用是将self.channel_name
从组self.room_group_name
中删除并断开连接。channel_layer.group_send
作用是像向组xx发送事件,这里是向self.room_group_name组发送事件。事件有一个特殊的键type,对应于在接收事件的消费者上调用的方法的名称。本例中,就是chat_message这个方法,你需要在chat_message方法中调用self.send()方法来发送。
异步实现
代码语言:javascript复制from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['group']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
message = text_data
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
# Send message to WebSocket
await self.send(message)
需要注意,异步需要继承自AsyncWebsocketConsumer类,然后所有的IO操作都是异步IO,类中的方法都是协程。但是需要注意,Django的模型和
参考文档:Channels