websocket学习笔记1

news2024/11/20 11:31:12

1. 知识模块一

1.1. websocket与http对比

1.1.1. http协议

主要关注:客户端->服务器(获取资源)

特点:

  • 无状态协议,每个请求都是独立的,请求应答模式,服务端无法主动给客户端推送消息,半双工(同一刻数据传输只能是单项的,还有单工和全双工)。
  • http受浏览器同源策略影响,需要保证协议、主机名、端口号一致,否则会出现跨域问题(为了安全)。
  • 适合获取资源、下载文件,但不适合实时性要求高的需求。

1.1.2.websocket协议

双向通信(全双工协议),每次不需要重新建立连接,可以一致相互通信,适合长通信。

1.1.3.关系

都是通信协议,websocket是建立在http基础之上的,第一次websocket握手是基于http的,底层传输都依靠TCP。

1.2.不用websocket以前是如何实现双向通信的

Comet,这个技术主要是为了实现服务端可以向客户端推送数据,为了解决实时性比较高的情况。

import express from "express";
import cors from "cors";

const app = express();
// 解决跨域问题
app.use(cors());

// 轮询,短轮询()


// 接口
app.get('/clock',function(req,res){
    res.send(new Date().toLocaleDateString());
})

// 通过node命令启动时,修改后并不会重新执行
// 通过nodeman启动可以在改变后自动执行
app.listen(3000,function(){
    console.log('server start 3000');
})

  1. 轮询

    clock-1.html

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    
    <body>
        <div id="clock"></div>
    
        <script>
            setInterval(() => {
             	// 创建请求
                const xhr = new XMLHttpRequest();
                
                // 访问请求,异步
                xhr.open('GET','http://localhost:3000/clock',true);
                xhr.onload = function () {
                    console.log(xhr.responseText);
                    clock.innerHTML = xhr.responseText;
                }
                // 发送请求
                xhr.send();
            }, 1000)//每隔一秒
        </script>
    </body>
    
    </html>
    

    存在问题:

    • 竞速问题:无法保证请求的先后顺序,可能会出现多个请求返回的时候同时修改资源,会导致一些不可预测的问题。
    • 频繁的网络请求,请求数目过多,会导致网络带宽的消耗,增加服务端和客户端的消耗。
    • http在发送请求的时候,会增加http报文(鉴权、内容类型),增加额外的数据消耗
    • 实时性比较低,如果服务端1s内变了三次,而客户端每隔1s发送一次请求。

    优点:

    • 容易实现,适合轻量级、低并发。
  2. 长轮询

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    
    <body>
        <div id="clock"></div>
    
        <script>
            // 客户端发送请求后,服务端相应后,我就发下一个请求
            function longPolling() {
                const xhr = new XMLHttpRequest();
                xhr.open('GET', 'http://localhost:3000/clock', true);
                xhr.onload = function () {
                    console.log(xhr.responseText);
                    clock.innerHTML = xhr.responseText;
                    longPolling();
                }
                xhr.send();
            }
            longPolling()
        </script>
    </body>
    
    </html>
    
    1. 想解决短轮询的问题,希望实时性更强,但是实时性强了的同时,也会造成频繁的网络请求(实时性强了,但是要求服务端的并发能力必须强)。
    2. 连接堆叠问题,这些链接都在服务端中保持打开,会占用服务端资源。
  3. iframe流(以前用的挺多的)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <div id="clock"></div>\
        <!-- 目前谷歌的document.domain跨域方法已经噶了 -->
        <script>
            document.domain = 'localhost'
        </script>
        <iframe src="http://localhost:3000/clock" frameborder="0"></iframe>
    </body>
    </html>
    
    import express from "express";
    import cors from "cors";
    
    const app = express();
    // 解决跨域问题
    app.use(cors());
    
    // 接口
    app.get('/clock', function (req, res) {
        // res.end或者res.send请求结束后会断开
        // res.write方法不会结束本次的响应
        setInterval(() => {
            res.write(`
            <script>
                document.domain = 'localhost'
                parent.document.getElementById('clock').innerHTML = "${new Date().toDateString()}"
            </script>
            `);
        })
    })
    
    // 通过node命令启动时,修改后并不会重新执行
    // 通过nodeman启动可以在改变后自动执行
    app.listen(3000, function () {
        console.log('server start 3000');
    })
    
    
    

    创建之后一直保持链接,会出现跨域问题

    可以保证实时性,而且不用客户频繁发送请求 。

    缺点:单向通信。

  4. sse EventSource(写法已经比较接近websocket了)

    html提供的,单向通信,客户端可以监控服务端推送的事件,只能推送文本类型的数据,适合小数据,需要做额外的处理。

    缺点:单向,客户端无法给服务端传递数据。

  5. websocket

    优势:

    1. 双向绑定
    2. 持久链接,可以一直握手
    3. 发送的消息增加帧非常小
    4. 支持多种数据格式
    5. 天生支持跨域

