【问题标题】:Django-channels : ChatConsumer is sending message to only one user instead of sending it to both usersDjango-channels:ChatConsumer 仅向一个用户发送消息,而不是向两个用户发送消息
【发布时间】:2021-03-22 14:56:35
【问题描述】:

我正在使用 django-channels 和 redis 在 django 和 Angular 中实现一个聊天应用程序。

套接字已连接并正常工作,但我面临的问题是,当两个用户在线并使用它连接的相同线程 url 连接同一个聊天室时,任何用户发送的消息仅发送给连接的用户套接字最近,它们被发送两次只给那个用户。

在 django 中我做了以下配置:

settings/base.py

....
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.sites',

    'channels',
    'chats'
]

ASGI_APPLICATION = "influnite.routing.application"

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("localhost", 6379)],
            # "hosts": [os.environ.get('REDIS_URL', 'redis://localhost:6379')]
        },
    },
}
....

routing.py

from django.conf.urls import url
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidator, OriginValidator

from chats.consumers import ChatConsumer

application = ProtocolTypeRouter({
    'websocket': AllowedHostsOriginValidator(
        AuthMiddlewareStack(
            URLRouter(
                [
                    url(r"^messages/(?P<thread_id>[\w.+]+)/", ChatConsumer())
                ]
            )
        )
    )
})

我创建了三个模型,即ThreadThreadMemberChatMessage

chats/models.py

from django.db import models
from django.db.models import Q
from django.utils import timezone
from django.contrib.auth.models import User

from base.models import BaseModel

# Create your models here.
MESSAGE_TYPE = [
    ('text', 'Text'),
    ('audio', 'Audio'),
    ('img', 'Image'),
    ('doc', 'Document'),
    ('link', 'Link')
]

THREAD_TYPE = [
    ('individual', 'Individual'),
    ('group', 'Group')
]

class Thread(BaseModel):
    name = models.CharField(max_length=20, null=True, blank=True)
    timestamp = models.DateTimeField(auto_now_add=True)
    thread_type = models.CharField(max_length=20, choices=THREAD_TYPE, default='individual')

    class Meta:
        db_table = 'in_thread'
        verbose_name = 'threads'
        verbose_name_plural = 'thread'
        ordering = ['-update_date']

class ThreadMember(BaseModel):
    thread = models.ForeignKey(Thread, on_delete=models.CASCADE, related_name='thread_member')
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='thread_member_user')
    is_grp_admin = models.BooleanField(default=False)

    def __str__(self):
        return f'{self.thread.name} > {self.user}'

    class Meta:
        db_table = 'in_thread_member'
        verbose_name = 'thread members'
        verbose_name_plural = 'thread member'

class ChatMessage(BaseModel):
    thread = models.ForeignKey(Thread, on_delete=models.CASCADE, related_name='msg_thread')
    sender = models.ForeignKey(ThreadMember, on_delete=models.CASCADE, related_name='msg_sender')
    message = models.TextField(null=True, blank=True)
    sent_at = models.DateTimeField(default=timezone.now())
    read_receipt = models.BooleanField(default=False)
    msg_type = models.CharField(max_length=20, choices=MESSAGE_TYPE, default='text')

    def __str__(self):
        return f'{self.sender} > {self.message}'

    class Meta:
        db_table = 'in_chat_message'
        verbose_name = 'chat message'
        verbose_name_plural = 'chat messages'
        ordering = ['sent_at']

下面是包含 ChatConsumer 类的 consumers.py 文件。

chats/consumers.py

from django.contrib.auth.models import User

import asyncio, json
from channels.consumer import AsyncConsumer
from channels.db import database_sync_to_async

from .models import Thread, ThreadMember, ChatMessage
from .serializers import ChatMessageSerializer

