Django-Channels聊天室

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

一、聊天室原理

即时通讯在如今已经十分常见,大到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,
        }))

  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
基于 Django 开发聊天室可以分为两个部分:前端和后端。 前端部分可以使用现成的 JavaScript 库,如 Socket.IO 或 SockJS,它们可以实现实时通信,同时提供了一些实用的功能,如房间管理、私聊等。前端代码可以放在一个单独的 HTML 文件中,也可以通过 Django 模板集成到 Django 项目中。 后端部分需要使用 DjangoWebSocket 库,如 Django Channels,来处理客户端和服务器之间的实时通信。使用 Django Channels,可以在 Django 中使用 WebSocket,将其与其他 Django 插件和功能集成,从而提高开发效率。在后端中,需要定义 WebSocket 通信协议,处理客户端的连接、断开、消息发送等事件。 下面是一个简单的基于 DjangoDjango Channels 的聊天室实现示例: 1. 安装 DjangoDjango Channels: ``` pip install Django pip install channels ``` 2. 创建一个 Django 项目和应用程序: ``` django-admin startproject chatroom cd chatroom python manage.py startapp chat ``` 3. 定义聊天室WebSocket 协议: 在 chat 应用程序中创建一个 `consumers.py` 文件,并定义一个 WebSocketConsumer 类处理 WebSocket 协议: ```python from channels.generic.websocket import WebsocketConsumer import json class ChatConsumer(WebsocketConsumer): def connect(self): self.accept() def disconnect(self, close_code): pass def receive(self, text_data): text_data_json = json.loads(text_data) message = text_data_json['message'] self.send(text_data=json.dumps({ 'message': message })) ``` 4. 配置 Django Channels: 在 chatroom 项目中的 `settings.py` 文件中添加以下配置: ```python INSTALLED_APPS = [ # ... 'channels', ] ASGI_APPLICATION = 'chatroom.routing.application' CHANNEL_LAYERS = { 'default': { 'BACKEND': 'channels.layers.InMemoryChannelLayer', }, } ``` 5. 定义 WebSocket 路由: 在 chatroom 项目中创建一个 `routing.py` 文件,定义 WebSocket 路由: ```python from django.urls import re_path from . import consumers websocket_urlpatterns = [ re_path(r'ws/chat/$', consumers.ChatConsumer.as_asgi()), ] ``` 6. 定义聊天室的前端界面: 在 chatroom 项目中的 `templates` 文件夹中创建一个 `index.html` 文件,并定义聊天室的前端界面: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Chat Room</title> </head> <body> <div id="chat"> <form id="chat-form"> <input type="text" id="chat-message-input" placeholder="Type your message here..."> <button type="submit">Send</button> </form> <ul id="chat-messages"></ul> </div> <script src="https://cdn.jsdelivr.net/npm/@chaitin/socket.io-client@1.0.0/dist/socket.io.min.js"></script> <script> const socket = io('http://localhost:8000/ws/chat/'); const chatForm = document.querySelector('#chat-form'); const chatInput = document.querySelector('#chat-message-input'); const chatMessages = document.querySelector('#chat-messages'); chatForm.addEventListener('submit', (event) => { event.preventDefault(); const message = chatInput.value; socket.emit('chat_message', { 'message': message }); chatInput.value = ''; }); socket.on('chat_message', (data) => { const message = data['message']; const li = document.createElement('li'); li.textContent = message; chatMessages.appendChild(li); }); </script> </body> </html> ``` 7. 创建 Django 视图和 URL: 在 chat 应用程序中创建一个 `views.py` 文件,定义一个视图函数返回聊天室的前端界面: ```python from django.shortcuts import render def index(request): return render(request, 'index.html') ``` 在 chat 应用程序中的 `urls.py` 文件中定义 URL: ```python from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), ] ``` 8. 运行聊天室: 在终端中运行以下命令: ``` python manage.py runserver ``` 在浏览器中打开 `http://localhost:8000/`,就可以看到聊天室的前端界面。在不同的浏览器窗口中打开该 URL,即可在聊天室中实现实时通信。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

杰西啊杰西

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

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

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

打赏作者

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

抵扣说明:

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

余额充值