Django使用WebSocket

1、websocket 相关

实现一个系统,20 个用户同时打开网站,呈现出来一个群聊界面

解决方案

  • 轮询:让浏览器每隔2s向后台发送一次请求,缺点:延迟,请求太多网站压力大

  • 轮询:客户端向服务端发送请求,服务器最多宕20s,一旦有数据接入,就立即返回。数据的响应没有延迟时间。

  • websocket:客户端和服务端创建连接后,不断开,实现双向通信

在这里插入图片描述

轮询

  • 访问 /home/ 显示的聊天室界面

  • 点击发送内容,数据可以发送到后台

  • 定时获取消息,发送到前端

长轮询

在这里插入图片描述

  • 访问/home/ 显示聊天界面, → 每个用户创建一个队列

  • 点击发送内容,数据也可以发送到后台 → 扔到每个用户的队列中

  • 递归获取消息, 去自己的队列中获取数据,然后展示在界面中。

问题:

  • 服务端持有连接,压力是否会很大?

    如果基于IO多复用 + 异步,还会有这种情况吗? 可以

  • 如果后台有100线程,同时对应100个用户的请求,则在等待期间(假设10s),这100个用户则一直占用县城,如果有更多的用户请求,则需等待释放。

webSocket

原来的web中:

  • http协议: 无状态 & 短连接

    • 客户端主动连接服务端。

    • 客户端向服务端发送消息,服务端接收后,返回数据

    • 客户端接收到数据

    • 断开连接

  • https协议 = http协议 + 对数据进行加密

我们在开发过程中,想要保留一些状态信息,基于Cookie来做

现在支持:

  • http协议:一次请求一次响应

  • websocket协议: 创建持有的连接不断开,基于这个连接可以进行收发数据。【服务端向客户端主动推送消息】

    • web聊天室

    • 实时图标,柱状图、饼图(echarts)

WebSocket原理

  • http协议

    • 连接

    • 数据传输

    • 断开连接

  • websocket 协议 → 建立在 http 协议之上

    • 连接, 客户端发出请求

    • 握手(验证), 客户端发送一段消息,后端接收到消息后,做一些特殊处理并返回。服务端支持 websocket 协议

      https://www.cnblogs.com/wupeiqi/p/6558766.html

  • 客户端向服务端发送握手信息

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
...
...
  • 服务端接收
    • 接收后的加密过程
// Sec-WebSocket-Key 与 magic String 进行拼接
Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg==  
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
v1 = "mnwFxiOlctXFN/DeMt1Amg==" +  '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'

v2 = hmac1(v1)  // 通过 hmac1 进行加密
v3 = base64(v2) // 通过 base64 加密
  • 返回数据
HTTP/1.1 101 Switching Protocols
Upgrade:websocket
Connection: Upgrade
Sec-WebSocket-Accept: 密文
  • 收发数据(加密)

    • 先获取第 2 个字节,对应 8 位

    • 在获取第二个字节的后 7 位

  • 断开连接

Django 框架实现 WebSocket

Django 默认不支持WebSocket,安装第三方组件

pip install channels

版本不能超过4.0,最好是3.0.5,不然不能成功启动asgi

配置:

django channels - 武沛齐 - 博客园

  • 注册 channels
	INSTALLED_APPS = [
	    'django.contrib.admin',
	    'django.contrib.auth',
	    'django.contrib.contenttypes',
	    'django.contrib.sessions',
	    'django.contrib.messages',
	    'django.contrib.staticfiles',
	    'channels',
	    'app01.apps.App01Config'
	]
  • 在 settings.py 中添加 asgi_application
	ASGI_APPLICATION = 'ws_demo.asgi.application'
  • 修改 asgi.py文件
	import os
	
	from django.core.asgi import get_asgi_application
	from channels.routing import ProtocolTypeRouter, URLRouter
	from ws_demo import routings
	
	os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ws_demo.settings')
	
	# application = get_asgi_application()
	
	# 支持 http 和 WebSocket 请求
	application = ProtocolTypeRouter({
	    "http": get_asgi_application(),  # 自动找 urls.py , 找视图函数  --》 http
	    "websocket": URLRouter(routings.websocket_urlpatterns),  # routing(urls)、 consumers(views)
	})
  • 在 settings.py同级目录下,创建routings.py