class ChatConsumer(AsyncConsumer):
    async def websocket_connect(self, event):
        print("connected", event)
        try:
            kwargs = self.scope['url_route']['kwargs']

            thread_id = kwargs.get('thread_id', False)
            if thread_id:
                thread = await self.get_thread(thread_id)
                if thread:
                    self.chat_room = f'thread_{thread_id}'
                    await self.channel_layer.group_add(
                        self.chat_room,
                        self.channel_name
                    )
                    await self.send({
                        "type": "websocket.accept"
                    })
            else:
                await self.send({
                    "type": "websocket.close"
                })
        except Exception as e:
            print("Error in websocket connection!")
            print(e)

    async def websocket_receive(self, event):
        print("receive", event)
        try:
            kwargs = self.scope['url_route']['kwargs']
            
            thread_id = kwargs.get('thread_id', False)
            thread = await self.get_thread(thread_id)

            response = event.get('text', False)
            response = json.loads(response)
            message = response.get('message', False)

            if message:
                data, message_saved = await self.save_message(
                    message, response.get('sender'), thread)

                if message_saved:
                    text = json.dumps(response)

                    if thread:
                        await self.channel_layer.group_send(
                            self.chat_room,
                            {
                                "type": "chat_message",
                                "text": text
                            }
                        )
        except Exception as e:
            print("Error in websocket receive!")
            print(e)
    
    async def websocket_disconnect(self, event):
        print("disconnected", event)

    async def chat_message(self, event):
        """sends the actual message"""
        try:
            await self.send({
                "type": "websocket.send",
                "text": event['text']
            })
        except Exception as e:
            print("Error sending messages!")
            print(e)

    @database_sync_to_async
    def get_thread(self, thread_id):
        return Thread.objects.get(id=thread_id)
    
    @database_sync_to_async
    def save_message(self, message, sender, thread):
        try:
            sender = ThreadMember.objects.get(
                thread=thread.id,
                user=User.objects.get(id=sender))
            chat = ChatMessage.objects.create(
                thread=thread,
                sender=sender,
                message=message
            )
            chat.save()
            thread.save()
            return ChatMessageSerializer(chat).data, True
        except Exception as e:
            print("Error saving chat!")
            print(e)
            return False

我在运行 redis 服务器时得到以下信息。

C:\Users\rh>cd C:\Program Files\Redis
C:\Program Files\Redis>redis-server redis.windows.conf
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 5.0.10 (1c047b68/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 9628
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

[9628] 11 Dec 12:38:17.011 # Server initialized
[9628] 11 Dec 12:38:17.011 * DB loaded from disk: 0.000 seconds
[9628] 11 Dec 12:38:17.011 * Ready to accept connections

我不知道我在这里做错了什么,我感谢一些帮助。如果需要更多信息,我会改进我的问题。

提前致谢!

【问题讨论】:

    标签: python django websocket django-channels


    【解决方案1】:

    试试这个:

    1. django.core.asgi 导入get_asgi_application
    2. 允许 Django 的 ASGI 应用程序处理传统的 http 请求,方法是在 ProtocolTypeRouter 内的字典中添加 http 作为键和 get_asgi_application() 作为键的值。
    3. 在路由ChatConsumer 消费者时使用/调用as_asgi() 类方法。

    routing.py

    from django.conf.urls import url
    from django.core.asgi import get_asgi_application #Change No.1
    
    from channels.routing import ProtocolTypeRouter, URLRouter
    from channels.auth import AuthMiddlewareStack
    from channels.security.websocket import AllowedHostsOriginValidator, OriginValidator
    
    from chat.consumers import ChatConsumer
    
    application = ProtocolTypeRouter({
        "http": get_asgi_application(), #Change No.2
        "websocket": AllowedHostsOriginValidator(
            AuthMiddlewareStack(
                URLRouter([
                    url(r"^messages/(?P<username>[\w.@+-]+)/$", ChatConsumer.as_asgi()), #Change No.3
               ])
            )
        )
    })
    

    更多关于channels.readthedocs.io的信息。

    【讨论】:

      【解决方案2】:

      就我而言,我需要将channels 更新到至少3.0.1(由于https://github.com/django/channels/issues/1550)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-01-12
        • 1970-01-01
        • 1970-01-01
        • 2021-04-07
        • 2021-08-02
        • 1970-01-01
        • 2017-06-04
        相关资源
        最近更新 更多