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)
Это должно работать идеально.