Можно ли перечислить каналы, хранящиеся в группе?
Можно ли получить доступ к списку каналов, добавленных в группу, с помощью django-channel?
def consumer(message):
Group("group_name").add(message.reply_channel)
channel_list = Group("group_name").???
РЕДАКТИРОВАТЬ:
Я пытаюсь добиться доступа к сеансам всех каналов, например, к списку подключенных пользователей.
До сих пор я использую систему на основе базы данных, которая перечисляет соединения. Но если сервер отключается без выполнения моих потребителей ws_disconnect, объект этих соединений останется в базе данных, и я этого не хочу.
РЕДАКТИРОВАТЬ 2:
Чтобы просмотреть список подключенных пользователей, я нашел django-channels-presence
, Я проверю это.
6 ответов
Я проверял django-channels-presence
и мне легко удалось перечислить подключенных пользователей для Group
создавая Room
(эта ручка Group
Управление / создание, channel
добавление / удаление...) и это обеспечивает get_users()
метод, который позволяет мне достичь того, что я искал.
Он также предоставляет способ очистки добавленных каналов, которые не удаляются в случае сбоя сервера (это не вызовет потребителя ws_disconnect, который используется для удаления этих каналов из группы). Это обеспечивает prune_presence
а также prune_room
задачи, которые очищают просроченные каналы.
Да, это возможно. И легкий взлом...
# Get channel_layer function
from channels.asgi import get_channel_layer
# passing group_channel takes channel name
channel_layer = get_channel_layer()
ch_group_list = channel_layer.group_channels('<your group name>')
Я нашел это channel_layer.group_channels('<your group name>')
, упомянутое выше, не работало на каналах 2. Итак, я решил сохранить необходимую мне информацию в channel_layer, и она заработала.
информация о версии
- каналы == 2.1.5
- aioredis == 1.2.0
- каналы-redis==2.3.2
В моем случае мне нужно знать номер каналов в группе, потому что я запускаю ритм сельдерея в фоновом режиме для продолжения передачи данных в каналы, но когда последний канал в группе отключается, я хочу остановить ритм сельдерея.
Я считаю каналы в группе, сохраняя их в channel_layer.
при подключении
count = getattr(self.channel_layer, self.group_name, 0)
if not count:
setattr(self.channel_layer, self.group_name, 1)
else:
setattr(self.channel_layer, self.group_name, count + 1)
при отключении
count = getattr(self.channel_layer, self.group_name, 0)
setattr(self.channel_layer, self.group_name, count - 1)
if count == 1:
delattr(self.channel_layer, self.group_name)
# stop my celery beat
При желании вы можете сохранить подключенного пользователя в комнате в базе данных. Создайте комнату и добавьте пользователей при подключении и удалите использование при отключении.
from django.db import models
from django.contrib.auth import get_user_model
from asgiref.sync import sync_to_async
# ./models
class Room(models.Model):
room_name = models.CharField(max_length=150, unique=True)
users = models.ManyToManyField(get_user_model(), related_name='rooms')
@classmethod
@sync_to_async
def add(cls, room_name, user):
room, created = cls.objects.get_or_create(room_name=room_name)
room.users.add(user)
return created # sockets => join or create
@classmethod
@sync_to_async
def users_count(cls, room_name):
rooms = cls.objects.filter(room_name=room_name)
if rooms.exists():
return rooms.first().users.count()
return 0
@classmethod
@sync_to_async
def remove_user(cls, user, room_name):
room = cls.objects.filter(room_name=room_name)
if room.exists():
room.users.remove(user)
# ./consumer.py
class YourConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'video_%s' % self.room_name
self.group_users = {self.scope.get('user').id: 1}
if self.scope.get('user').is_authenticated:
room = await Room.add(self.room_name, self.scope.get('user'))
await self.channel_layer.group_add(
self.room_name,
self.channel_name
)
await self.accept()
async def disconnect(self, code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
if self.scope.get('user').is_authenticated:
await Room.remove_user(self.room_name, self.scope.get('user'))
async def websocket_receive(self, message):
count = await Room.users_count(room_name=self.room_name)
await self.send(json.dumps({
'type': 'websocket.send',
'text': 'pong',
'group': self.room_group_name,
'room': self.room_name,
'connections': count
}))
я создаю объект вAuthMiddlewareStack
class TokenAuthMiddleware:
handler: MessengerHandler = None
def __init__(self, inner):
self.inner = inner
async def generate_object(self):
if self.handler is None: # just for first time
self.handler= MessengerHandler()
await self.handler.import_key()
return self.handler
async def __call__(self, scope, send, rec):
model = await self.generate_object()
scope["handler"] = model
return await self.inner(scope, send, rec)
вAsyncWebsocketConsumer
сорт
@property
def _get_handler(self) -> MessengerHandler:
return self.scope["handler"]
соединять
self._get_handler.add_new_channel(self.channel_name, self.user_api.user_id)
Отключить
async def disconnect(self, close_code):
if self.user_api.is_active:
self._get_handler.remove_channel(self.channel_name, self.user_api.user_id)
pass
MessengerHandler
class MessengerHandler:
channels: dict[int, ClientChannels] = {}
def add_new_channel(self, channel: str, user_id: int):
....
def remove_channel(self, channel: str, user_id: int):
....
def get_user_channels(self, user_id: int, without_channel: str = None) -> list:
...
в противном случае безопасный поток с использованием пакета Django-Redis
обрабатывать каналы
@staticmethod
def add_new_channel(channel: str, user_id: int, client_type: str, client_id: int):
channel_name = f"u_{user_id}_{client_type}_{client_id}"
cache.set(channel_name, channel, timeout=None)
@staticmethod
def remove_channel(channel_type: str, user_id: int, client_id: int):
channel_name = f"u_{user_id}_{channel_type}_{client_id}"
cache.delete(channel_name)
@staticmethod
def get_user_channels(user_id: int) -> list:
channel_name = f"u_{user_id}_*"
values = cache.get_many(cache.keys(channel_name))
return [v for k, v in values.items()]
Это, вероятно, не очень хорошее решение в целом, и в моем случае оно точно не сократит его в 100% случаев, действительно было предназначено только для конкретного использования, но в любом случае я остановился на этом:
- Обратите внимание на метод в файле consumer.py (как его обычно называют в учебниках), прикрепленный к вашему классу потребителей - в моем случае player_manager.
async_to_sync(self.channel_layer.group_send)
(self.game_id_name,
{
'type': 'player_manager', #notice this name "player_manager"
'team': team,
'name': name,
'p_id': p_id
})
- Перейдите к этому методу, и вы можете добавить переменную
в моем случае на канал так:
if getattr(self.channel_layer, "data", None):
self.channel_layer.data = {} #this just a dictionary that you can now modify to add data accessible to any one connected to a specific channel group
- Затем я отправляю все эти данные и на стороне клиента (веб-страница), после получения, анализирую ее и соответствующим образом обновляю страницу.