from django.urls import re_path

from app01 import consumers

websocket_urlpatterns = [
    # 示例 url : xxxxx/room/x1/
    re_path(r"room/(?P<group>\w+)/$", consumers.ChatConsumer.as_asgi())
]
  • 在 app01 目录下,创建consumers.py ,用于设置 WebSocket 请求
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer


class ChatConsumer(WebsocketConsumer):  # 继承WebsocketConsumer
   def websocket_connect(self, message):
       print("有人进行连接了。。。。")
       # 有客户端向后端发送 WebSocket 连接的请求时,自动触发(握手)
       self.accept()

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

   def websocket_disconnect(self, message):
       # 客户端向服务端断开连接时,自动触发
       print("连接断开!!")
       raise StopConsumer()

django 中,需要了解:

  • wsg:

在这里插入图片描述

  • asgi: wsgi + 异步 + WebSocket

在这里插入图片描述

聊天室

  • 访问地址看到聊天室界面,使用 http 请求

  • 让客户端主动向服务端发起 websocket连接,服务端接收到连接后,进行握手

    • 客户端向后台发布WebSocket请求

      var socket = new WebSocket("ws://localhost:8000/room/123/")
      
    • 服务端接收消息

         from channels.generic.websocket import WebsocketConsumer
         from channels.exceptions import StopConsumer
         
         
         class ChatConsumer(WebsocketConsumer):  # 继承WebsocketConsumer
             def websocket_connect(self, message):
                 print("有人进行连接了。。。。")
                 # 有客户端向后端发送 WebSocket 连接的请求时,自动触发(握手)
                 self.accept()
      
  • 收发消息(客户端向服务端发消息)

    • 客户端发送消息
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Title</title>
   <style>
       .message {
           height: 300px;
           border: 1px solid #ddd;
           width: 100%;
       }
   </style>
</head>
<body>
<div class="message" id="message"></div>
<div>
   <label>
       <input type="text" placeholder="请输入" id="txt">
   </label>
   <input type="button" value="发送" οnclick="sendMessage()">
</div>
<script>
   // 创建websocket对象,向后台发送请求
   let socket = new WebSocket("ws://localhost:8000/room/123/");

   function sendMessage(){
       let tag = document.getElementById("txt");
       socket.send(tag.value);
   }

</script>
</body>
</html>
  • 服务端接收消息
def websocket_receive(self, message):
 # 浏览器基于 WebSocket 向后端发送数据,自动触发接收消息
 text = message["text"]
 print("接收到的消息为:", text)
  • 收发消息(服务端主动发给客户端)
# 连接之后,服务端给客户端发送消息
self.send("来了啊 !!!")
// 回调函数,客户端接收服务端消息
socket.onmessage = function (event){
  console.log(event.data)
}

其他方法

// 创建websocket对象,向后台发送请求
let socket = new WebSocket("ws://localhost:8000/room/123/");

// 当客户端和服务端刚创建好连接(self.accept)之后,自动触发.
socket.onopen = function (event){
  let tag = document.createElement("div");
  tag.innerText = "[连接成功]";
  document.getElementById("message").appendChild(tag);
}

// 回调函数,客户端接收服务端消息
socket.onmessage = function (event){
  let tag = document.createElement("div");
  tag.innerText = event.data;
  document.getElementById("message").appendChild(tag);
}

// 当断开连接时,触发该函数
socket.onclose =function (event){
  let tag = document.createElement("div");
  tag.innerText = "[连接断开]";
  document.getElementById("message").appendChild(tag);
}

function sendMessage(){
  let tag = document.getElementById("txt");
  socket.send(tag.value);
}

function closeMessage(){
  socket.close();
}

function handleKeyPress(event){
  if (event.keyCode === 13){
    document.getElementById("send").click();
    document.getElementById("txt").value = "";
  }
}

