websocket在django中的运用

14-2 聊天室实现思路:轮训、长轮训、websocket_哔哩哔哩_bilibili  参考大佬的B站学习笔记

https://www.cnblogs.com/wupeiqi/p/6558766.html    参考博客

https://www.cnblogs.com/wupeiqi/articles/9593858.html  参考博客


http协议: 是短连接,无状态的,一次性的,无法保证实时信息交互

  • 客户端主动连接服务器
  • 客户端向服务端发送消息,服务端接收到返回数据
  • 客户端接收到数据
  • 端口连接

websock协议:创建持久的连接不断开,基于这个连接进行收发数据

  • 实时响应:接收发送消息
  • 实时图表,柱状图,饼图

websocket 原理:

  • 连接,客户端发起
  • 握手,客户端发送一个消息,后端接收到消息再做一些特殊处理返回(服务端要支持websocket协议)
  • 收发数据(加密)
  • 断开连接

握手流程:

        1.客户端向服务端发送

GET /chatsocket HTTP/1.1
Host: 127.0.0.1:8002
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://localhost:63342
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

         2.服务端接收:

请求和响应的【握手】信息需要遵循规则:

    从请求【握手】信息中提取 Sec-WebSocket-Key
    利用magic_string 和 Sec-WebSocket-Key 进行hmac1加密,再进行base64加密
    将加密结果响应给客户端

注:magic string固定为:258EAFA5-E914-47DA-95CA-C5AB0DC85B11

 返回数据给客户端浏览器,验证通过则完成握手

HTTP/1.1 101 Switching Protocols
      Upgrade:websocket
      Connection: Upgrade
      Sec-WebSocket-Accept: 密文

 收发数据流程:

数据 b'asdfa;efawe;sdfas;awdfawea;sdfasdfaf;sdfasdfa;'

先获取第二个字节,8位  10001010

再获取第二个字节的后七位   0001010  -> payload len

  • =127  2个字节,8个字节      其他字节(4字节 masking key + 数据)
  • =126  2个字节,8个字节      其他字节(4字节 masking key + 数据)
  • <=125  2个字节                     其他字节(4字节 masking key + 数据)
  • 获取masking key,然后对数据进行解密
    • var DECODED = "";
      for (var i = 0; i < ENCODED.length; i++) {
          DECODED[i] = ENCODED[i] ^ MASK[i % 4];
      }

实时交互的解决方案:

  1. 轮训,浏览器每隔一段时间向后台发送一次请求。缺点:有延迟、请求太多网站压力大
  2. 长轮询,客户端向服务端发送请求,保持一定的时间,一旦有数据就立即返回。特点:数据无延迟,常应用于大平台、WebQQ、Web微信
  3. websocket,客户端和服务端创建连接不断开,可以实现双向通信。特点:旧版浏览器不支持

长轮询实现群聊功能

  • 访问url进入聊天室页面,为每个用户创建一个队列
  • 点击发送内容,数据发送到后台,给到每个人的队列中
  • 递归获取消息,去队列中获取数据,展示在页面

前端

<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="closeConn()">-->
</div>
<script>
    USER_ID = "{{uid}}";
    function sendMessage(){
        var text=$('#txt').val();
        // 基于ajax将用户文本信息发送到后台
        $.ajax({
            url:'/send/msg/',
            data:{text:text},
            type: 'GET',
            dataType:'JSON',
            success: function (res){
                console.log('请求发送成功',res)
                //超时,没有新数据
                // 有数据,立即展示
                // if (res.status){
                //     var tag =$("<div>");
                //     tag.text(res.data)
                //     $("#message").appendImage(tag);
                // }
            }
        })
    }
    function getMessage(){
        $.ajax({
            url:'/get/msg/',
            data:{uid:USER_ID},
            type: 'GET',
            dataType:'JSON',
            success: function (res){
                //超时,没有新数据
                // 有数据,立即展示
                if (res.status){
                    var tag =$("<div>");
                    tag.text(res.data)
                    $("#message").appendImage(tag);
                    }
                getMessage(); // 递归调用该函数
            }
        })
    }
    $(function (){
        getMessage();
    })
