【玩转全栈】---- Django 基于 Websocket 实现群聊(解决channel连接不了)

news2025/4/1 5:36:02

学习视频:

14-11 群聊(一)_哔哩哔哩_bilibili

目录

Websocket 连接不了?

收发数据

断开连接

完整代码

聊天室的实现

聊天室一

聊天室二

settings 配置

consumer 配置

多聊天室


Websocket 连接不了?

基于这篇博客:

【全栈开发】---- 一文掌握 Websocket 原理,并用 Django 框架实现_django websocket-CSDN博客

        之前这篇博客虽然大致原理都介绍了,但最终的代码并没有实现,这是因为博主当时遇见了一个问题,尽管我按照教程来的,但是 websocket 服务就是连不上,后面也参考了许多博客,也去官网看了,还去 github 上抄项目来对比,都解决不了,后来急得我转 SpringBoot 去了。但偶然间发现了这篇博客:

https://blog.csdn.net/qq_25218219/article/details/131752459Django的websocket

最终问题才得以解决,再次感谢这位博主!!!

解决办法很简单,基于上面学习视频的配置后,需要在注册组件的 “channels” 前面添加一个组件    "daphne"

INSTALLED_APPS = [
    "daphne",
    "channels",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "app01.apps.App01Config"
]

然后运行就能连上 asgi 了:

大致原因是 pip install channels 按照命令默认按照的是最新版的 channels ,可能与 Django 版本并不匹配。

收发数据

websocket 模式中,服务端和客户端都能主动收发数据:

在客户端发数据:

function sendMessage(){
        var txt = document.getElementById("txt")
        console.log(txt.value)
        socket.send(txt.value)
    }

在服务端收数据:

    def websocket_receive(self, message):
        # 收数据message
        print("接收消息-->",message["text"])

在服务端发数据:

使用 send() 方法即可

    def websocket_connect(self,message):
        print("发送连接请求")
        self.accept()
        # 发数据
        self.send("来了呀客官")
    def websocket_receive(self, message):
        # 收数据message
        print("接收消息-->",message["text"])
        self.send(message["text"])

在客户端收数据:

这里的服务端发数据在发送 websocket 连接函数和接收消息函数中都可,相对于,在客户端收数据也对应两种方法,一个是 socket.onopen ,创建好连接后自动触发(握手环节,服务端执行self.accept());还有一个就是 socket.onmessage ,用于正常接收数据。

socket.onopen = function(event){
        console.log(event.value)
        let lag = document.createElement("div")
        lag.innerText = "[websocket连接成功]"
        document.getElementById("message").appendChild(lag)
    }