document.onkeydown = handleKeyPress;
    def websocket_connect(self, message):
        print("有人进行连接了。。。。")
        # 有客户端向后端发送 WebSocket 连接的请求时,自动触发(握手)
        self.accept()

        # 连接之后,服务端给客户端发送消息
        self.send("来了啊 !!!")

    def websocket_receive(self, message):
        # 浏览器基于 WebSocket 向后端发送数据,自动触发接收消息
        text = message["text"]
        print("接收到的消息为:", text)
        # 当接收的值为【关闭】,则服务端关闭连接
        if text == "close":
            self.close()
            return
        else:
            self.send(text + "NB")

    def websocket_disconnect(self, message):
        # 客户端向服务端断开连接时,自动触发
        print("断开连接!!!")
        # 当客户端断开连接时,服务端也需关闭与客户端的连接,连接是双向的
        raise StopConsumer()

小结

现在的交互还是停留在对某个人的操作

群聊

基于 channels 中提供的channel layers 来实现

  • settings 中配置
# 声明基于内存的 channel layers
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels.layers.InMemoryChannelLayer"
    }
}

也可声明基于 redis 的 channel layer → pip install channels-redis

# 基于redis 内存的 channel layers
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": ["redis://10.211.55.25:6379/1"]
        }
    }
}
  • consumers 中特殊的代码
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from asgiref.sync import async_to_sync


class ChatConsumer(WebsocketConsumer):  # 继承WebsocketConsumer
    def websocket_connect(self, message):
        # 接收客户端的连接
        self.accept()
        print("连接成功!!!")
        # 获取群号
        group_num = self.scope["url_route"]["kwargs"].get("group")

        # 将这个客户端的链接对象添加到某个地方(内存或者 redis)
        # self.channel_layer.group_add(group_num, self.channel_name)
        async_to_sync(self.channel_layer.group_add)(group_num, self.channel_name)


    def websocket_receive(self, message):
        # 浏览器基于 WebSocket 向后端发送数据,自动触发接收消息
        text = message["text"]
        print("接收到的消息为:", text)
        group_num = self.scope["url_route"]["kwargs"].get("group")
        print("group_num", group_num)
        # 通知组内的所有的客户端,执行 xx_oo方法,在方法中可以自定义任意的功能
        # self.channel_layer.group_send(group_num, {"type": "xx.oo", "message": message})
        async_to_sync(self.channel_layer.group_send)(group_num, {"type": "xx.oo", "message": message})

    def xx_oo(self, event):
        text = event["message"]["text"]
        print("发送的 text:", text)
        self.send(text)  # 给组中的每一个人去发送消息

    def websocket_disconnect(self, message):
        # 客户端向服务端断开连接时,自动触发
        print("断开连接!!!")
        group_num = self.scope["url_route"]["kwargs"].get("group_num")

        # self.channel_layer.group_discard(group_num, self.channel_name)
        async_to_sync(self.channel_layer.group_discard)(group_num, self.channel_name)
        raise StopConsumer()
        
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .message {
            height: 300px;
            border: 1px solid #ddd;
            width: 100%;
        }
    </style>
</head>
<body>
<div class="message" id="message"></div>
<div>
    <label>
        <input type="text" placeholder="请输入" id="txt">
    </label>
    <input type="button" value="发送" οnclick="sendMessage()" id="send">

    <input type="button" value="关闭链接" οnclick="closeMessage()">
</div>
<script>
    // 创建websocket对象,向后台发送请求
    let socket = new WebSocket("ws://localhost:8000/room/{{ group_num }}/");

    // 当客户端和服务端刚创建好连接(self.accept)之后,自动触发.
    socket.onopen = function (event){
        let tag = document.createElement("div");
        tag.innerText = "[连接成功]";
        document.getElementById("message").appendChild(tag);
    }

    // 回调函数,客户端接收服务端消息
    socket.onmessage = function (event){
        let tag = document.createElement("div");
        tag.innerText = event.data;
        document.getElementById("message").appendChild(tag);
    }

    // 当断开连接时,触发该函数
    socket.onclose =function (event){
        let tag = document.createElement("div");
        tag.innerText = "[连接断开]";
        document.getElementById("message").appendChild(tag);
    }

    function sendMessage(){
        let tag = document.getElementById("txt");
        socket.send(tag.value);
    }

    function closeMessage(){
        socket.close();
    }

    function handleKeyPress(event){
        if (event.keyCode === 13){
            document.getElementById("send").click();
            document.getElementById("txt").value = "";
        }
    }

    document.onkeydown = handleKeyPress;

