在带有Django频道的房间中获取连接用户的列表

发布于 2025-02-13 08:57:08 字数 2311 浏览 2 评论 0原文

我们如何通过Django频道将连接用户的列表列入频道内的房间?我知道这个问题已经很多次,但是老实说,我在互联网上都搜寻了,我很惊讶没有传统的方式。根据 this 答案,Django频道已经避免了这种情况,但是,还有另一个软件包, django存在,可以满足它。

到目前为止,我有一个非常简单的应用程序,其中Websocket广播了从每个客户端发送的数据,因此房间中的每个连接客户端都会收到该数据。我也有一个简单的身份验证,因此只有身份验证的用户才能连接。这就是我所拥有的:

routing.py

application = ProtocolTypeRouter(
    {
        "websocket": TokenAuthMiddleware(
            URLRouter([
                re_path(r'ws/(?P<room_name>\w+)/$', consumers.MyConsumer.as_asgi()),
            ])
        )
    }
)

middleware.py

class TokenAuthMiddleware(BaseMiddleware):
    def __init__(self, inner):
        super().__init__(inner)

    async def __call__(self, scope, receive, send):
        # Get the token
        token = parse_qs(scope["query_string"].decode("utf8"))["token"][0]

        #custom code to validate token
        if token_is_valid:
            # assign scope['user'] = user
        else:
            scope['user'] = AnonymousUser()

        return await super().__call__(scope, receive, send)

commuter.py

    def connect(self):
        user = self.scope["user"]

        if user.is_anonymous:
            self.close()
        else:
            self.room_name = self.scope['url_route']['kwargs']['room_name']
            async_to_sync(self.channel_layer.group_add) (
                self.room_name,
                self.channel_name
            )

            self.accept() # WHAT CAN I DO HERE TO RETURN THE LIST OF CONNECTED USERS IN THIS SAME ROOM NAME?

    def disconnect(self, close_code):
        async_to_sync(self.channel_layer.group_discard) (
            self.room_name,
            self.channel_name
        )

关于我在消费者中的评论我想到了两个想法(第一个想法可能比第二个好):

  1. 使用内存通道层来跟踪用户连接/断开连接的用户。这样一来,我希望将用户列表回复回到Connect上已建立的连接(即客户端)。如何完成?

  2. 另外,我已经看到了可以使用数据库跟踪用户活动(即在线/离线等)的示例。对我来说,这不是一个完全最佳的解决方案,因为我不想查询此类活动的数据库。

如果有一个更好的解决方案,我全都是耳朵,但是从本质上讲,这怎么办?

How can we get the list of connected users to a room within channel with Django Channels? I know this question has been asked many times, but honestly, I've scoured all over the internet and I'm very surprised there isn't a conventional way of doing so. According to this answer, Django Channels have avoided this scenario, however, there is another package, Django Presence, that can cater for it.

So far, I've got a very simple app where a websocket broadcasts data that is sent from each client, so that each connected client in a room receives that data. I've also got a simple authentication so that only authenticated users can connect. This is what I have:

routing.py

application = ProtocolTypeRouter(
    {
        "websocket": TokenAuthMiddleware(
            URLRouter([
                re_path(r'ws/(?P<room_name>\w+)/

middleware.py

class TokenAuthMiddleware(BaseMiddleware):
    def __init__(self, inner):
        super().__init__(inner)

    async def __call__(self, scope, receive, send):
        # Get the token
        token = parse_qs(scope["query_string"].decode("utf8"))["token"][0]

        #custom code to validate token
        if token_is_valid:
            # assign scope['user'] = user
        else:
            scope['user'] = AnonymousUser()

        return await super().__call__(scope, receive, send)

consumer.py

    def connect(self):
        user = self.scope["user"]

        if user.is_anonymous:
            self.close()
        else:
            self.room_name = self.scope['url_route']['kwargs']['room_name']
            async_to_sync(self.channel_layer.group_add) (
                self.room_name,
                self.channel_name
            )

            self.accept() # WHAT CAN I DO HERE TO RETURN THE LIST OF CONNECTED USERS IN THIS SAME ROOM NAME?

    def disconnect(self, close_code):
        async_to_sync(self.channel_layer.group_discard) (
            self.room_name,
            self.channel_name
        )

with regards to my comment in the consumers, two ideas come to my mind (the first probably better than the second):

  1. Make use of the in memory channel layer to track which users connect/disconnect. In doing so, I'd like for the list of users to be replied back to the established connection (i.e. the client) on connect. How can this be done?

  2. Alternatively, I've seen examples where we can track a user's activity (i.e. online/away/offline etc) using the database. This for me isn't a totally optimal solution since I don't want to be querying the database for that sort of activity.

If there is a better solution, I'm all ears, but essentially, how can this be done?

, consumers.MyConsumer.as_asgi()), ]) ) } )