2. 知识模块二

2.1.基础内容

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <!-- 客户端 -->
    <script>
        // 与服务端提供的一个websocket服务相关联
        const ws = new WebSocket('ws://localhost:3000');
        // 给服务端发送消息
        ws.onopen = function(){
            console.log('Connection opend');
            ws.send('hello server');// 给服务端发送消息
        }

        // 监控服务端的数据
        ws.onmessage = function(e){
            console.log('服务端相应的数据:' + e.data);
        }

        // http各种header的使用

        // websocket怎么实现握手、数据长什么样的、怎么通信的
        // 协议的表示方式
    
        // 请求行:GET ws://localhost:3000 HTTP/1.1
        // Connection:Upgrade
        // Sec-Websocket-Key:用于保证是安全的websocket链接,防止恶意连接,用于握手
        // Sec-Websoeckt-Version:版本

        // 握手成功后服务端会返回一个Sec-Websocket-Accept,是根据key算出来的
        // Upgrade:websocket,表示升级成什么协议

    </script>
</body>
</html>
import express, { response } from 'express';
import http from 'http';
import { WebSocketServer } from 'ws';

const app = express();
const server = http.createServer(app); // http服务

const wss = new WebSocketServer({server});

// 监控连接成功
wss.on('connection',(ws)=>{
    console.log('Connection opend');

    // 给客户端发送消息
    ws.send('hello client');

    // 第一个参数可以为
    // close、error、message、open、ping、pong、upgrade、unexpected-response
    ws.on('message', function(message){
        console.log("客户端数据:"+message);
    })
})

// 监控端口
server.listen(3000)

2.2. key和accept的换算

// 可以使用wireshark抓包软件,分析协议信息
// key-> P2P2F9kEf/wg18RKzXM8eA== ,握手的时候创建一个随机的key
// 服务端通过key加上
// const number = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
// 然后经历sha1算法计算生成accept,
// accept-> adAEOXRx506qcgqahbjvIHPI1Sk= ,服务端要相应一个值
import crypto from 'crypto'

const number = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'const WebsocketKey = 'P2P2F9kEf/wg18RKzXM8eA=='; // key是随机值

const WebsocketAccept = crtpto
		 .createHash('sha1')
		 .update(websocketKey + number)
		 .digest('base64');

2.3.具体握手过程

2.3.1.三次握手:

  1. 第一次握手:建立连接,客户端A发送SYN=1、随机产生Seq=client_isn的数据包到服务器B,等待服务器确认。
  2. 第二次握手:服务器B收到请求后确认联机(可以接受数据),发起第二次握手请求,ACK=(A的Seq+1)、SYN=1,随机产生Seq=client_isn的数据包到A。
  3. 第三次握手:A收到后检查ACK是否正确,若正确,A会在发送确认包ACK=服务器B的Seq+1、ACK=1,服务器B收到后确认Seq值与ACK值,若正确,则建立连接。