</script>
</body>

后端

# view 视图
import queue
from django.shortcuts import render,HttpResponse
from django.http import request,JsonResponse
USER_QUEUE = {}
def index(request):
    qq_number = request.GET.get('num')
    return render(request,'index.html',{"qq_number":qq_number})

def home(request):
    uid = request.GET.get('uid')
    USER_QUEUE[uid]=queue.Queue()
    return render(request,'home.html',{'uid':uid})

def send_msg(request):
    text =request.GET.get('text')
    for uid,q in USER_QUEUE.items():
        q.put(text)
    # print("接收到客户端的请求:"+request.GET)
    return HttpResponse("ok")

def get_msg(request):
    # 去自己的队列中获取数据
    uid = request.GET.get('uid')
    q  = USER_QUEUE[uid]
    result = {'status':True,'data':None}
    try:
        data = q.get(timeout=10)
        result['data']=data
    except queue.Empty as e:
        result['status']=False
    return JsonResponse(result)


# url
path('home/', home),
path('send/msg/', send_msg),
path('get/msg/', get_msg),

django 中配置websocket

pip install channels  # 安装组件

# 注册app
'channels'  

# 配置asgi.application
ASGI_APPLICATION = 'web.asgi.application'

更新asgi文件(在支持http的基础上支持websocket)

# asgi.py

import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter,URLRouter
from . import routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'web.settings')

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

创建routing文件在setting同级目录

from django.urls import re_path

from app import consumers

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

创建app目录下consumer文件

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

"""
wsgi: 同步
asgi: 异步+asgi+websocket
"""
class ChatConsumer(WebsocketConsumer):
    def websocket_connect(self, message):
        # 客户端向后端发送websocket连接请求时自动触发

        # 容许和客户端创建连接
        self.accept()

    def websocket_receive(self, message):
        # 浏览器基于websocket向后端发送数据,自动触发接收消息
        print(message)
        self.send('不要回复!!!')

    def websocket_disconnect(self, message):
        # 客户端与服务端断开时自动触发
        raise StopConsumer

运行结果:

注意: 当启动服务器是 Starting development server 而非ASGI服务时要检查channel版本,较高的版本可能不适配django,经调试发现3.0.1版本适配,重新安装channels即可

pip install channels==3.0.1

 Pycharm 的html 注释小技巧!

setting --> Template Languages --> None

 websocket 收发消息流程

  • 访问地址看到聊天室的页面
  • 客户端主动向服务端发送websocket连接,服务端接收到通过(完成握手)
  • 客户端,执行websocket连接动作
    // http://www.baidu.com
    // ws://www.baidu.com 注释下面 ws://xxx的使用
    socket = new WebSocket("ws://127.0.0.1:8000/ws/123/");
  • 服务端
    def websocket_connect(self, message):
        # 客户端向后端发送websocket连接请求时自动触发
        # 容许和客户端创建连接(握手)
        print("有人来连接了")
        self.accept()
  • 收发消息(客户端向服务端发送消息)
    • 客户端
      <input type="button" value="发送" οnclick="sendMessage()">
      // 获取输入框的信息进行发送
      function sendMessage(){
          let tag =document.getElementById('txt');
          socket.send(tag.value);
      }
    • 服务端
      def websocket_receive(self, message):
          # 浏览器基于websocket向后端发送数据,自动触发接收消息
          txt  = message['text']
          print('收到消息-->', txt)
          self.send(txt+'哈哈') # 在客户端发送消息的基础上加上字段 "哈哈"
      
    • 收发消息(服务端主动发给客户端)
      • 服务端
        
        def websocket_connect(self, message):
            # 客户端向后端发送websocket连接请求时自动触发
            # 容许和客户端创建连接(握手)
            print("有人来连接了")
            self.accept()
            # 服务端向客户端发送消息
            self.send('来了呀年轻人!')
        
      • 客户端
        // 当websocket接收到服务端发来的消息时,自动触发这个函数
        socket.onmessage = function (event){
            var tag= document.createElement('div');
            tag.innerText = event.data;
            console.log(tag.textContent);
        }

 运行结果:

前端页面的实现及DOM函数的触发

<div class="message" id="message"></div>
<div>
    <input type="text" placeholder="请输入" id="txt">
    <input type="button" value="发送" onclick="sendMessage()">
    <input type="button" value="关闭连接" onclick="closeConn()">
</div>
<script>
    // http://www.baidu.com
    // ws://www.baidu.com 注释下面 ws://xxx的使用
    socket = new WebSocket("ws://127.0.0.1:8000/ws/123/");

    // 创建好连接之后自动触发,即当执行self.accept()
    socket.onopen = function (event){
        let tag= document.createElement('div');
        tag.innerText = '[连接成功]';
        console.log(tag.textContent)
        document.getElementById('message').appendChild(tag);
    }

    // 当websocket接收到服务端发来的消息时,自动触发这个函数
    socket.onmessage = function (event){
        let tag= document.createElement('div');
        tag.innerText = event.data;
        // console.log(tag.textContent);
        document.getElementById('message').appendChild(tag);
    }

    // 断开连接时触发
    socket.onclose =function (event){
        let tag= document.createElement('div');
        tag.innerText = '[断开连接]';
        console.log(tag.textContent)
        document.getElementById('message').appendChild(tag);
    }

    // 获取输入框的信息进行发送
    function sendMessage(){
        let tag =document.getElementById('txt');
        socket.send(tag.value);
    }
    // 关闭连接
    function closeConn(){
        socket.close(); // 向服务端发送断开连接的请求
    }
</script>

群聊功能的实践:

方法1: 方法笨重,可行性差,不便于接口

CONN_LIST = []
def websocket_connect(self, message):
    # 客户端向后端发送websocket连接请求时自动触发
    self.accept()
    CONN_LIST.append(self)
def websocket_receive(self, message):
    txt  = message['text']
    for conn in CONN_LIST:
        conn.send(txt)
def websocket_disconnect(self, message):
    CONN_LIST.remove(self)
    raise StopConsumer  # 容许断开连接

Channel Layers实现群聊

方法2:基于channel中提供channel layers来实现-主要流程

  • 配置setting channel layers

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

  • 更新view
    qq_number = request.GET.get('num')
  • 更新收发后端consumer.py
    def websocket_connect(self, message):
        # 客户端向后端发送websocket连接请求时自动触发
        self.accept()
        # 获取群号即路由匹配的数值
        group = self.scope['url_route']['kwargs'].get('group') # 固定用法
        # 将客户端的连接对象加入到某个地方 redis or 内存
        async_to_sync(self.channel_layer.group_add)(group,self.channel_name)
        # 服务端向客户端发送消息
        self.send('来了呀年轻人!')
    def websocket_receive(self, message):
        # 获取群号即路由匹配的数值
        group = self.scope['url_route']['kwargs'].get('group') # 固定用法
        # 通知组内的所有客户端,执行 xx_oo 方法,在此方法中自定义功能
        async_to_sync(self.channel_layer.group_send)(group, {'type':'xx.oo','message':message})
    def xx_oo(self,event):
        text = event['message']['text']
        self.send(text)
    def websocket_disconnect(self, message):
        group = self.scope['url_route']['kwargs'].get('group') # 固定用法
        async_to_sync(self.channel_layer.group_discard)(group,self.channel_name)
    
        # 客户端与服务端断开时自动触发
        raise StopConsumer  # 容许断开连接

运行结果:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode】每日一题:使二叉树所有路径值相等的最小代价