middleware.py

consumer.py

with regards to my comment in the consumers, two ideas come to my mind (the first probably better than the second):

  1. Make use of the in memory channel layer to track which users connect/disconnect. In doing so, I'd like for the list of users to be replied back to the established connection (i.e. the client) on connect. How can this be done?

  2. Alternatively, I've seen examples where we can track a user's activity (i.e. online/away/offline etc) using the database. This for me isn't a totally optimal solution since I don't want to be querying the database for that sort of activity.

If there is a better solution, I'm all ears, but essentially, how can this be done?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

没有心的人 2025-02-20 08:57:09

虽然我尚未在生产中测试它,但我能够通过将defaultDict作为类属性添加到我的消费者的类属性,并跟踪内存中相关连接信息。对于我的用例,我只关心用户的计数:

from collections import defaultdict
from channels.generic.websocket import AsyncWebsocketConsumer

class MyConsumer(AsyncWebsocketConsumer):
        room_connection_counts = defaultdict(lambda: 0)

        async def connect(self):
            self.user = self.scope['user']
            self.room_name = self.scope['url_route']['kwargs']['room_name']
            self.room_group_name = f'group_{self.room_name}'

            await self.channel_layer.group_add(
                self.room_group_name,
                self.channel_name,
            )

            await self.accept()

            self.room_connection_counts[self.room_name] += 1

            print(
                "Connection Count:",
                self.room_connection_counts[self.room_name]
            )

        async def disconnect(self):
            await self.channel_layer.group_discard(
                self.room_group_name,
                self.channel_name,
            )
            self.room_connection_counts[self.room_name] -= 1
            print(
                "Connection Count:",
                self.room_connection_counts[self.room_name]
            )

在您的情况下,您想跟踪连接用户的列表。为此,您将修改room_connection_counts defaultDict以返回listset> set默认情况下。例如,在班级主体中,您将使用:

room_connected_users = defaultdict(set)

在连接方法中,您将使用:

self.room_connected_users[self.room_name].add(self.user)

在断开连接中:

self.room_connected_users[self.room_name].remove(self.user)

对于问题的第二部分,您想将连接用户的列表发送回频道组。您可以通过在连接方法中添加以下内容来执行此操作:

async def connect(self):
    ...
    await self.channel_layer.group_send(
        self.room_group_name,
        {
            'type': 'user_connect',
            'message': list(self.room_connected_users[self.room_name])
        }
    )

并在您的消费者中定义user_connect方法,请通过group_send方法调用:

async def user_connect(self, event):
    message = event['message']

    await self.send(text_data=json.dumps({
        'type': 'player_connect',
        'message': message
    }))

您将遵循相同的模式以在disconnect上发送消息为出色地。

当然,您必须小心不要使内存超载,因此您可能希望对单个房间存储多少用户在将其传递给DB之前,对其进行限制。

我希望这会有所帮助!我很想听听其他人在这种方法中看到的好处/缺点,而不是打电话给数据库。

While I haven't tested it in production, I was able to handle this by adding a defaultdict as a class attribute to my consumer, and keeping track of the relevant connection information in-memory. For my use case, I only was concerned with the count of users:

from collections import defaultdict
from channels.generic.websocket import AsyncWebsocketConsumer

class MyConsumer(AsyncWebsocketConsumer):
        room_connection_counts = defaultdict(lambda: 0)

        async def connect(self):
            self.user = self.scope['user']
            self.room_name = self.scope['url_route']['kwargs']['room_name']
            self.room_group_name = f'group_{self.room_name}'

            await self.channel_layer.group_add(
                self.room_group_name,
                self.channel_name,
            )

            await self.accept()

            self.room_connection_counts[self.room_name] += 1

            print(
                "Connection Count:",
                self.room_connection_counts[self.room_name]
            )

        async def disconnect(self):
            await self.channel_layer.group_discard(
                self.room_group_name,
                self.channel_name,
            )
            self.room_connection_counts[self.room_name] -= 1
            print(
                "Connection Count:",
                self.room_connection_counts[self.room_name]
            )