通俗点,客户端跟服务端说我们结婚吧,服务端给客户端说好的我们结婚吧,然后服务端和客户端结婚了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.3.2.websocket数据帧格式:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • FIN:1个比特,如果是1,表示这是消息(message)的最后一个分片(fragment),如果是0,表示不是消息(message)的最后一个分片(fragment)。

  • RSV1、RSV2、RSV1:各占1个比特,一般情况全为0.当客户端、服务端协商采用websocket扩展时,这三个标志位可以非0,且值的含义由拓展进行定义。如果出现非零的值,且并没有采用websocket拓展,连接出错。

  • Opcode:4个比特。操作代码,决定了应该如何解析后续的数据载荷(data payload)。如果操作代码是不认识的,那么接收端应该断开连接

    • %x1:表示这是一个文本帧。
    • %x2:表示这是一个二进制帧。
    • %x2:表示这是一个二进制帧。
    • %x3-7:保留的操作代码,用于后续定义的非控制帧。
    • %x8:表示连接断开
    • %x9:表示这是一个ping操作
    • %xA:表示这是一个pong操作
    • %xB-F:保留的操作代码,用于后续定义的控制帧
  • Mask:1个比特。表示是否要对数据载荷进行掩码操作

    • 从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作,如果服务端接收到的数据没有进行掩码操作,服务端需要断开连接。

      在这里插入图片描述

    • 如果Mask是1,那么在 Masking-key中会定义一个掩码键(masking key),并用这个掩码键来对数据载荷进行反掩码。所有客户端发送到服务端的数据帧,Mask都是1。

      在这里插入图片描述

  • Payload length:表示数据载荷的长度,单位是字节,由7位/7+16位/7+64位

    • Payload length=x为0~125:数据的长度为x字节。
    • Payload length=x为126:后续2个字节代表一个16位的无符号整数,该无符号整数的值为数据的长度。
    • Payload length=x为127:后续8个字节代表一个64位的无符号整数(最高位为0),该无符号整数的值为数据的长度。
    • 如果Payload length占用了多个字节的话,Payload length的二进制表达采用网络序(big endian,重要的位在前)
  • Masking-key:0或4字节(32位),所有从客户端传到服务端的数据帧,数据载荷都进行了掩码操作,Mask为1,且携带了4字节的Masking-key。如果Mask为0,则没有Masking-key。载荷数据的长度不包括mask key的长度

  • Payload data:(x+y)字节

    • 载荷数据:包括了拓展数据、应用数据。其中拓展数据x字节,应用数据y字节

2.3.3.具体代码模拟

// 引入node内的tcp模块,可以接收原始的tcp消息
import net from 'net';
import crypto from 'crypto';
const server = net.createServer(function (socket) { //每个人都会产生一个socket
    // 接收二进制信息
    socket.once('data', function (data) {
        // 将二进制信息转化为字符串
        data = data.toString();
        // 如果升级为websocket协议
        // console.log(data);
        // GET / HTTP/1.1
		// Host: localhost:3000
		// Connection: Upgrade
		// Pragma: no-cache
		// Cache-Control: no-cache
		// User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) 						//AppleWebKit/537.36 (KHTML, like Gecko) 
        //Chrome/117.0.0.0 Safari/537.36
		// Upgrade: websocket
		// Origin: http://127.0.0.1:5500
		// Sec-WebSocket-Version: 13
		// Accept-Encoding: gzip, deflate, br
		// Accept-Language: zh-CN,zh;q=0.9
		// Sec-WebSocket-Key: 1tIB0I01z9xlRZt89EDUxw==
		// Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
        if (data.match(/Upgrade: websocket/)){
            // 报文是以换行来分割的
            let rows = data.split('\r\n');
            // 解析出请求头
            const headers = rows.slice(1,-2).reduce((memo,row)=>{
                let [key,value] = row.split(': ')
                // 改成小写
                memo[key.toLowerCase()] = value;
                return memo;
            },{});
            const number = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
            let websocketKey = headers['sec-websocket-key'];
            let websocketAccept = crypto.createHash('sha1').update(websocketKey + number).digest('base64');

            // 相应报文
            let response = [
                'HTTP/1.1 101 Switching Protocols',
                'Upgrade: websocket',
                `Sec-Websocket-Accept: ${websocketAccept}`,
                'Connection: Upgrade',
                '\r\n'
            ].join('\r\n');

            // 表示websocket建立连接成功
            socket.write(response);

            // 继续解析 后续发来的websocket数据
            socket.on('data', function(buffers) {
                // 解析websocket的格式

                // 一、客户端发消息过来,先判断消息是否结束了
                // 第一个字节(1个字节是8个位,如何获取第一位是不是1)
                // 位运算:
                // 1、按位或,有一个为1即为1
                // 0000 1111
                // 1111 0000
                //--------------
                // 1111 1111
                // 2、按位与,都是1才是1
                // 0000 1111
                // 1111 1111
                // -------------
                // 0000 1111
                // 3、异或,相同为0不同为1
                // 0000 0111
                // 1000 0110
                //--------------
                // 1000 0001
                const FIN = ((buffers[0] & 0b10000000) === 0b10000000); //表示完成了
                console.log(FIN); //true

                // 二、判断发送数据的格式
                // 1表示的是文本,由于前四位不需要所以为0000 1111
                const OPCOED = (buffers[0] & 0b00001111);
                console.log(OPCOED); // 1

                // 三、计算masked,由于第一位数已经使用完,这里开始使用第二位
                const MASKED = ((buffers[1] & 0b10000000) === 0b10000000);
                console.log(MASKED); //true

                // 四、计算payload_len
                const PAYLOAD_LEN = ((buffers[1] & 0b01111111));
                console.log(PAYLOAD_LEN); // 12

                // 五、获取掩码,掩码的长度是4个字节
                const MASK_KEY = buffers.slice(2,6);

                // 六、获取真正的数据内容,这个内容是被掩码过的,需要用掩码做异或操作(相同为0不同为1)
                const PAYLOAD = buffers.slice(6);
                for (let i = 0 ; i<PAYLOAD.length; i++){
                    // 如果数据有多个字节但是掩码是4个字节时
                    PAYLOAD[i] = PAYLOAD[i]^MASK_KEY[i%4];
                }
                console.log(PAYLOAD.toString()); // hello server

                // 以上内容为客户端给服务端发送消息流程。
                // 服务端如果想给客户端发送消息,按照一样的格式发送即可(服务端给客户端发送消息是不用加掩码的)
                 

            })
        }
    })
})