</script>
</body>
</html>

总结

  • WebSocket 是什么? 协议

  • django 中实现 WebSocket, channels 组件

    • 单独连接和收发数据

    • 手动创建列表 & channel layers

运维&运维开发的同学,使用 WebSocket 实现代码发布系统项目

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

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

相关文章

DevOps(四)

CD(二) 1. CDStep 1 - 上传代码Step 2 - 下载代码Step 3 - 检查代码Step 4 - 编译代码Step 5 - 上传仓库Step 6 - 下载软件Step 7 - 制作镜像Step 8 - 上传镜像Step 9 - 部署服务2. 整体预览2.1 预览1. 修改代码2. 查看sonarqube检查结果3. 查看nexus仓库4. 查看harbor仓库5.…

Python数据分析实战-dataframe筛选某字段包含(模糊匹配)某些值的记录(附源码和实现效果)

实现功能 Python利用df[].str.contains()对dataframe筛选某字段包含&#xff08;模糊匹配&#xff09;某些值的记录 实现代码 import pandas as pddf {地址:[北京,上海,长沙,北京省会,广州市区],table:[user,student,course,sc,book]} df pd.DataFrame(df) print(df) print…

数据库字段变更监控平台设计开发

序&#xff1a; 在开发过程中&#xff0c;在值班解决客服问题时&#xff0c;在分析定位别人写的业务代码问题时&#xff0c;重点是不是自己写的代码&#xff0c;只看到了数据库中落库最终数据&#xff0c;并不知道业务逻辑问题发生时数据库表中当时数据情况&#xff1f;如果能知…

mybatis_使用注解开发

第一步&#xff1a;使用注解写一个接口 Select("select * from user")List<User> getUsers(); 第二步&#xff1a;绑定接口 第三步&#xff1a;测试 官方提示&#xff1a; 使用注解来映射简单语句会使代码显得更加简洁&#xff0c;但对于稍微复杂一点的语句&…

flask路由添加参数

flask路由添加参数 在 Flask 中&#xff0c;可以通过两种方式在路由中添加参数&#xff1a;在路由字符串中直接指定参数&#xff0c;或者通过 request 对象从请求中获取参数。 在路由字符串中指定参数&#xff1a;可以将参数直接包含在路由字符串中。参数可以是字符串、整数、…

【JVM】JVM执行流程 JVM类加载 垃圾回收机制等

目录 &#x1f337;1、JVM是什么&#xff1f; &#x1f337;2、JVM的执行流程&#xff08;能够描述数据区5部分&#xff09; &#x1f337;3、JVM类加载过程 &#x1f337;4、双亲委派机制&#xff1a;描述类加载的过程 问题1&#xff1a;类加载器 问题2&#xff1a;什么…

MyBatis学习笔记之逆向工程

文章目录 逆向工程配置与生成QBC查询风格 所谓的逆向工程是&#xff1a;根据数据库表逆向生成的Java的pojo类、SqlMapper.xml文件、以及mapper接口类等 要完成这个工作&#xff0c;需要借助别人写好的逆向工程插件。 虽然有点经典白学&#xff0c;但好像也没不白学 思考&#x…

经营简报及考核360表格

文章目录 经营简报效果图代码tableObjectSpanMethod.js 考核360委员会效果图 经营简报效果图不需要合并单元格且有汇总表头的 懒得封装了&#xff0c;所以整体没有封装 经营简报 效果图 代码 <template><el-tableref"tableRef":data"tableData.lengt…

【团队协作开发】将Gitee项目导入到本地IDEA中出现根目录不完整的问题解决(已解决)

前言&#xff1a;在团队协作开发过程中&#xff0c;通常我们的Gitee完整项目中会包含很多内容&#xff1a;后端代码、前端代码、项目结构图、项目文档等一系列资产。 将Gitee项目导入到本地IDEA中&#xff0c;通常会出现根目录不完整的问题。这是因为项目里面包含了后端代码、前…

DevOps(三)

CD(二) 1. 整体流程2. 环境准备1. jenkins安装2. 编译安装git3. docker安装4. docker-compose安装5. sonarqube安装6. harbor安装7. gitlab私服8. maven安装9. Nexus部署10. K8s部署3. 安装java及编写代码3.1 安装java3.2 安装IntelliJ IDEA3.3 安装tomcat3.4 安装maven3.5 c…

