HOOOS

手把手教你:用 Django Channels 打造 WebSocket 实时聊天室

0 5 码农小飞 Django ChannelsWebSocket聊天室
Apple

想让你的 Django 项目拥有实时互动功能?WebSocket 是个不错的选择。而 Django Channels,则让在 Django 中使用 WebSocket 变得简单高效。本文将带你一步步使用 Django Channels 构建一个简单的在线聊天室,涵盖用户认证、消息广播和持久化存储等关键环节。

1. 什么是 Django Channels?

Channels 是一个 Django 的扩展,它将 Django 的同步 HTTP 处理模型扩展到支持 WebSocket、MQTT 等其他协议,并允许以异步方式处理这些协议。 简单来说,Channels 允许 Django 项目处理 WebSocket 连接,实现实时通信。

Channels 的优势:

  • 与 Django 深度集成: 可以无缝使用 Django 的模型、认证系统等。
  • 异步处理: 在高并发场景下,能更好地处理大量并发连接。
  • 多协议支持: 除了 WebSocket,还支持其他协议,如 MQTT。

2. 环境搭建与配置

2.1 创建 Django 项目

首先,创建一个新的 Django 项目:

django-admin startproject chat_project
cd chat_project
python manage.py startapp chat

2.2 安装 Channels

使用 pip 安装 Channels 及其依赖:

pip install channels asgiref
pip install daphne  #生产环境推荐使用daphne作为 ASGI server

2.3 配置 Channels

chat_project/settings.py 文件中进行如下配置:

  • 添加到 INSTALLED_APPS:
INSTALLED_APPS = [
    'channels',
    'chat', # 你的 app
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
  • 配置 ASGI_APPLICATION:
ASGI_APPLICATION = 'chat_project.asgi.application'
  • 配置 Channel Layer: Channels 使用 Channel Layer 来进行消息路由。 这里我们使用 InMemoryChannelLayer注意: InMemoryChannelLayer 仅适用于开发环境,生产环境应使用 Redis 或其他更可靠的 Channel Layer。
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels.layers.InMemoryChannelLayer'
    }
}

2.4 创建 asgi.py 文件

如果你的 Django 项目中没有 asgi.py 文件,需要手动创建。 在 chat_project 目录下创建 asgi.py 文件,并添加以下内容:

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chat_project.settings')

application = get_asgi_application()

from channels.routing import ProtocolTypeRouter
from channels.auth import AuthMiddlewareStack
from channels.routing import URLRouter
import chat.routing

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": AuthMiddlewareStack(
        URLRouter(
            chat.routing.websocket_urlpatterns
        )
    ),
})

3. 用户认证

为了实现用户认证,我们需要使用 Django 的认证系统。 首先,确保你已经创建了用户模型,并且可以使用 django.contrib.auth 提供的认证功能。

3.1 创建用户模型 (可选)

如果你还没有自定义用户模型,可以使用 Django 默认的用户模型。如果需要自定义用户模型,请参考 Django 官方文档。

3.2 创建登录/注册视图 (可选)

创建登录和注册视图,允许用户登录和注册。 这里省略具体代码,你可以参考 Django 的官方文档或者其他教程。

3.3 配置 AuthMiddlewareStack

asgi.py 文件中,我们使用了 AuthMiddlewareStack 来处理 WebSocket 连接的认证。 AuthMiddlewareStack 会自动从 HTTP session 中获取用户信息,并将其添加到 WebSocket 的 scope 中。

4. 编写 WebSocket Consumers

Consumers 是 Channels 中处理 WebSocket 连接的核心组件。 它类似于 Django 的 View,但处理的是 WebSocket 连接的事件,例如连接、接收消息和断开连接。

4.1 创建 Consumer

chat/consumers.py 文件中创建 Consumer:

import json
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync

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

        # 加入房间组
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )

        self.accept()

    def disconnect(self, close_code):
        # 离开房间组
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name,
            self.channel_name
        )

    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']
        username = self.scope['user'].username  # 获取用户名

        # 发送消息到房间组
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message,
                'username': username, # 包含用户名
            }
        )

    def chat_message(self, event):
        message = event['message']
        username = event['username']

        # 发送消息到 WebSocket
        self.send(text_data=json.dumps({
            'message': message,
            'username': username, # 包含用户名
        }))
  • connect 方法: 当 WebSocket 连接建立时被调用。 它获取房间名称,并将当前连接加入到对应的房间组中。 async_to_sync 用于在同步环境中调用异步方法。
  • disconnect 方法: 当 WebSocket 连接断开时被调用。 它将当前连接从房间组中移除。
  • receive 方法: 当从 WebSocket 接收到消息时被调用。 它解析 JSON 数据,获取消息内容,然后将消息发送到房间组。 这里我们从 self.scope['user'].username 获取用户名,这依赖于 AuthMiddlewareStack 的配置。
  • chat_message 方法: 当从房间组接收到消息时被调用。 它将消息内容发送到 WebSocket 连接。

