Django Channels 消费者消费 1 次调用两次
Django Channels consumer consuming 1 call twice
我正在使用 DRF 3.11.0 和 Channels 2.4.0 的组合来实现后端,它托管在 Heroku on 1 dyno 上并附加了 Redis 资源。我的 React 前端有一个套接字,它从后端服务器成功 sends/received。
我遇到一个问题,即通过套接字发送回前端的任何消息都被发送了两次。我已经通过 console.log
确认前端只对后端执行一次 ping 操作。我可以通过 API 调用内部的 print()
确认该函数也只调用 async_to_sync(channel_layer.group_send)
一次。问题出在我的消费者身上——当我在 share_document_via_videocall()
中使用 print(self.channel_name)
时,我可以看到正在调用两个具有不同 self.channel_name
的实例(specific.AOQenhTn!fUybdYEsViaP
和 specific.AOQenhTn!NgtWxuiHtHBw
。似乎消费者连接了两个独立的通道,但我不确定为什么。当我将 print()
语句放入我的 connect()
时,我只看到它经历了一次连接过程.
如何确保我只连接到一个频道?
在settings.py
中:
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
#"hosts": [('127.0.0.1', 6379)],
"hosts": [(REDIS_HOST)],
},
},
}
消费者:
import json
from asgiref.sync import async_to_sync
from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncWebsocketConsumer
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import AnonymousUser
from .exceptions import ClientError
import datetime
from django.utils import timezone
class HeaderConsumer(AsyncWebsocketConsumer):
async def connect(self):
print("connecting")
await self.accept()
print("starting")
print(self.channel_name)
await self.send("request_for_token")
async def continue_connect(self):
print("continuing")
print(self.channel_name)
await self.get_user_from_token(self.scope['token'])
await self.channel_layer.group_add(
"u_%d" % self.user['id'],
self.channel_name,
)
#... more stuff
async def disconnect(self, code):
await self.channel_layer.group_discard(
"u_%d" % self.user['id'],
self.channel_name,
)
async def receive(self, text_data):
text_data_json = json.loads(text_data)
if 'token' in text_data_json:
self.scope['token'] = text_data_json['token']
await self.continue_connect()
async def share_document_via_videocall(self, event):
# Send a message down to the client
print("share_document received")
print(event)
print(self.channel_name)
print(self.user['id'])
await self.send(text_data=json.dumps(
{
"type": event['type'],
"message": event["message"],
},
))
@database_sync_to_async
def get_user_from_token(self, t):
try:
print("trying token" + t)
token = Token.objects.get(key=t)
self.user = token.user.get_profile.json()
except Token.DoesNotExist:
print("failed")
self.user = AnonymousUser()
REST API 调用:
class ShareViaVideoChat(APIView):
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, format=None):
data = request.data
recipient_list = data['recipient_list']
channel_layer = get_channel_layer()
for u in recipient_list:
if u['id'] != None:
print("sending to:")
print('u_%d' % u['id'])
async_to_sync(channel_layer.group_send)(
'u_%d' % u['id'],
{'type': 'share_document_via_videocall',
'message': {
'document': {'data': {}},
'sender': {'name': 'some name'}
}
}
)
return Response()
关于您使用不同频道名称进行呼叫,您确定您的前端没有两次连接到消费者吗?在浏览器中检查调试控制台。
我在使用 nextjs 作为 Django 通道 WebSocket 服务器的前端时遇到了同样的问题。
搜索后我发现了与两件事有关的问题:
1- react 严格模式(请求发送两次):
要在 next.js 中禁用 React 严格模式,转到模块名称“next.config.js”,并将严格模式的值更改为 false,如下所示:
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false,
}
module.exports = nextConfig
2- 在 nextjs 中代码 运行 两次(在 useEffect Hook 之外),一次在服务器端,第二次在客户端(这意味着每个用户将连接到 websocket 服务器两次,并获得两个通道name , 每次加入同一个群两次,频道名称不同。) ,
所以我更改了我的代码以仅从客户端连接 Django 通道服务器,如果您想查看我的完整代码/示例,请访问以下 URL,并注意有关“typeof window === "未定义":
前端 nextjs 代码:
我不知道我的问题是否与您的问题相同,但希望对您有所帮助。
我正在使用 DRF 3.11.0 和 Channels 2.4.0 的组合来实现后端,它托管在 Heroku on 1 dyno 上并附加了 Redis 资源。我的 React 前端有一个套接字,它从后端服务器成功 sends/received。
我遇到一个问题,即通过套接字发送回前端的任何消息都被发送了两次。我已经通过 console.log
确认前端只对后端执行一次 ping 操作。我可以通过 API 调用内部的 print()
确认该函数也只调用 async_to_sync(channel_layer.group_send)
一次。问题出在我的消费者身上——当我在 share_document_via_videocall()
中使用 print(self.channel_name)
时,我可以看到正在调用两个具有不同 self.channel_name
的实例(specific.AOQenhTn!fUybdYEsViaP
和 specific.AOQenhTn!NgtWxuiHtHBw
。似乎消费者连接了两个独立的通道,但我不确定为什么。当我将 print()
语句放入我的 connect()
时,我只看到它经历了一次连接过程.
如何确保我只连接到一个频道?
在settings.py
中:
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
#"hosts": [('127.0.0.1', 6379)],
"hosts": [(REDIS_HOST)],
},
},
}
消费者:
import json
from asgiref.sync import async_to_sync
from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncWebsocketConsumer
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import AnonymousUser
from .exceptions import ClientError
import datetime
from django.utils import timezone
class HeaderConsumer(AsyncWebsocketConsumer):
async def connect(self):
print("connecting")
await self.accept()
print("starting")
print(self.channel_name)
await self.send("request_for_token")
async def continue_connect(self):
print("continuing")
print(self.channel_name)
await self.get_user_from_token(self.scope['token'])
await self.channel_layer.group_add(
"u_%d" % self.user['id'],
self.channel_name,
)
#... more stuff
async def disconnect(self, code):
await self.channel_layer.group_discard(
"u_%d" % self.user['id'],
self.channel_name,
)
async def receive(self, text_data):
text_data_json = json.loads(text_data)
if 'token' in text_data_json:
self.scope['token'] = text_data_json['token']
await self.continue_connect()
async def share_document_via_videocall(self, event):
# Send a message down to the client
print("share_document received")
print(event)
print(self.channel_name)
print(self.user['id'])
await self.send(text_data=json.dumps(
{
"type": event['type'],
"message": event["message"],
},
))
@database_sync_to_async
def get_user_from_token(self, t):
try:
print("trying token" + t)
token = Token.objects.get(key=t)
self.user = token.user.get_profile.json()
except Token.DoesNotExist:
print("failed")
self.user = AnonymousUser()
REST API 调用:
class ShareViaVideoChat(APIView):
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, format=None):
data = request.data
recipient_list = data['recipient_list']
channel_layer = get_channel_layer()
for u in recipient_list:
if u['id'] != None:
print("sending to:")
print('u_%d' % u['id'])
async_to_sync(channel_layer.group_send)(
'u_%d' % u['id'],
{'type': 'share_document_via_videocall',
'message': {
'document': {'data': {}},
'sender': {'name': 'some name'}
}
}
)
return Response()
关于您使用不同频道名称进行呼叫,您确定您的前端没有两次连接到消费者吗?在浏览器中检查调试控制台。
我在使用 nextjs 作为 Django 通道 WebSocket 服务器的前端时遇到了同样的问题。
搜索后我发现了与两件事有关的问题:
1- react 严格模式(请求发送两次):
要在 next.js 中禁用 React 严格模式,转到模块名称“next.config.js”,并将严格模式的值更改为 false,如下所示:
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false,
}
module.exports = nextConfig
2- 在 nextjs 中代码 运行 两次(在 useEffect Hook 之外),一次在服务器端,第二次在客户端(这意味着每个用户将连接到 websocket 服务器两次,并获得两个通道name , 每次加入同一个群两次,频道名称不同。) ,
所以我更改了我的代码以仅从客户端连接 Django 通道服务器,如果您想查看我的完整代码/示例,请访问以下 URL,并注意有关“typeof window === "未定义":
前端 nextjs 代码:
我不知道我的问题是否与您的问题相同,但希望对您有所帮助。