In your case, you want to keep track of a list of connected users. To do that, you would modify the room_connection_counts defaultdict to return a list or set of users by default. For example, in the class body, you would use:

room_connected_users = defaultdict(set)

And in the connect method, you would use:

self.room_connected_users[self.room_name].add(self.user)

And in the disconnect:

self.room_connected_users[self.room_name].remove(self.user)

For the second part of your question, you want to send the list of connected users back to the channel group. You can do that by adding the following in your connect method:

async def connect(self):
    ...
    await self.channel_layer.group_send(
        self.room_group_name,
        {
            'type': 'user_connect',
            'message': list(self.room_connected_users[self.room_name])
        }
    )

And define a user_connect method in your consumer, to be called by the group_send method:

async def user_connect(self, event):
    message = event['message']

    await self.send(text_data=json.dumps({
        'type': 'player_connect',
        'message': message
    }))

You would follow the same pattern to send a message on disconnect as well.

Of course, you have to be careful not to overload your memory, so you might wish to put limits on how many users can be stored for a single room before passing it along to the db.

I hope this helps! I'm curious to hear the benefits/drawbacks others see in this approach vs. making calls out to the db.

遗弃M 2025-02-20 08:57:08

如果使用RedisChannellayer,请使用下一个方法扩展它们:

class ExtendedRedisChannelLayer(RedisChannelLayer):

    async def get_group_channels(self, group):
        assert self.valid_group_name(group), "Group name not valid"
        key = self._group_key(group)
        connection = self.connection(self.consistent_hash(group))
        # Discard old channels based on group_expiry
        await connection.zremrangebyscore(
            key, min=0, max=int(time.time()) - self.group_expiry
        )

        return [x.decode("utf8") for x in await connection.zrange(key, 0, -1)]

在设置中定义:

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "your_path.ExtendedRedisChannelLayer",
        ...
    }
}

在消费者内使用:

group_channels = await self.channel_layer.get_group_channels('group_name')

If you use RedisChannelLayer, extend them with next method:

class ExtendedRedisChannelLayer(RedisChannelLayer):

    async def get_group_channels(self, group):
        assert self.valid_group_name(group), "Group name not valid"
        key = self._group_key(group)
        connection = self.connection(self.consistent_hash(group))
        # Discard old channels based on group_expiry
        await connection.zremrangebyscore(
            key, min=0, max=int(time.time()) - self.group_expiry
        )

        return [x.decode("utf8") for x in await connection.zrange(key, 0, -1)]

define in settings:

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "your_path.ExtendedRedisChannelLayer",
        ...
    }
}

use inside the consumer:

group_channels = await self.channel_layer.get_group_channels('group_name')
浅听莫相离 2025-02-20 08:57:08

您可以创建一个模型chat,该将表示此ROOM

class Chat(models.Model):

    #collect in charfield `pk` of all users online 
    users_online = models.CharField(max_length=1500)

然后,当任何用户连接到Websocket时,将其添加到CHAT的 field field users_online

def connect(self):
    user = self.scope["user"]

    if user.is_anonymous:
        self.close()
    else:
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        async_to_sync(self.channel_layer.group_add) (
            self.room_name,
            self.channel_name
        )

        self.accept() 
        
        #after user successfully conected
        chat_object = Chat.objects.get(get_chat_object = here)
        # first option append it with semicolon, so later you could split list
        chat_object.users_online.append(str(user.pk)+";")
        self.custom_user_list = []

当用户与Websocket的用户断开连接时,用户用户pkusers_online字段

You can create a model chat which will represent this room

class Chat(models.Model):

    #collect in charfield `pk` of all users online 
    users_online = models.CharField(max_length=1500)

then when any user connects to the websocket, add him to Chat's field users_online:

def connect(self):
    user = self.scope["user"]

    if user.is_anonymous:
        self.close()
    else:
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        async_to_sync(self.channel_layer.group_add) (
            self.room_name,
            self.channel_name
        )

        self.accept() 
        
        #after user successfully conected
        chat_object = Chat.objects.get(get_chat_object = here)
        # first option append it with semicolon, so later you could split list
        chat_object.users_online.append(str(user.pk)+";")
        self.custom_user_list = []

When user disconnets from your websocket, remove users pk from the users_online field

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文