Django websocket отправка сообщений async в цикле

Я пытаюсь создать канал websocket, используя класс AsyncJsonWebsocketConsumer. Я хочу отправлять сообщения в цикле через каждые 5 секунд, и мой потребитель websocket-канала (ReactJS App Client) также должен получать сообщения через каждые 5 секунд. Однако мое приложение-потребитель получает все сообщения сразу после отправки последнего сообщения или после завершения функции receive_json. Я не могу решить эту проблему. Нужна помощь. Спасибо

test.py

from channels.generic.websocket import AsyncJsonWebsocketConsumer



class TestController(AsyncJsonWebsocketConsumer):

    async def connect(self):
        await self.accept()


    async def receive_json(self, content: dict):

        for i in range(0, 5):

            await self.send_json({
                "text": f"Loop - {i + 1}"
            })

            sleep(5)

        await self.send_json({
            "text": {
                "type": "finished"
            }
        })


    async def disconnect(self, close_code):
        print()
        print("DISCONNECTED")
        print(close_code)
        print()

routing.py

from django.urls import path

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator

from app.test import TestController



channel_routing = ProtocolTypeRouter({
    "websocket": AllowedHostsOriginValidator(
        AuthMiddlewareStack(
            URLRouter([
                path("websocket/test", TestController.as_asgi())
            ])
        )
    )
})

Этот код очень запутанный. Что вы пытаетесь сделать?

Ваш async def receive_json(self, content: dict): должен вызываться каждый раз при получении сообщения.

Итак, на момент написания статьи, если исправить ошибку, о которой упомянул @Igonato, заменив sleep(5) на await asyncio.sleep(5), ваш код будет отправлять 6 ws-сообщений в течение примерно 25 секунд в ответ на каждое входящее ws-сообщение.

Мне нравится думать о коде websocket, работающем на нашем сервере, - о том, что запускается в consumers.py, - как о реактивном коде. Другими словами, код, который реагирует на команды, отправленные ему через то же или другое соединение websocket. Это также поможет сэкономить ресурсы сервера; вместо того чтобы запускать несколько циклов или подсчитывать время для нескольких пользователей, мы позволим пользователю подсчитать время и отправить наш ответ обратно.

Рассмотрим "TestController" или демон, который отвечает на отправленную ему команду, показывая пользователю нужное сообщение. На клиенте запущен цикл javascript или reactJS, который отправляет сообщение каждые 5 секунд. Я вижу, что вы используете счетчик в диапазоне цикла for, вы могли бы сделать то же самое на javascript, отслеживая счет в цикле, а затем отправляя его демону в виде переменной в строке. Мы будем использовать первый символ нашей строки для обозначения команды, а второй символ строки - для обозначения переменной функции этой команды.

Рассмотрите следующий код, чтобы увидеть, как можно настроить функцию receive, чтобы она реагировала на различные команды. Когда мы отправляем данные с внешнего клиента на наш websocket, настройте content как словарь с каждой строкой или символом, которые вам нужны.

 var pingNum = 1;
 var pingID = 1;
 function newFunction() {
            socket.send(JSON.stringify({
                "command": "ping",
                "pingID" : pingID,
                "pingNum": pingNum
            }));
            pingNum += 1;
 }
 setInterval(newFunction, 5000);

Далее мы получаем этот json в нашей функции receive_json и ищем каждый ключ в JSON, чтобы увидеть, что он говорит, чтобы направить нашу программу вперед и ответить клиенту.

class TestController(AsyncJsonWebsocketConsumer):

##### WebSocket event handlers

async def connect(self):
    """
    Called when the websocket is handshaking as part of initial connection.
    """
    # Accept the connection
    await self.accept()

async def receive_json(self, content):
    """
    Called when we get a text frame. Channels will JSON-decode the payload
    for us and pass it as the first argument.
    """
    # Messages will have a "command" key we can switch on, otherwise its a ready check
    command = content.get("command", None)
    pingNum = content.get("pingNum", None)

    if command == "ping":
        if pingNum <= 5:
            await self.send_json({
                "text": "Loop - " + str(pingNum)
            })
        else:
            await self.send_json({
                "text": {
                "type": "finished"
                }
            })

Пожалуйста, используйте await asyncio.sleep(5)

import asyncio
from channels.generic.websocket import AsyncJsonWebsocketConsumer


class TestController(AsyncJsonWebsocketConsumer):

    async def connect(self):
        await self.accept()

    async def receive_json(self, content: dict):
        for i in range(0, 5):
            await self.send_json({
                "text": f"Loop - {i + 1}"
            })
            await asyncio.sleep(5)

        await self.send_json({
            "text": {
                "type": "finished"
            }
        })

    async def disconnect(self, close_code):
        print()
        print("DISCONNECTED")
        print(close_code)
        print()

Это потому, что вы используете блокирующую функцию сна. Вместо этого следует использовать асинхронную функцию asyncio.sleep, которая не блокирует цикл событий.

Э.г.

import asyncio
from channels.generic.websocket import AsyncJsonWebsocketConsumer

class TestController(AsyncJsonWebsocketConsumer):
    async def connect(self):
        await self.accept()

    async def receive_json(self, content: dict):
        for i in range(5):
            await self.send_json({"text": f"Loop - {i + 1}"})
            await asyncio.sleep(5)

        await self.send_json({"text": {"type": "finished"}})

    async def disconnect(self, close_code):
        print("DISCONNECTED")
        print(close_code)

Это должно работать идеально.

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