I am building the chat functionality on my app with django-channels and redis, and I am new to websocket.
after collecting enough infomation, here's my sketch of the application.
basically there are two types of chat:
- team chat
- user chat
they are all integrited into one chat friends view.
for team chat, such a team with pk=8
, the user connects to
let route = `ws://${window.location.host}/ws/team/${team.pk}/`
to send and receive messages. and if the user is not in the team, the connection will be rejected.
this means if you joined to team8, team12, team13... you be connected to ws8, ws12, ws13...
for user app, establish ws connection between two user: for example, ws1 is used for user1 to receive message, which if user2 want to send message to user1, user2 must connect to the socket.
this means if you have n
contacts, it will be n
ws connections.
the chat consumer only applies in chat view, and I have a notification app uses ws and is implemented throughout the application. user1 connect to note1 to receive system infomation.
this means if n
user is online, n
note socket connections will be established.
soon I noticed the performance and number limit issue, which per browser only allow those for particular domain:
IE 6
chrome 256
firefox 200
safari(macos) 1273
and the larger the amount of connections, the more pressue there will be for server.
my app's target user works in office, therefore the messages are important and should be saved in the database.
class ChatDatabaseMixin:
@staticmethod
@database_sync_to_async
def newText(read, text, style, sender_pk, receiver_pk):
msg = Message(
sender=User.objects.get(pk=sender_pk).chatProfile,
receiver=User.objects.get(pk=receiver_pk).chatProfile,
text=text,
style=style,
read=read,
)
msg.save()
return msg
@staticmethod
@database_sync_to_async
def newAudio(read, binary, sender_pk, receiver_pk):
msg = Message(
sender=User.objects.get(pk=sender_pk).chatProfile,
receiver=User.objects.get(pk=receiver_pk).chatProfile,
audio=binary,
read=read,
)
msg.save()
return msg
@staticmethod
@database_sync_to_async
def talkAble(sender_pk: int, receiver_pk: int):
return Relation.talkAble(sender_pk, receiver_pk)
it is complicated since user and team both support sending varies media:
class ChatConsumer(ChatDatabaseMixin, AsyncJsonWebsocketConsumer):
@staticmethod
def send_online(sender_pk, receiver_pk):
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
__class__.rn(receiver_pk),
{
'type': 'online_status',
'message': 'online',
'user': sender_pk,
}
)
async def online_status(self, event):
# print(f"{event['user']} online to {self.room_name}")
await self.send(text_data=json.dumps({
'type': 'online',
'message': event['message'],
'user': event['user'],
}))
async def receive(self, text_data=None, bytes_data=None):
sender_pk = int(self.scope["user"].pk)
receiver_pk = int(self.room_name)
async def receive(self, text_data=None, bytes_data=None):
sender_pk = int(self.scope["user"].pk)
receiver_pk = int(self.room_name)
talkable = await ChatConsumer.talkAble(sender_pk, receiver_pk)
if not talkable['status']:
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'info_message',
'to': sender_pk,
'e': talkable['e'],
}
)
return
try:
e = talkable['e']
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'info_message',
'to': sender_pk,
'e': e,
}
)
except KeyError:
pass
if bytes_data:
if len(bytes_data) > 1000000:
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'info_message',
'to': sender_pk,
'e': 'sorry the audio is too long, please limit it by 30seconds',
}
)
return
# audio
read = await ChatConsumer.toreadReceiveBool(self)
msg = await ChatConsumer.newAudio(
read,
bytes_data,
sender_pk,
receiver_pk,
)
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'audio_message',
'pk': msg.pk,
'audio': bytes_data,
'sender': sender_pk,
'receiver': receiver_pk,
'read': read,
}
)
else:
# text
text_data_json = json.loads(text_data)
if text_data_json['type'] == 'text':
message = text_data_json['message']
style = None
try:
style = text_data_json['style']
except KeyError:
pass
'''
sender: self.scope["user"].pk
receiver: self.room_name
'''
read = await ChatConsumer.toreadReceiveBool(self)
# print(read)
msg = await ChatConsumer.newText(
read,
message,
style,
sender_pk,
receiver_pk,
)
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'pk': msg.pk,
'message': message,
'style': style,
'sender': sender_pk,
'receiver': receiver_pk,
'read': read,
}
)
async def info_message(self, event):
await self.send(text_data=json.dumps({
'to': event['to'],
'type': 'info',
'e': event['e'],
}))
async def chat_message(self, event):
# print(event)
await self.send(text_data=json.dumps({
'sender': event['sender'],
'receiver': event['receiver'],
'type': 'text',
'pk': event['pk'],
'message': event['message'],
'style': event['style'],
'read': event['read'],
}))
async def audio_message(self, event):
# backend always send json
# onmessage always receive json
# frontend can send blob, self.scope point to user
await self.send(
text_data=json.dumps({
'pk': event['pk'],
'sender': event['sender'],
'receiver': event['receiver'],
'type': 'audio',
'audio': base64.b64encode(event['audio']).decode('utf-8'),
'read': event['read'],
}),
# bytes_data=event['audio']
)
team consumer is half different half the same. The most complicated part of each consumer is the receive method that checks the conditionals.
Regard this post: Design/Architecture: web-socket one connection vs multiple connections
current server:
- cpu: 4 cores
- ram: 16GB
- bandwidth: 14mbps
I am not sure if this the correct way of doing it, I read a lot of relevant post suggest using less connections, does it mean one socket for one user
, or one socket throughout the entire application
? I don’t know what is the suitable architecture for my application.
sorry, I am not intended to post the entire thing up to let you to solve it for me, I just need a direction, if you have been there please show me a way to go.
if I should put it all in one
, wouldn’t that be difficult to combine three very different consumers together with clarity and elegancy? And wouldn’t that be insecure since anyone can connect to it, send via it and listen to it?
if my structure is fine, can I just add a user and team contact restriction then?