django-channels实现群聊

2021-12-08 16:10:52 浏览数 (1)

启用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

0 人点赞