该题采用自底向上的思路的话&#xff0c;很容易想到使用贪心的思想&#xff0c;但是如何进行具体操作却有些难度。 这里补充一个重要的结论&#xff1a;二叉树的数组形式中&#xff0c;第i个节点的父节点是i/2&#xff1b;接下来只需要让自底向上让每个路径上的代价保持最低限…

Python实现自动检测设备连通性并发送告警到企业微信

背景&#xff1a;门禁机器使用的WiFi连接&#xff0c;因为某些原因会不定期自动断开连接&#xff0c;需要人工及时干预&#xff0c;以免影响门禁数据同步&#xff0c;故写此脚本&#xff0c;定时检测门禁网络联通性。 #首次使用要安装tcping模块 pip install tcpingfrom tcpin…

Jupyter Notebook 下载+简单设置

这里写目录标题 1. Jupyter Notebook安装2.切换打开别的盘3. 创建代码文件4.为jupyter notebook添加目录 (Jupyter安装拓展nbextensions)step1&#xff1a;安装命令step2&#xff1a;用户配置step3&#xff1a;上述过程均完成后&#xff0c;打开jupyter notebook就会发现界面多…

静态方式部署集中式网关

在静态方式部署集中式网关的场景中&#xff0c;控制平面的流程包括VXLAN隧道建立、MAC地址动态学习&#xff1b;转发平面的流程包括同子网已知单播报文转发、同子网BUM&#xff08;Broadcast&Unknown-unicast&Multicast&#xff09;报文转发、跨子网报文转发。 静态方…

qml 项目依赖

文章目录 出现的问题最终对比下一步 把 apptestQml3_6.exe 放到一个单独目录下&#xff0c;执行 windeployqt.exe ./apptestQml3_6.exe但是出了很多问题&#xff0c;根本运行不起来。 但是在release目录下执行下&#xff0c;程序能跑起来。 根据错误提示&#xff0c;进行添加。…

vue3+vite+ts配置多个代理并解决报404问题

之前配置接口代理总是报404,明明接口地址是对的但还是报是因数写法不对;用了vue2中的写法 pathRewrite改为rewrite 根路径下创建env文件根据自己需要名命 .env.development文件内容 # just a flag ENVdevelopment# static前缀 VITE_APP_PUBLIC_PREFIX"" # 基础模块…

php 支持mssqlserver

系统不支持:sqlsrv 需要一下几个环节 1.准备检测php版本 查看 VC 版本 查看操作系统位数&#xff1a;X86(32位) 和X64 2.下载php的sqlserver库 extensionphp_sqlsrv_74_nts_x64.dll extensionphp_pdo_sqlsrv_74_nts_x64.dll extensionphp_sqlsrv_74_nts_x64 extensionphp_…

基于Vue的高校课程考勤成绩管理系统SpringBoot+nodejs+python

设计目标&#xff1a; 课程管理系统开发的目的是管理全校开设课程的基本信息&#xff0c;安排各班级的课程以及上课时间和教室。系统的使用对象包括教务,学生、教师、管理员等。通过对日常课程管理工作的分析&#xff0c;可以将课程管理系统的功能分为下面几个方面&#xff1a…

C++ //练习10.3 用accumulate求一个vector<int>中的元素之和。

C Primer&#xff08;第5版&#xff09; 练习 10.3 练习10.3 用accumulate求一个vector中的元素之和。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /*******************************************************************…

DataGrip2023配置连接Mssqlserver、Mysql、Oracle若干问题解决方案

1、Mssqlserver连接 本人连的是Sql2008&#xff0c;默认添加时&#xff0c;地址、端口、实例、账号、密码后&#xff0c;测试连接出现错误。 Use SSL&#xff1a;不要勾选 VM option&#xff1a;填写&#xff0c;"-Djdk.tls.disabledAlgorithmsSSLv3, RC4, DES, MD5withR…

(Linux学习二)文件管理基础操作命令笔记

