Django+Nginx+uwsgi网站使用Channels+redis+daphne实现简单的多人在线聊天及消息存储功能
网站部署在华为云服务器上,Debian系统,使用Django+Nginx+uwsgi搭建。最终效果如下图所示。

一、响应逻辑顺序
1. 聊天页面请求
客户端请求/chat/(输入聊天室房间号界面)和/chat/room_name(某个聊天室页面)链接时,由Nginx转到Django由urls.py解析并返回相应页面,在返回的聊天室页面内置了javascript程序,请求建立wss:/ws/chat/room_name的websocket连接。
2. websocket连接请求
客户端向Nginx发送websocket连接请求,根据Ngnix配置文件location /ws {}中设置的反向代理,请求被转到本地的7001端口( http://127.0.0.1:7001),将由运行在该端口上的由daphne托管的asgi服务解析并响应(配置文件为myproject/asgi.py)。
3. asgi.py解析并响应
asgi.py中定义的application定义了websocket的解析规则,具体由/myproject/routing.py中的websocket_urlpatterns进行地址解析,通过Channels由myapp/comsumers.py进行异步信息处理并响应。
二、配置Channels和asgi
1. 安装Channels
sudo pip3 install channels
2. settings.py中添加应用并配置channels和asgi
INSTALLED_APPS = ['django.contrib.auth','django.contrib.admin', 'django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','channels','myapp',
]ASGI_APPLICATION = 'myproject.asgi.application'# Channels_Layers相关配置
CHANNEL_LAYERS = {'default': {'BACKEND': 'channels_redis.core.RedisChannelLayer','CONFIG': {"hosts": [('127.0.0.1', 6379)],},},
}# 配置认证后端
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',
)
Django Channels利用 Redis 作为通道层来管理客户端和服务器之间的通信。当客户端通过 WebSocket 连接发送消息时,Django Channels 会使用 Redis 将消息分发到所有连接的客户端。Redis 充当消息代理,实时有效地处理消息的路由和传递。
ASGI_APPLICATION指向Django项目下的asgi.py文件中的application。
3. 配置asgi.py文件
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
django.setup()from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from myproject import routingsapplication = ProtocolTypeRouter({'http':get_asgi_application(), #支持http请求'websocket':AuthMiddlewareStack(URLRouter(routings.websocket_urlpatterns) #支持webscoket请求),
})
此处django.setup()和DJANGO_SETTINGS_MODULE设置应放在导入模块前,否则容易出现如下报错:
django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
Requested setting DEBUG, but settings are not configured.
You must either define the environment variable DJANGO_SETTINGS_MODULE
or call settings.configure() before accessing settings...
WSGI和ASGI 的主要区别是同步VS异步,WSGI是同步的,每个请求必须等待前一个请求完成。而ASGI是异步的,可以同时处理多个请求。 WSGI主要用于HTTP协议,ASGI旨在支持WebSocket、HTTP2等协议。ASGI的主要特点是异步非阻塞,它能够更好地处理并发请求。
上述设置中websocket地址解析由Django项目下的routings.py完成,其功能与urls.py类似。
4. 配置routings.py
from django.urls import path
from myapp import consumerswebsocket_urlpatterns = [path(r'ws/chat/<str:room_name>/', consumers.ChatConsumer.as_asgi())
]
myapp为新建的应用,其下的consumers.py文件定义了websocket的响应函数ChatConsumer。
5. 配置myapp/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from asgiref.sync import sync_to_async
from myapp.models import ChatMessageclass ChatConsumer(AsyncWebsocketConsumer):async def connect(self):self.room_name = self.scope["url_route"]["kwargs"].get("room_name") #获取传输的房间号if self.room_name:self.room_group_name = f"chat_{self.room_name}"# Join room groupawait self.channel_layer.group_add(self.room_group_name, self.channel_name)await self.accept()else:await self.close()async def disconnect(self, close_code):# Leave room groupawait self.channel_layer.group_discard(self.room_group_name, self.channel_name)# Receive message from WebSocketasync def receive(self, text_data):text_data_json = json.loads(text_data)message = text_data_json["message"]username = text_data_json["username"]#保存消息await self.save_message(username, self.room_name, message)# 向群组内的所有客户端发送消息,type为“chat_message"await self.channel_layer.group_send(self.room_group_name, {"type": "chat_message", "message": message,'username':username})# Receive message from room groupasync def chat_message(self, event):message = event["message"]username = event['username']# Send message to WebSocketawait self.send(text_data=json.dumps({"message": message,'username':username}))# 信息写入数据库@sync_to_async()def save_message(self, username, room, message):if len(message) !=0 :newmsg = ChatMessage.objects.create(username=username, room=room, content=message)newmsg.save()
文件中定义了websocket的连接,消息的接收、发送和存储,每条消息在发送给群组内的所有客户端前先存储到数据库。
三、消息存储
1. 配置myapp/models.py,用于消息存储
from django.db import modelsclass ChatMessage(models.Model):username = models.CharField(max_length=100,verbose_name='用户名')room = models.CharField(max_length=100)content = models.TextField(verbose_name='内容')create_time = models.DateTimeField(auto_now_add=True,verbose_name='写入时间')def __str__(self):return f"房间号:{self.room}--用户:{self.username}({self.content})"
2. 配置myapp/admin.py,便于在Django后台处理存储的消息
from django.contrib import admin# Register your models here.
from .models import ChatMessage
admin.site.register(ChatMessage)
效果如下:

四、安装redis并启动
1. 安装redis
sudo apt-get install redis-server
安装后redis-server会自动启动,可以使用sudo systemctl start/stop/restart/status redis.service来启动/停止/重启/查看服务器。
$sudo systemctl status redis.service ● redis-server.service - Advanced key-value storeLoaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled)Active: active (running) since Tue 2024-11-19 23:38:28 CST; 20h agoDocs: http://redis.io/documentation,man:redis-server(1)Main PID: 123767 (redis-server)Status: "Ready to accept connections"Tasks: 5 (limit: 2321)Memory: 7.2MCPU: 1min 36.022sCGroup: /system.slice/redis-server.service└─123767 /usr/bin/redis-server 127.0.0.1:6379
五、安装daphne,并启动asgi服务
1. 安装daphne
Daphne 是 Django Channels 项目的一部分,专门用于为 Django 提供支持 WebSocket和 ASGI 协议的异步服务器。
sudo pip3 install daphne
启动asgi服务的命令如下,监听端口为7001。
daphne -p 7001 myproject.asgi:application
asgi服务的配置文件即为前面已经完成的asgi.py。
为了管理方便,将其做成系统服务开机启动。
2. 设置daphne.service服务
新建/etc/systemd/system/daphne.service文件
sudo nano /etc/systemd/system/daphne.service
内容如下:
[Unit]
Description=Daphne Server
After=network.target[Service]
Type=exec
WorkingDirectory=/home/yislwll/Django/myproject
ExecStart=/usr/bin/daphne -p 7001 myproject.asgi:application
Restart=always
User=yislwll
Group=Yisl
Environment=PYTHONPATH=/home/yislwll/Django/myproject[Install]
WantedBy=multi-user.target
然后启动daphne服务:
sudo systemctl enable daphne.service
sudo systemctl start daphne.service
需要特别注意的是,修改consumers.py文件后一定要重启daphne服务,否则对websocket通讯内容的更改无法更新。
sudo systemctl restart daphne.service
六、配置Nginx
根据响应逻辑,Ngnix应该对websocket的连接“wss://域名/ws/chat/room_name”做出响应,请求将被代理到本地的7001端口,由运行在该端口上由daphne托管的asgi服务解析并响应。
1. 配置/etc/nginx/nginx.conf
网站为https://连接,所以webscoket连接为wss://
server {listen 443 ssl;server_name xxxx.com;charset utf-8;ssl_certificate /path/to/fullchain.pem; # SSL证书的路径ssl_certificate_key /path/to/privkey.pem; # 私钥的路径# SSL 配置ssl_session_timeout 1d;ssl_session_cache shared:MozSSL:10m; # about 4000 sessionsssl_session_tickets off;# 指定加密套件ssl_protocols TLSv1.2 TLSv1.3;ssl_ciphers 'xxxxxxxxx';ssl_prefer_server_ciphers on;location / {#由django响应uwsgi_pass 127.0.0.1:8080;include uwsgi_params;include /etc/nginx/uwsgi_params;uwsgi_param UWSGI_SCRIPT iCourse.wsgi;uwsgi_param UWSGI_CHDIR /iCourse;index index.html index.htm;client_max_body_size 35m;index index.html index.htm;}location /ws {#wss协议由asgi服务响应proxy_http_version 1.1;proxy_set_header Host $host;proxy_set_header X-Real-Ip $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Nginx-Proxy true;proxy_redirect off;client_max_body_size 10m;proxy_pass http://127.0.0.1:7001;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade";proxy_connect_timeout 300s;proxy_read_timeout 300s;proxy_send_timeout 300s; }}
2. 重启Nginx服务
sudo systemctl restart nginx.service
七、客户端界面
1. 配置urls.py,响应客户端界面请求
from myapp import views as channelsviewurlpatterns = [......path('chat/',channelsview.chat, name='webchat'),path('chat/<str:room_name>/', channelsview.chatroom, name='chatroom'),......
]
客户端页面请求将由myapp/views.py来处理。
2. 配置views.py
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from .models import ChatMessage# Create your views here.
@login_required(login_url='/login/')
def chat(request):return render(request,"channels/chattingindex.html")@login_required(login_url='/login/')
def chatroom(request,room_name):username = request.session.get('username','游客')msgs = ChatMessage.objects.filter(room=room_name).order_by('create_time')[0:20]return render(request,"channels/chattingroom.html",{'room_name':room_name,'username':username, 'msgs':msgs})
在chatroom函数中,对当前聊天室历史消息的最近20条进行了查询,并由页面展示出来。
3. 聊天页面设计及通讯
chattingroom.html文件内容大致如下:
{% extends "newdesign/newbase.html" %}{% load django_bootstrap5 %}{% block mytitle %}<title>{{room_name}}号聊天室</title><style>.chat-window {max-width: 900px;height: 500px;overflow-y: scroll; /* 添加垂直滚动条 */margin: auto;background-color: #f1f1f1;border: 2px solid #09e3f7;border-radius: 5px;padding: 10px;}.chat-message {clear: both;overflow: hidden;margin-bottom: 10px;text-align: left;}.chat-message .message-content {border-radius: 5px;padding: 8px;max-width: 500px;float: left;clear: both;}.chat-message.right .message-content {background-color: #428bca;color: white;float: right;width:420px;}.chat-message.right .user-content {background-color: #f7e91d;border-radius:4px;color: black;float: right;width: auto;text-align: right;padding-left:10px;padding-right:10px;}.chat-message.left .message-content {background-color: #2ef3be;border-color: #ddd;float:left;width:420px;}.chat-message.left .user-content {background-color: #f7e91d;border-radius:4px;border-color: #ddd;float: left;width: auto;text-align: left;padding-left:8px;padding-right:8px;}</style>{% endblock %}{% block maincontent %} <div class="container"><div id="chat-record" class="chat-window">{% for m in msgs %}{% if m.username == request.user.username %}<div class="chat-message right"><div class="user-content">{{m.username}}</div><div class="message-content"><span>{{m.content}}</span></div></div><br>{% else %}<div class="chat-message left"><div class="user-content">{{m.username}}</div><div class="message-content"><span>{{m.content}}</span></div></div><br>{% endif %}{% endfor %}</div></div><br><div class="mx-auto px-auto" style="text-align: center;"><input id="chat-message-input" type="text" style="width:900px;" size="150"><br><br><input id="chat-message-submit" type="button" value="发送消息"></div>{{ room_name|json_script:"room-name" }}{{ username|json_script:"username" }}<script>window.onload = function() {var scrollableDiv = document.getElementById('chat-record');// 设置scrollTop使得滚动条向下翻scrollableDiv.scrollTop = scrollableDiv.scrollHeight;};const roomName = JSON.parse(document.getElementById('room-name').textContent);const username = JSON.parse(document.getElementById('username').textContent);const chatSocket = new WebSocket('wss://scybbd.com/ws/chat/' + roomName + '/');chatSocket.onmessage = function(e) {const data = JSON.parse(e.data);//data为收到的后端发出来的数据console.log(data);if (data['message']) {if(data['username'] == username){document.querySelector('#chat-record').innerHTML += ('<div class="chat-message right"><div class="user-content">' + data['username'] + '</div><div class="message-content"><span>' +data['message'] + '</span></div></div><br>');}else{document.querySelector('#chat-record').innerHTML += ('<div class="chat-message left"><div class="user-content">' + data['username'] + '</div><div class="message-content"><span>' + data['message'] + '</span></div></div><br>');}} else {alert('消息为空!')}var scrollableDiv = document.getElementById('chat-record');// 设置scrollTop使得滚动条向下翻scrollableDiv.scrollTop = scrollableDiv.scrollHeight;};chatSocket.onclose = function(e) {console.error('聊天端口非正常关闭!');};document.querySelector('#chat-message-input').focus();document.querySelector('#chat-message-input').onkeyup = function(e) {if (e.keyCode === 13) { // enter, returndocument.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,'username':username}));messageInputDom.value = '';};</script>
{% endblock %}
聊天界面比较简陋,客户端与服务器的wss://连接和通讯由页面中javacript脚本完成。
至此,一个简单的多人在线聊天的页面基本完成。
相关文章:
Django+Nginx+uwsgi网站使用Channels+redis+daphne实现简单的多人在线聊天及消息存储功能
网站部署在华为云服务器上,Debian系统,使用DjangoNginxuwsgi搭建。最终效果如下图所示。 一、响应逻辑顺序 1. 聊天页面请求 客户端请求/chat/(输入聊天室房间号界面)和/chat/room_name(某个聊天室页面)链…...
数据结构在二叉树Oj中利用子问题思路来解决问题
二叉树Oj题 获取二叉树的节点数获取二叉树的终端节点个数获取k层节点的个数获取二叉树的高度检测为value的元素是否存在判断两颗树是否相同判断是否是另一棵的子树反转二叉树判断一颗二叉树是否是平衡二叉树时间复杂度O(n*n)复杂度O(N) 二叉树的遍历判断是否是对称的二叉树二叉…...
华为openEuler考试真题演练(附答案)
【单选题】 以下关于互联网的描述,哪个选项是正确的? A:Nginx 在万维网中可以作为 ftp 服务器的反向代理,并与ftp服务器的数量--对应 B:Nginx 在互联网中可以作为 web服务器端,成为万维网的一个节点 C:互联网上的的资源需使用 Nginx进行七层…...
生成自签名证书并配置 HTTPS 使用自签名证书
生成自签名证书 1. 运行 OpenSSL 命令生成证书和私钥 在终端中输入以下命令,生成自签名证书和私钥文件: sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout self_signed.key -out self_signed.pem-x509:生成自签名证书。…...
物联网核心安全系列——智能汽车安全防护的重要性
汽车行业引入的智能硬件技术已经越来越多,早先设计者更多考虑到的是硬件成本和软件用户体验等因素,但随着国外两位技术人员成功实现远程控制汽车的视频曝出后,智能汽车安全便成为了一个热议话题。 汽车总线架构及原理比较复杂,日…...
数据库视图
数据库视图(Database View)是数据库中的虚拟表,其内容由查询定义,通常用来简化复杂的查询或提供安全访问数据。视图并不存储实际数据,而是将查询的结果集作为一个“虚拟表”呈现。用户通过查询视图,就可以看…...
从传统分析到智能问数,打造零门槛数据分析方案
众所周知,传统报表和自助分析工具存在使用门槛,且早期智能分析不够智能。随着AI技术发展,现有数据应用模式难以满足多样化、快速变化的需求,数据驱动、敏捷决策、精细运营成为了各大企业的新课题。 01企业数据应用挑战 业务人员的…...
java 设计模式 模板方法模式
模板方法模式(Template Method Pattern)是一种行为型设计模式,它在父类中定义一个算法的框架,允许子类在不改变算法结构的情况下重写算法的某些特定步骤。这种模式非常适合于那些有一定公共流程,但某些步骤需要子类定制…...
基于UDP和TCP实现回显服务器
目录 一. UDP 回显服务器 1. UDP Echo Server 2. UDP Echo Client 二. TCP 回显服务器 1. TCP Echo Server 2. TCP Echo Client 回显服务器 (Echo Server) 就是客户端发送什么样的请求, 服务器就返回什么样的响应, 没有任何的计算和处理逻辑. 一. UDP 回显服务器 1. UD…...
在 CentOS 系统上直接安装 MongoDB 4.0.25
文章目录 步骤 1:配置 MongoDB 官方源步骤 2:安装 MongoDB步骤 3:启动 MongoDB 服务步骤 4:验证安装步骤 5:可选配置注意事项 以下是在 CentOS 系统上直接安装 MongoDB 4.0.25 的详细步骤: 步骤 1&#x…...
Android和IOS的区别
一、系统区别 1、系统和框架的区别 (1)Android系统的底层建立在Linux系统之上;而ios基于UNIX系统 Android完全开放,iOS完全封源开发 (2)编程语言:Android的编程语言是Java和KotLin;而ios的则为O…...
数据库基础(MySQL)
1. 数据库基础 1.1 什么是数据库 存储数据用文件就可以了,为什么还要弄个数据库? 文件保存数据有以下几个缺点: 文件的安全性问题文件不利于数据查询和管理文件不利于存储海量数据文件在程序中控制不方便 数据库存储介质: 磁盘内存 为…...
Vue前端开发子组件向父组件传参
在父组件中,如果需要获取子组件中的数据,有两种方式,一种是在子组件中自定义事件,父组件绑定该事件,当触发自定义事件时,向父组件传入参数;另一种是先通过ref属性给子组件命名,然后在父组件中就…...
javaScript语法基础(函数,对象,常用类Array,String,Math和Date)
# 本文详细结束了JavaScript中函数、对象、常用类Array,String,Math和Date的用法。 一、函数 1、概述 将程序中多次要用到的代码块封装起来,就是函数。函数使代码块的重复使用更方便,且功能独立,便于维护。 2、函数的…...
WebStorm 2022.3.2/IntelliJ IDEA 2024.3出现elementUI提示未知 HTML 标记、组件引用爆红等问题处理
WebStorm 2022.3.2/IntelliJ IDEA 2024.3出现elementUI提示未知 HTML 标记、组件引用爆红等问题处理 1. 标题识别elementUI组件爆红 这个原因是: 在官网说明里,才版本2024.1开始,默认启用的 Vue Language Server,但是在 Vue 2 项…...
k8s-NetworkPolicy
NetworkPolicy 是k8s中的网络策略可以限制pod以及namespace之间的访问流量 演示一下名称空间之间基于端口的访问限制 官方对networkpolicy的介绍 官方网址: 网络策略 |Kubernetes (简体中文) 一:创建NetworkPolicy vim…...
【C++】踏上C++学习之旅(九):深入“类和对象“世界,掌握编程的黄金法则(四)(包含四大默认成员函数的练习以及const对象)
文章目录 前言1. 实现Date类的构造函数2. 实现Date类的拷贝构造函数3. 实现Date类的赋值运算符重载4. 实现各Date对象之间的比较接口5. 实现Date对象的加减接口6. const成员7. 取地址及const取地址操作符重载 前言 在我们前面学习到了"类和对象"的四大默认成员函数(…...
C++——智能指针剖析
参考: 恋恋风辰官方博客 动态内存管理 - cppreference.com SRombauts/shared_ptr: 一个最小的 shared/unique_ptr 实现,用于处理 boost/std::shared/unique_ptr 不可用的情况。 C智能指针_c 智能指针-CSDN博客 当…...
241119.LeetCode——383.赎金信
题目描述 给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以,返回 true ;否则返回 false 。 magazine 中的每个字符只能在 ransomNote 中使用一次。 示例 1: 输…...
基于SSM的农家乐管理系统+论文示例参考
1.项目介绍 功能模块:管理员(农家乐管理、美食信息管理、住宿信息管理、活动信息、用户管理、活动报名、论坛等),普通用户(注册登录、活动报名、客房预订、用户评价、收藏管理、模拟支付等)技术选型&#…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