{#收数据#}
socket.onmessage = function (event){
        var data = event.data
        console.log("客户端接收到消息-->",data)
        let lag = document.createElement("div")
        lag.innerText = data
        document.getElementById("message").appendChild(lag)
    }

断开连接

在服务端断开连接一般是经过下面这个函数:

    def websocket_disconnect(self, message):
        # 浏览器关闭也会自动发送断开链接请求
        print("断开连接")
        # 服务端同意断开连接
        raise StopConsumer()

        这个函数不仅仅关闭浏览器的请求链接,还会关闭服务端链接,实现完全断连。在类中其他函数中可使用 self.close()  来调用此关闭链接函数,实现完全断连;而如果用 raiseStopConsumer() ,则表示仅仅断开服务器连接,也不会执行 websocket_disconnect 函数。

服务器断开连接时,客户端也会触发一个函数:

socket.onclose = function (event){

    }

并且客户端也可以设置按钮,主动断开连接:

function closeOnn(){
        socket.close()
    }

完整代码

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .message{
            height: 300px;
            width: 100%;
            border: 1px solid #dddddd;
        }
    </style>
</head>
<body>
<div class="message" id="message"></div>
<div>
    <input type="text" placeholder="请输入" id="txt">
    <input type="button" value="发送" onclick="sendMessage()">
    <input type="button" value="断开连接" onclick="closeOnn()">
</div>
<script>
    socket = new WebSocket("ws://127.0.0.1:8080/room/123/")
    {#创建好连接后自动触发(握手环节,服务端执行self.accept())#}
    socket.onopen = function(event){
        console.log(event.value)
        let lag = document.createElement("div")
        lag.innerText = "[websocket连接成功]"
        document.getElementById("message").appendChild(lag)
    }
    {#发数据#}
    function sendMessage(){
        var txt = document.getElementById("txt")
        console.log(txt.value)
        socket.send(txt.value)
    }
    {#收数据#}
    socket.onmessage = function (event){
        var data = event.data
        console.log("客户端接收到消息-->",data)
        let lag = document.createElement("div")
        lag.innerText = data
        document.getElementById("message").appendChild(lag)
    }
    {#服务器主动断开连接,触发#}
    socket.onclose = function (event){

    }
    function closeOnn(){
        socket.close()
    }
</script>
</body>
</html>

consumers.py:

from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
# socket = new WebSocket("ws://127.0.0.1:8000/room/123/")
class ChatConsumer(WebsocketConsumer):
    print("进入消费者")
    def websocket_connect(self,message):
        print("发送连接请求")
        self.accept()
        # 发数据
        self.send("来了呀客官")
    def websocket_receive(self, message):
        # 收数据message
        print("接收消息-->",message["text"])
        # 服务器主动断开连接
        if message["text"] == "关闭":
            self.close()
            # 如果在这儿加上下面代码,执行StopConsumer异常,那么就不会执行websocket_disconnect
            raise StopConsumer()
            # return
        self.send(message["text"])
    # 调用self.close()方法默认都会调用下面这个函数
    def websocket_disconnect(self, message):
        # 浏览器关闭也会自动发送断开链接请求
        print("断开连接")
        # 服务端同意断开连接
        raise StopConsumer()

聊天室的实现

当然,上面只是介绍 websocket 的一般使用,还并没有实际应用,下面将以聊天室场景进行应用。

聊天室一

前面的操作都是基于 self 来的。服务端仅仅关心自己与对应浏览器的连接通道,而不会联系到其它浏览器。可使用列表存储各个用户,某用户想断开连接或者主动退出浏览器时,再到列表中删除用户:

需要注意的是,用户添加到列表中后,后续的一系列操作需要在列表中循环操作每一个对象,以实现群聊

from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer

CONN_LIST = []
class ChatConsumer(WebsocketConsumer):
    print("进入消费者")
    def websocket_connect(self,message):
        print("发送连接请求")
        self.accept()
        CONN_LIST.append(self)
    def websocket_receive(self, message):
        res = message["text"]
        # 收数据message
        print("接收消息-->",res)
        for conn in CONN_LIST:
            conn.send(res)
    # 调用self.close()方法默认都会调用下面这个函数
    def websocket_disconnect(self, message):
        # 浏览器关闭也会自动发送断开链接请求
        print("断开连接")
        CONN_LIST.remove(self)
        # 服务端同意断开连接
        raise StopConsumer()

结果:

聊天室二

        聊天室一虽然能实现简单的群聊功能,但是使用列表来储存各个用户,其实效率会很低,并且功能也不强大,Django 的 channels 组件中有一个更加厉害的东西叫  channel layers,可以帮助我们更加方便地去实现这种群聊。

参考文章:django channels - 武沛齐 - 博客园

settings 配置

layers 需要在 setting 中进行配置:

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels.layers.InMemoryChannelLayer",
    }
}

consumer 配置

再修改 Consumer 代码:

from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from asgiref.sync import async_to_sync

class ChatConsumer(WebsocketConsumer):
    print("进入消费者")
    def websocket_connect(self,message):
        print("发送连接请求")
        self.accept()
        # 将这个客户端的连接对象加入到某个地方(内存或redis),channel_name 随机给个名字
        async_to_sync(self.channel_layer.group_add)("111",self.channel_name)
    def websocket_receive(self, message):
        res = message["text"]
        # 收数据message
        print("接收消息-->",res)
        async_to_sync(self.channel_layer.group_send)("111",{"type":"send_to","message":message})
    def send_to(self,event):
        # 群中每一个连接对象都发送
        text = event["message"]["text"]
        self.send(text)
    # 调用self.close()方法默认都会调用下面这个函数
    def websocket_disconnect(self, message):
        # 浏览器关闭也会自动发送断开链接请求
        print("断开连接")
        async_to_sync(self.channel_layer.group_discard)("111",self.channel_name)
        # 服务端同意断开连接
        raise StopConsumer()

部分解释:

需要注意的是,这里的 channel_layer 操作都是异步进行的,需要自己导入 async_to_sync 进行异步转同步操作。

async_to_sync((self.channel_layer.group_add)("111",self.channel_name)

这里的作用是将本连接对象存入 channel_layer 中,并且 group 名为 "111" ,self.channel_name 的作用是连接对象存储时,随机给一个名字。

async_to_sync(self.channel_layer.group_send)("111",{"type":"send_to","message":message})
def send_to(self,event):
    # 群中每一个连接对象都发送
    text = event["message"]["text"]
    self.send(text)

这里的作用是为 "111" 群聊中每个连接对象执行 type 对应的方法,并传入 message 给每个连接对象;下面的 send_to 方法就是为每一个连接对象发送 text 消息。

async_to_sync(self.channel_layer.group_discard)("111",self.channel_name)

这里的作用是为群聊中的每一个连接对象关闭连接。

上诉代码已能实现聊天室功能,但还不够高级,因为群聊 id 是固定的。下面介绍在浏览器中打开多个聊天室,各个聊天室之间有不同的 id ,各个聊天室之前互不干扰。

多聊天室

实现思路是通过 http get 传参将群号传给视图函数,视图函数给 index.html 页面,在 index 页面构造 websocket url 并加入群号,在 consumer 中获取群号,并替换群号为原先的固定群号。

实现:

视图函数传参:

def index(request):
    QQ_number = request.GET.get('qq')
    return render(request, 'index.html', {'QQ_number': QQ_number})

index 页面 websocket 传参:

socket = new WebSocket("ws://127.0.0.1:8080/room/{{ QQ_number }}/")

routings 中正则接收参数:

websocket_urlpatterns = [
    re_path(r'^room/(?P<group>\w+)/$', consumers.ChatConsumer.as_asgi()),
]

consumer 中接收 group 并修改群号为 group:

from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from asgiref.sync import async_to_sync

class ChatConsumer(WebsocketConsumer):
    print("进入消费者")
    def websocket_connect(self,message):
        print("发送连接请求")
        self.accept()
        group = self.scope['url_route']['kwargs'].get('group')
        # 将这个客户端的连接对象加入到某个地方(内存或redis),channel_name 随机给个名字
        async_to_sync(self.channel_layer.group_add)(group,self.channel_name)
    def websocket_receive(self, message):
        group = self.scope['url_route']['kwargs'].get('group')
        res = message["text"]
        # 收数据message
        print("接收消息-->",res)
        async_to_sync(self.channel_layer.group_send)(group,{"type":"send_to","message":message})
    def send_to(self,event):
        # 群中每一个连接对象都发送
        text = event["message"]["text"]
        self.send(text)
    # 调用self.close()方法默认都会调用下面这个函数
    def websocket_disconnect(self, message):
        group = self.scope['url_route']['kwargs'].get('group')
        # 浏览器关闭也会自动发送断开链接请求
        print("断开连接")
        async_to_sync(self.channel_layer.group_discard)(group,self.channel_name)
        # 服务端同意断开连接
        raise StopConsumer()

结果:

这样即能实现多聊天室,各个聊天室互不打扰。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2323510.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何快速解决django报错:cx_Oracle.DatabaseError: ORA-00942: table or view does not exist

我们在使用django连接oracle进行编程时&#xff0c;使用model进行表映射对接oracle数据时&#xff0c;默认表名组成结构为&#xff1a;应用名_类名&#xff08;如&#xff1a;OracleModel_test&#xff09;&#xff0c;故即使我们库中存在表test&#xff0c;运行查询时候&#…

本地安装git

下载git 通过官网 下载 &#xff1a;Git - Downloading Package 若此页面无法直达&#xff0c;请删掉download/win尝试 2.双击运行安装 选择安装目录&#xff1a; 选择配置&#xff0c;默认不动 git安装目录名 默认即可 Git 的默认编辑器&#xff0c;建议使用默认的 Vim 编辑器…

小程序内表格合并功能实现—行合并

功能介绍&#xff1a;支付宝小程序手写表格实现行内合并&#xff0c;依据动态数据自动计算每次需求合并的值&#xff0c;本次记录行内合并&#xff0c;如果列内合并&#xff0c;同理即可实现 前端技术&#xff1a;grid布局 display&#xff1a;grid 先看实现效果: axml&…

SSE协议介绍和python实现

概述&#xff1a; SSE&#xff08;Server-Sent Events&#xff09;协议是一种允许服务器向客户端实时推送更新的技术&#xff0c;基于HTTP协议&#xff0c;常用于实时数据推送特点&#xff1a; 单向通信&#xff1a;服务器向客户端推送数据&#xff0c;客户端无法发送数据。基…

甘肃旅游服务平台+论文源码视频演示

4 系统设计 4.1系统概要设计 甘肃旅游服务平台并没有使用C/S结构&#xff0c;而是基于网络浏览器的方式去访问服务器&#xff0c;进而获取需要的数据信息&#xff0c;这种依靠浏览器进行数据访问的模式就是现在用得比较广泛的适用于广域网并且没有网速限制要求的小程序结构&am…

WebRTC中音视频服务质量QoS之FEC+NACK调用流程

WebRTC中音视频服务质量QoS之FECNACK调用流程 WebRTC中音视频服务质量QoS之FECNACK调用流程 WebRTC中音视频服务质量QoS之FECNACK调用流程前言一、WebRTC中FEC基础原理1. FEC基础操作 异或操作XOR2、 FEC中 行向和纵向 计算3、 WebRTC中 媒体包分组和生成FEC的包数① kFecRateT…

神经网络知识点整理

目录 ​一、深度学习基础与流程 二、神经网络基础组件 三、卷积神经网络&#xff08;CNN&#xff09;​编辑 四、循环神经网络&#xff08;RNN&#xff09;与LSTM 五、优化技巧与调参 六、应用场景与前沿​编辑 七、总结与展望​编辑 一、深度学习基础与流程 机器学习流…

远程办公新体验:用触屏手机流畅操作电脑桌面

在数字化浪潮的推动下&#xff0c;远程办公已从“应急选项”转变为职场常态。无论是居家隔离、差旅途中&#xff0c;还是咖啡厅临时办公&#xff0c;高效连接公司电脑的需求从未如此迫切。然而&#xff0c;传统的远程控制软件常因操作复杂、画面卡顿或功能限制而影响效率。如今…

【面试八股】:常见的锁策略

常见的锁策略 synchronized &#xff08;标准库的锁不够你用了&#xff09;锁策略和 Java 不强相关&#xff0c;其他语言涉及到锁&#xff0c;也有这样的锁策略。 1. 悲观锁&#xff0c;乐观锁&#xff08;描述的加锁时遇到的场景&#xff09; 悲观锁&#xff1a;预测接下来…

【python】OpenCV—Hand Detection

文章目录 1、功能描述2、代码实现3、效果展示4、完整代码5、参考6、其它手部检测和手势识别的方案 更多有趣的代码示例&#xff0c;可参考【Programming】 1、功能描述 基于 opencv-python 和 mediapipe 进行手部检测 2、代码实现 导入必要的库函数 import cv2 import media…

Flink中聚合算子介绍

前言 在flink api中&#xff0c;聚合算子是非常常用的。所谓的聚合就是在分组的基础上做比较计算的操作。下面通过几个简单案例来说明聚合算子的用法和注意事项。 聚合算子案例 因为flink的api操作流程比较固定&#xff0c;从获取执行环境》获取数据源》执行数据转换操作》输…

【基础】Windows 中通过 VSCode 使用 GCC 编译调试 C++

准备 安装 VSCode 及 C 插件。通过 MSYS2 安装 MinGW-w64 工具链&#xff0c;为您提供必要的工具来编译代码、调试代码并配置它以使用IntelliSense。参考&#xff1a;Windows 中的 Linux 开发工具链 验证安装&#xff1a; gcc --version g --version gdb --version三个核心配…

知识就是力量——物联网应用技术

基础知识篇 一、常用电子元器件1——USB Type C 接口引脚详解特点接口定义作用主从设备关于6P引脚的简介 2——常用通信芯片CH343P概述特点引脚定义 CH340概述特点封装 3——蜂鸣器概述类型驱动电路原文链接 二、常用封装介绍贴片电阻电容封装介绍封装尺寸与功率关系&#xff1…

(windows)conda虚拟环境下open-webui安装与启动

一、创建conda环境 重点强调下&#xff0c;如果用python pip安装&#xff0c;一定要选择python3.11系列版本&#xff0c;我选的3.11.9。 如果你的版本不是这个系列&#xff0c;将会出现一些未知的问题。 conda create -n open-webui python3.11 -y如下就创建好了 二、安装o…

资本运营:基于Python实现的资本运作模拟

基于Python实现的一个简单的资本运营框架&#xff1b; ​企业生命周期演示&#xff1a;观察初创→成长→上市→并购全流程 ​行业对比分析&#xff1a;不同行业的财务特征和估值差异 ​资本运作策略&#xff1a;体验IPO定价、投资决策、并购整合等操作 ​市场动态观察&#xff…

当EFISH-SBC-RK3576遇上区块链:物联网安全与可信数据网络‌

在工业物联网场景中&#xff0c;设备身份伪造与数据篡改是核心安全隐患。‌EFISH-SBC-RK3576‌ 通过 ‌硬件安全模块 区块链链上验证‌&#xff0c;实现设备身份可信锚定与数据全生命周期加密&#xff0c;安全性能提升10倍以上。 1. 安全架构&#xff1a;从芯片到链的端到端防…

分布式系统面试总结:3、分布式锁(和本地锁的区别、特点、常见实现方案)

仅供自学回顾使用&#xff0c;请支持javaGuide原版书籍。 本篇文章涉及到的分布式锁&#xff0c;在本人其他文章中也有涉及。 《JUC&#xff1a;三、两阶段终止模式、死锁的jconsole检测、乐观锁&#xff08;版本号机制CAS实现&#xff09;悲观锁》&#xff1a;https://blog.…

【VSCode的安装与配置】

目录&#xff1a; 一&#xff1a;下载 VSCode二&#xff1a;安装 VSCode三&#xff1a;配置 VSCode 一&#xff1a;下载 VSCode 下载地址&#xff1a;https://code.visualstudio.com/download 下载完成之后&#xff0c;在对应的下载目录中可以看到安装程序。 二&#xff1a;安装…

脱围机制-react18废除forwardRef->react19直接使用ref的理解

采用ref&#xff0c;可以在父组件调用到子组件的功能 第一步&#xff1a;在父组件声明ref并传递ref interface SideOptsHandle {refreshData: () > Promise<void> }const sideOptsRef useRef<SideOptsHandle>(null) // 创建 ref<SideOpts ref{sideOptsRef…

Windows中安装git工具

下载好git安装包 点击next 选择安装目录 根据需要去勾选 点击next 点击next PATH环境选择第二个【Git...software】即可&#xff0c;再点击【Next】。 第一种配置是“仅从Git Bash使用Git”。这是最安全的选择&#xff0c;因为您的PATH根本不会被修改。您只能使用 Git Bash 的…