Linux目录结构&#xff1a; bin 二进制文件 boot 启动目录 home 普通用户 root 超管 tmp 临时文件 run 临时运行数据 var 日志 usr 应用程序、文件 etc 配置文件 dev 文件系统 一、基础操作 在 Linux 终端中&#xff0c;你可以使用以下命令来清屏&#xff1a; clear 命令&am…

HW高水位问题及解决办法

一、问题描述及分析 应用业务反馈应用响应缓慢。登录数据库检查&#xff0c;发现数据库响应慢&#xff0c;有大量enq:HW–contention等待事件。结合awr报告和ash报告&#xff0c;发现整体等待时间消耗在推高水位线征用上&#xff0c;如下awr top事件&#xff1a;Ash消耗也是en…

数据恢复软件有哪些?分享10款好用的数据恢复软件

在数字化时代&#xff0c;数据的安全性和可恢复性变得至关重要。由于各种原因&#xff0c;如设备故障、误删、病毒攻击等&#xff0c;我们可能会面临数据丢失的风险。为了应对这种情况&#xff0c;市场上涌现出许多数据恢复软件。下面给大家分享10个好用的数据恢复软件&#xf…

[golang] 25 图片操作

用 “github.com/fogleman/gg” 可以画线, 框 用 “github.com/disintegration/imaging” 可以变换颜色 一 渲染 1.1 框和字 import "github.com/fogleman/gg"func DrawRectangles(inPath string, cRects []ColorTextRect, fnImgNameChange FnImgNameChange) (str…

C语言数据结构基础-单链表

1.链表概念 在前面的学习中&#xff0c;我们知道了线性表&#xff0c;其中逻辑结构与物理结构都连续的叫顺序表&#xff0c;那么&#xff1a; 链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。 2.链表组…

线性稳压器电路,用于各种电视机、收录机、电子仪器、设备的稳压电源上,内置短路保护电路,热保护电路——78MXX

78MXX系列是用于各种电视机、收录机、电子仪器、设备的稳压电源电路。包括78M05、78M06、 78M08、 78M09、 78M10、 78M12、 78M15。 主要特点&#xff1a; ● 极限输出电流: 0.5A ● 固定输出电压: 5V、6V、8V、9V、10V、 12V、 15V ● 内置短路保护电路 ● 内置热保护电路 …

云原生精品资料合集(附下载)

云计算是产业数字化转型的关键基础设施,以基础设施资源为中心的云搬迁时代接近尾声&#xff0c;以应用价值为中心的云原生时代已经到&#xff0c;所以IT人员学习云原生正当时&#xff01;最近跟各位大神征集了云原生的教程&#xff0c;行业报告和最佳实践&#xff0c;总有一款适…

python web框架fastapi模板渲染--Jinja2使用技巧总结

文章目录 1.jinja2模板1.1、jinja2 的变量1.1.1 列表类型数据渲染1.1.2 字典类型数据渲染 2. jinja2 的过滤器3. jinja2 的控制结构3.1、分支控制3.2、循环控制 1.jinja2模板 要了解jinja2&#xff0c;那么需要先理解模板的概念。模板在Python的web开发中⼴泛使⽤&#xff0c;…

Unity 常用操作

2D素材网站 https://craftpix.net/ https://itch.io/game-assets/tag-2d/tag-backgrounds 3D素材资源网址 https://www.mixamo.com/#/ 场景常用操作&#xff1a; 快捷键&#xff1a;QWER Q&#xff1a;Q键或鼠标中键&#xff0c;可以拉动场景。 W&#xff1a;选中物体后&…

【QT+QGIS跨平台编译】之五十六:【QGIS_CORE跨平台编译】—【qgsmeshcalclexer.cpp生成】

文章目录 一、Flex二、生成来源三、构建过程一、Flex Flex (fast lexical analyser generator) 是 Lex 的另一个替代品。它经常和自由软件 Bison 语法分析器生成器 一起使用。Flex 最初由 Vern Paxson 于 1987 年用 C 语言写成。 “flex 是一个生成扫描器的工具,能够识别文本中…