server.listen(3000, function() {
    console.log('server start 3000');
})

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

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

相关文章

linux下的永久保存行号

linux下的永久保存行号 1.首先 这里是引用 输入命令&#xff1a;vi ~/.vimrc 其次 这里是引用 输入命令 set number

一款超实用的AI漫画生成器,支持9种漫画风格,无限免费使用

HI&#xff0c;同学们&#xff0c;我是赤辰&#xff0c;本期是赤辰第12篇AI工具类教程&#xff0c;文章底部准备了粉丝福利&#xff0c;看完可以领取&#xff01;今天给大家介绍一款AI漫画生成器——AI Comic Factory&#xff0c;只需输入提示词&#xff0c;即可瞬间创造出一幅…

【torch】parameters与named_parameters的区别

【torch】parameters与named_parameters的区别 前言 为了详细的查看网络的结构参数等&#xff0c;因此本文研究一下 parameters()与 named_parameters 的区别。 此示例属于从 nn.Module 中继承的成员函数。函数位于&#xff1a;[python环境路径]/lib/python3.8/site-packages…

角谱计算时的fftshift及其原理

做一个fft运算&#xff0c;第一个事先用fftshift对待变换的E0进行操作&#xff0c;第二个没有用fftshift&#xff0c;第三个没有用fftshift但是进行了相位手动修正&#xff1a; %%用fft进行角谱传输计算 %对比fft运算与傅里叶变换&#xff08;黎曼和&#xff09;的区别以及修正…

Nature Machine Intelligence | “化学元素知识+功能提示”双驱动,探索分子预测新方法

论文题目&#xff1a;Knowledge graph-enhanced molecular contrastive learning with functional prompt 论文链接&#xff1a;https://doi.org/10.1038/s42256-023-00654-0 项目地址&#xff1a;GitHub - HICAI-ZJU/KANO: Code and data for the Nature Machine Intelligence…

光纤掺杂浓度之间的转换计算方法

掺杂浓度表示形式 掺杂浓度是光纤光学中无源或有源掺杂光纤中最重要的参数之一。在文献中可以找到许多不同的方法来表示基于原子或摩尔的掺杂浓度。 化学元素基于原子或离子的定义是非常明确的。例如原子百分比&#xff08;atomic percentage&#xff0c;at.%&#xff09;、原…

爬虫为什么需要 HTTP 代理 IP?

前言 爬虫在互联网数据采集、分析和挖掘中扮演着至关重要的角色&#xff0c;但是对于目标网站而言&#xff0c;频繁的爬虫请求可能会对其服务器产生不小的负担&#xff0c;严重的情况甚至会导致网站崩溃或者访问受限。为了避免这种情况的发生&#xff0c;同时也为了保护客户端…

如何安全驾驭物联网视频革命

相机即将连接到您附近的连接设备。来自旧手机的廉价图像传感器正在涌入市场&#xff0c;并将视频带入物联网 (IoT)。 吸尘器、喂鸟器、联网汽车甚至智能烤箱现在都配备了摄像头&#xff0c;可以识别菜肴并建议剩余的烹饪时间。这是联网设备功能的重大转变&#xff0c;目前全球…

EasyXnote5关于批量绘图

