Учебник, часть 3. Переписываем сервер чата как асинхронный

Этот урок продолжает урок 2. Мы перепишем потребительский код как асинхронный, а не синхронный, чтобы улучшить его производительность.

Переписываем потребителя, чтобы он был асинхронным

ChatConsumer, который мы написали, в настоящее время является синхронным. Синхронные потребители удобны тем, что могут вызывать обычные функции синхронного ввода-вывода, например те, которые обращаются к моделям Django без написания специального кода. Однако асинхронные потребители могут обеспечить более высокий уровень производительности, поскольку им не нужно создавать дополнительные потоки при обработке запросов.

ChatConsumer использует только асинхронные библиотеки (Channels и канальный уровень) и, в частности, не имеет доступа к синхронным моделям Django. Поэтому его можно переписать как асинхронный без осложнений.

Примечание

Даже если ChatConsumer имел доступ к моделям Django или другому синхронному коду, все равно можно переписать его как асинхронный. Такие утилиты, как asgiref.sync.sync_to_async и channel.db.database_sync_to_async, могут использоваться для вызова синхронного кода от асинхронного потребителя. Однако прирост производительности будет меньше, чем если бы он использовал только асинхронные библиотеки.

Давайте перепишем ChatConsumer в асинхронном стиле. Поместите следующий код в chat/consumer.py:

# chat/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        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):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        # 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(text_data=json.dumps({
            'message': message
        }))

Этот новый код для ChatConsumer очень похож на оригинальный код, со следующими отличиями:

  • ChatConsumer теперь наследуется от AsyncWebsocketConsumer, а не от WebsocketConsumer.

  • Все методы являются async def, а не просто def.

  • await используется для вызова асинхронных функций, которые выполняют ввод/вывод.

  • async_to_sync больше не требуется при вызове методов на канальном уровне.

Давайте проверим, что потребитель для пути /ws/chat/ROOM_NAME/ по-прежнему работает. Чтобы запустить сервер разработки Channels, выполните следующую команду:

$ python3 manage.py runserver

Откройте вкладку браузера на странице комнаты по адресу http://127.0.0.1:8000/chat/lobby/. Откройте вторую вкладку браузера на той же странице комнаты.

Во второй вкладке браузера введите сообщение «hello» и нажмите клавишу ввода. Теперь вы должны увидеть «hello», отраженный в журнале чата как на второй вкладке браузера, так и на первой вкладке браузера.

Теперь ваш чат-сервер полностью асинхронный!

Этот учебник продолжается в 4ой части.

Вернуться на верх