Django-Channels聊天室

本文介绍如何使用WebSocket技术实现多人在线聊天室。从原理出发,详细解释WebSocket连接机制,并通过Django框架进行代码实战,包括环境搭建、配置及消费者逻辑处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

讲解路线:聊天室原理——>多人聊天——>房间活动——>点对点聊天——>消息推送

一、聊天室原理

即时通讯在如今已经十分常见,大到QQ、微信,小到网站客服,这其实是同一个“聊天室”原理。聊天室采用WebSocket技术,下面来聊一聊WebSocket技术。

1. WebSocket原理

与http连接不同,websocket是长连接,一次握手即可传输消息,每个连接有一个唯一的身份标识
在这里插入图片描述

2. 多人聊天原理

多人聊天则是通过groupid将一个个的连接进行分组。客户端要发送聊天信息给服务器端,服务器在和该客户端同一组的组内进行消息广播,这样,每个连接都能接收到消息。
在这里插入图片描述

点对点聊天原理

相当于这两个人是一组

消息推送

相当于一个人一组,这同时也使用于特殊定制通知服务。

二、代码实战

教材 https://channels.readthedocs.io/en/stable/introduction.html

django 同时支持http和websocket请求,只不过涉及到的配置文件不同

1. 安装

python -m pip install -U channels

2. 在Django中注册channels

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
]

3. 使用asgi服务器与wsgi服务器

asgi.py

import os
from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'GameDesc.settings')
application = get_asgi_application()

wsgi.py

import os
from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'GameDesc.settings')
application = get_wsgi_application()

4. 新启项目Room并进行配置

4.1 新建项目
python3 manage.py startapp Room
4.2 配置settings
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
    'Room',
]
4.3 新建文件夹customers和routings.py

views文件夹下为http类型接口处理的视图函数,customers文件夹下处理websocket请求
urls.py为http类型的路由,routings为websocket类型的路由
在这里插入图片描述

6. 添加总路由

在项目文件夹(有settings的文件夹)下新建routings.py,作为总路由
routings.py

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import Room.routing

application = ProtocolTypeRouter({
    # (http->django views is added by default)
    'websocket': AuthMiddlewareStack(
        URLRouter(
            Room.routing.websocket_urlpatterns
        )
    ),
})

7. 设置关于channels的settings

目录结构
在这里插入图片描述

# Channels
ASGI_APPLICATION = 'GameDesc.routing.application'
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [('127.0.0.1', 6379)],
        },
    },
}

8. 创建视图文件

8.1 预备知识1

基本的customers结构(同步)

# chat/consumers.py
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer

class ChatConsumer(WebsocketConsumer):
	
	# 建立连接
    def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = 'chat_%s' % self.room_name

        # Join room group
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )
        
		# 接受连接,不接收不连接
        self.accept()
	
	# 断开连接
    def disconnect(self, close_code):
        # Leave room group
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name,
            self.channel_name
        )

    # Receive message from WebSocket 从客户端发送的包中获取消息
    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        
        # 从包中获取消息,前端的字段为message
        message = text_data_json['message']

        # Send message to room group 将要广播的消息添加至包中,字段为message
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message
            }
        )

    # Receive message from room group  
    def chat_message(self, event):
        message = event['message']

        # Send message to WebSocket 发送到ws连接中
        self.send(text_data=json.dumps({
            'message': message
        }))
8.2 预备知识2

基本的customers结构(异步),和同步一样

import json
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = 'chat_%s' % self.room_name

        # Join room group
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )

        await self.accept()

    async def disconnect(self, close_code):
        # Leave room group
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    # Receive message from WebSocket
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        # Send message to room group
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message
            }
        )

    # Receive message from room group
    async def chat_message(self, event):
        message = event['message']

        # Send message to WebSocket
        await self.send(text_data=json.dumps({
            'message': message
        }))
8.3 代码实战

回到customers文件夹下,创建ChatCustomers.py

8.3.1 说明:

(1)由于是教学版,故省略较多逻辑检查
(2)项目需要,故返回了历史聊天记录,但是实际上是不需要储存聊天记录的,就像加入QQ群聊,只能获取加入之后的消息,是不会获取到加入之前的聊天记录的
(3)这一环节没有涉及到mysql等关系型数据库的操作,故可以使用异步提高效率,但是涉及到对mysql数据库增删改查等操作时,不能使用异步,只能使用同步,否则会报错(亲测)

8.3.2 ChatCustomers.py

有了上面的知识,相信你一定可以看明白啦

from GameDesc.settings import MAX_REDIS_SAVE_TIME
from channels.generic.websocket import AsyncWebsocketConsumer
from GameDesc.redis_tools.redis_message import redis_get_room_message, redis_save_room_message # 此处是redis存储消息,是为了获取历史消息,可以不管
import json
import datetime
import time


# 进入房间后即可点击加入聊天室  注意检查用户是否登录
class RoomChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_id = self.scope['url_route']['kwargs']['room_id'] # 获取传输的房间Id
        self.user_id = self.scope['url_route']['kwargs']['user_id']  # 获取传输的user_id
        self.room_group_name = 'chat_%s' % self.room_id  # 添加分组名以chat_开头
        # Join room chat group
        await self.channel_layer.group_add(
            self.room_group_name,  # 房间号标识 用于分组
            self.channel_name,  # 个人标识
        )
        await self.accept()
        # 发送加入房间消息并且获取历史聊天记录
        flag, old_messages = redis_get_room_message(room_group_name=self.room_group_name)
        if flag == False:
            message = {
                "_id": '2',
                "groupId": self.room_id,
                "nickName": self.user_id, 
                "avatarUrl": "",
                "textContent": "未获得历史消息",
                "user_id": "oKYr74nbN8kRyqZZXGAHb-0xWuzQ1",  # user_id
                "sendTime": time.ctime(),
                "sendTimeTS": time.ctime()
            }
            old_messages.append(message)
        else:
            message = {
                "_id": '1',
                "groupId": self.room_id,
                "nickName": self.user_id,
                "avatarUrl": "",
                "textContent": "{0} 已加入聊天室...".format(self.user_id),
                "user_id": "oKYr74nbN8kRyqZZXGAHb-0xWuzQ1",
                "sendTime": time.ctime(),
                "sendTimeTS": time.ctime()
            }
            old_messages.append(message)
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': old_messages  # 把发送的信息放到event['message']字段里面 ,返回所有信息,数组形式
            }
        )

    async def disconnect(self, close_code):
        self.room_id = self.scope['url_route']['kwargs']['room_id']
        self.user_id = self.scope['url_route']['kwargs']['user_id']
        # 退出聊天室向组内所有人广播
        message = {
            "_id": '0',
            "groupId": self.room_id,
            "nickName": self.user_id,  
            "avatarUrl": "",
            "textContent": "{0} 已退出聊天室...".format(self.user_id),
            "user_id": "oKYr74nbN8kRyqZZXGAHb-0xWuzQ1",
            "sendTime": time.ctime(),
            "sendTimeTS": time.ctime()
        }
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message  # 把发送的信息放到event['message']字段里面
            }
        )
        # Leave room group
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    # Receive message from WebSocket
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']
        redis_save_room_message(self.room_group_name, message, MAX_REDIS_SAVE_TIME)  # 时间暂定
        # Send message to room group
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message,  # 把发送的信息放到event['message']字段里面
            }
        )

    # Receive message from room group
    async def chat_message(self, event):
        message = event['message']
        # Send message to WebSocket 将前面函数返回的消息发送到ws连接上
        await self.send(text_data=json.dumps({
            'message': message,
        }))

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰西啊杰西

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值