专栏&#xff1a;EasyX图形化编程 文章目录 问题引入 绘制画面批量绘图解释批量绘图使用 问题引入 之前的讲解中&#xff0c;我们可以发现创建的窗体在进行动画的显示时会出现闪烁现象&#xff0c;本节课将会一步一步探讨如何解决&#xff0c;可以使以后学习中的动画效果更加流…

二项分布以及实现

文章目录 前言所谓二项分布就是只会产生两种结果的概率 1.概念 前言 所谓二项分布就是只会产生两种结果的概率 1.概念 下面是一个二项分布的的theano实现 import numpy as np import theano import theano.tensor as T from theano.tensor.nnet import conv from theano.ten…

【数据结构--八大排序】之快速排序

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

MySQL——使用mysqldump备份与恢复数据

目录 1.mysqldump简介 2.mysqldump备份数据 2.1 备份所有数据库 2.2 备份一个/多个数据库 2.3 备份指定库中的指定表 3.mysqldump恢复数据 3.1 恢复数据库 3.2 恢复数据表 1.mysqldump简介 mysqldump命令可以将数据库中指定或所有的库、表导出为SQL脚本。表的结构和表中…

图像压缩:Transformer-based Image Compression with Variable Image Quality Objectives

论文作者&#xff1a;Chia-Hao Kao,Yi-Hsin Chen,Cheng Chien,Wei-Chen Chiu,Wen-Hsiao Peng 作者单位&#xff1a;National Yang Ming Chiao Tung University 论文链接&#xff1a;http://arxiv.org/abs/2309.12717v1 内容简介&#xff1a; 1&#xff09;方向&#xff1a;…

代码随想录 Day11 二叉树 LeetCode T144,145,94 前中后序遍历 (递归解法)

题解及更详细解答来自于:代码随想录 (programmercarl.com) 前言: 递归三要素 确定递归函数的参数和返回值&#xff1a; 确定哪些参数是递归的过程中需要处理的&#xff0c;那么就在递归函数里加上这个参数&#xff0c; 并且还要明确每次递归的返回值是什么进而确定递归函数的返…

深度学习实战基础案例——卷积神经网络(CNN)基于MobileNetV3的肺炎识别|第3例

文章目录 前言一、数据集介绍二、前期工作三、数据集读取四、构建CA注意力模块五、构建模型六、开始训练 前言 Google公司继MobileNetV2之后&#xff0c;在2019年发表了它的改进版本MobileNetV3。而MobileNetV3共有两个版本&#xff0c;分别是MobileNetV3-Large和MobileNetV2-…

不容易解的题10.5

31.下一个排列 31. 下一个排列 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/next-permutation/?envTypelist&envIdZCa7r67M会做就不算难题&#xff0c;如果没做过不知道思路&#xff0c;这道题将会变得很难。 这道题相当于模拟cpp的next_permu…

Windows系统上使用CLion远程开发Linux程序

CLion远程开发Linux程序 情景说明Ubuntu配置CLion配置同步 情景说明 在Windows系统上使用CLion开发Linux程序&#xff0c;安装CLion集成化开发环境时会自动安装cmake、mingw&#xff0c;代码提示功能也比较友好。 但是在socket开发时&#xff0c;包含sys/socket.h头文件时&am…

浅谈CDN内容分发与全局负载均衡

CDN简介 CDN的全称是Content Delivery Network&#xff0c;即内容分发网络。CDN是构建在现有网络基础之上的智能虚拟网络&#xff0c;依靠部署在各地的边缘服务器&#xff0c;通过中心平台的负载均衡、内容分发、调度等功能模块&#xff0c;使用户就近获取所需内容&#xff0c…

软件设计师_数据结构与算法_学习笔记

文章目录 6.1 数组与矩阵6.1.1 数组6.1.2 稀疏矩阵 6.2 线性表6.2.1 数据结构的定义6.2.2 顺序表与链表6.2.2.1 定义6.2.2.2 链表的操作 6.2.3 顺序存储和链式存储的对比6.2.4 队列、循环队列、栈6.2.4.2 循环队列队空与队满条件6.2.4.3 出入后不可能出现的序列练习 6.2.5 串 6…

C语言学习系列->联合体and枚举

文章目录 前言联合体概述联合体的特点联合体大小的计算优点练习 枚举概述优点使用 前言 在上一篇文章中&#xff0c;小编将结构体的学习笔记整理出来了。现在&#xff0c;小编将枚举和联合体笔记分享给大家。枚举和联合体与结构体一样&#xff0c;都是自定义类型&#xff0c;在…