4.2 配置路由

chat/routing.py 文件中配置 WebSocket 路由:

from django.urls import re_path

from . import consumers

websocket_urlpatterns = [
    re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
]

4.3 将路由添加到 asgi.py

确保将你的app的路由添加到 asgi.py 文件中,像这样:

import chat.routing

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": AuthMiddlewareStack(
        URLRouter(
            chat.routing.websocket_urlpatterns
        )
    ),
})

5. 使用 Channels Groups 进行消息广播

Channels Groups 允许你将多个 WebSocket 连接添加到一个组中,并向该组发送消息。 这非常适合实现聊天室的消息广播功能。

在上面的 ChatConsumer 中,我们使用了 channel_layer.group_add 将连接添加到房间组,并使用 channel_layer.group_send 向房间组发送消息。

6. 消息持久化存储

为了保存聊天记录,你需要将消息持久化存储到数据库中。 你可以创建一个 Django 模型来存储消息,并在 receive 方法中将消息保存到数据库。

6.1 创建消息模型

chat/models.py 文件中创建消息模型:

from django.db import models
from django.contrib.auth.models import User

class Message(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    room_name = models.CharField(max_length=255)
    content = models.TextField()
    timestamp = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.content

6.2 修改 Consumer 保存消息

修改 ChatConsumerreceive 方法,将消息保存到数据库:

from .models import Message

def receive(self, text_data):
    text_data_json = json.loads(text_data)
    message = text_data_json['message']
    username = self.scope['user'].username

    # 保存消息到数据库
    Message.objects.create(user=self.scope['user'], room_name=self.room_name, content=message)

    # 发送消息到房间组
    async_to_sync(self.channel_layer.group_send)(
        self.room_group_name,
        {
            'type': 'chat_message',
            'message': message,
            'username': username,
        }
    )

6.3 显示历史消息

你可以创建一个 Django View 来获取历史消息,并在模板中显示它们。

7. 前端实现

前端需要使用 JavaScript 连接 WebSocket,并发送和接收消息。 这里提供一个简单的示例:

<!DOCTYPE html>
<html>
<head>
    <title>Chat Room</title>
</head>
<body>
    <h1>Chat Room: {{ room_name }}</h1>
    <div id="chat-log"></div>
    <input type="text" id="chat-message-input" size="100">
    <button id="chat-message-submit">Send</button>

    <script>
        const roomName = {{ room_name|safe }};

        const chatSocket = new WebSocket(
            'ws://'
            + window.location.host
            + '/ws/chat/'
            + roomName
            + '/'
        );

        chatSocket.onmessage = function(e) {
            const data = JSON.parse(e.data);
            const message = data['message'];
            const username = data['username'];
            document.querySelector('#chat-log').innerHTML += ('<p><strong>' + username + ':</strong> ' + message + '</p>');
        };

        chatSocket.onclose = function(e) {
            console.error('Chat socket closed unexpectedly');
        };

        document.querySelector('#chat-message-input').focus();
        document.querySelector('#chat-message-input').onkeyup = function(e) {
            if (e.keyCode === 13) {  // enter, return
                document.querySelector('#chat-message-submit').click();
            }
        };

        document.querySelector('#chat-message-submit').onclick = function(e) {
            const messageInputDom = document.querySelector('#chat-message-input');
            const message = messageInputDom.value;
            chatSocket.send(JSON.stringify({
                'message': message
            }));
            messageInputDom.value = '';
        };
    </script>
</body>
</html>

8. 运行项目

  1. 运行 Django 开发服务器:

    python manage.py runserver
    
  2. 运行 Daphne ASGI 服务器:

    daphne chat_project.asgi:application --port 8000
    

    注意: 在生产环境中,你应该使用 Supervisor 或 Systemd 等工具来管理 Daphne 进程。

  3. 在浏览器中打开 http://localhost:8000/chat/<你的房间名>/, 即可开始聊天。

9. 总结

本文详细介绍了如何使用 Django Channels 构建一个简单的在线聊天室,涵盖了环境搭建、用户认证、WebSocket Consumers、消息广播和持久化存储等关键环节。 希望本文能帮助你快速上手 Django Channels,构建更强大的实时应用。 记住,这只是一个简单的示例,你可以根据自己的需求进行扩展和优化。 例如,你可以添加更多的功能,如发送图片、语音消息等,或者使用更高级的 Channel Layer,如 Redis Channel Layer,来提高性能和可靠性。

点评评价

captcha
健康