访问:http://localhost:8070/actuator/bus-refresh 问题

1、请求发送不出去 原因&#xff1a; 自己 config-server端 application.yml 配置的端口号是8888&#xff0c;访问server修改为配置的端口号 2、请求报错405 几个解决办法&#xff1a; 1、版本问题变为busrefresh 2、bus-refresh加单引号或双引号尝试 3、加配置尝试&#xff1a…

【MySQL】复合查询

目录 一、基本查询 1、查询工资高于500或岗位为MANAGER的雇员&#xff0c;同时还要满足他们的姓名首字母为大写的J 2、按照部门号升序而雇员的工资降序排序 3、使用年薪进行降序排序 4、显示工资最高的员工的名字和工作岗位 5、显示工资高于平均工资的员工信息 6、显示每…

【Lua学习笔记】Lua进阶——Table,迭代器

文章目录 官方唯一指定数据结构--tabletable的一万种用法字典和数组 迭代器ipairs()pairs() 回到Table 在【Lua学习笔记】Lua入门中我们讲到了Lua的一些入门知识点&#xff0c;本文将补充Lua的一些进阶知识 官方唯一指定数据结构–table 在上篇文章的最后&#xff0c;我们指出…

【飞书】飞书导出md文档 | 飞书markdown文档导出 | 解决飞书只能导出pdf word

一、飞书导出markdown github地址&#xff1a;https://github.com/Wsine/feishu2md 这是一个下载飞书文档为 Markdown 文件的工具&#xff0c;使用 Go 语言实现。 请看这里&#xff1a;招募有需求和有兴趣的开发者&#xff0c;共同探讨开发维护&#xff0c;有兴趣请联系。 二、…

Hive视图

hive的视图 简介 hive的视图简单理解为逻辑上的表hive只支持逻辑视图&#xff0c;不支持物化视图视图存在的意义 对数据进行局部暴露&#xff08;涉及隐私的数据不暴露&#xff09;简化复杂查询 创建视图&#xff1a; create view if not exists v_1 as select uid,movie f…

Esp32_Arduino接入腾讯云笔记

ESP32是一款由乐鑫科技&#xff08;Espressif Systems&#xff09;推出的双核、低功耗、集成Wi-Fi和蓝牙的单芯片微控制器。它采用了Tensilica Xtensa LX6高性能处理器&#xff0c;具有大量的GPIO引脚、模数转换器、SPI、I2S、UART、PWM、I2C和SD卡接口等功能&#xff0c;可以满…

vue数据单双渲染以及代码讲解

&#x1f600;前言 本片文章是vue系列第2篇整理了vue的单双数据绑定以及代码讲解 &#x1f3e0;个人主页&#xff1a;尘觉主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是尘觉&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满意是我的动力&#x1f…

了解Unity编辑器之组件篇Tilemap(五)

Tilemap&#xff1a;用于创建和编辑2D网格地图的工具。Tilemap的主要作用是简化2D游戏中地图的创建、编辑和渲染过程。以下是一些Tilemap的主要用途&#xff1a; 2D地图绘制&#xff1a;Tilemap提供了一个可视化的编辑器界面&#xff0c;可以快速绘制2D地图&#xff0c;例如迷…

Flask 页面展示文件目录及文件,通过勾选复习框删除

(45条消息) flask 读取文件夹文件&#xff0c;展示在页面&#xff0c;可以通过勾选删除_U盘失踪了的博客-CSDN博客 基本实现 针对上面的功能再优化 项目结构 app.py import os import shutil from flask import Flask, render_template, request, redirect, url_forapp F…

服务器数据恢复-Windows服务器RAID5数据恢复案例

服务器数据恢复环境&#xff1a; 一台服务器挂载三台IBM某型号存储设备&#xff0c;共64块SAS硬盘&#xff0c;组建RAID5磁盘阵列&#xff1b; 服务器操作系统&#xff1a;Windows Server&#xff1b;文件系统&#xff1a;NTFS。 服务器故障&#xff1a; 一台存储中的一块硬盘离…