websocket前后端实例 及 服务器基于tcp实现从http升级到websocket协议 ...

打印 上一主题 下一主题

主题 642|帖子 642|积分 1926

本文代码:本文代码:
1.HTTP的架构模式
  1.1. HTTP的特点
2. 双向通讯
  2.1 轮询
  2.2 长轮询
  2.3 iframe流
  2.4 EventSource流
  2.4.1 欣赏器端
  2.4.2 服务端
3.websocket
  3.1 websocket 优势
  3.2 websocket实战
    3.2.1 服务端
    3.2.2 客户端
  3.3 如何建立连接
    3.3.1 客户端:申请协议升级
    3.3.2 服务端:相应协议升级
    3.3.3 Sec-WebSocket-Accept的盘算
    3.3.4 Sec-WebSocket-Key/Accept的作用
  3.4 数据帧格式
    3.4.1 数据帧格式
    3.4.2 掩码算法
    3.4.3 服务器实战
  1. HTTP的架构模式
Http是客户端/服务器模式中哀求-相应所用的协议,在这种模式中,客户端(一样平常是web欣赏器)向服务器提交HTTP哀求,服务器相应哀求的资源。
1.1. HTTP的特点


  • HTTP是半双工协议,也就是说,在同一时候数据只能单向流动,客户端向服务器发送哀求(单向的),然后服务器相应哀求(单向的)。
  • 服务器不能主动推送数据给欣赏器。
2. 双向通讯
Comet是一种用于web的推送技能,能使服务器能实时地将更新的信息传送到客户端,而无须客户端发出哀求,目前有三种实现方式:轮询(polling) 长轮询(long-polling)和iframe流(streaming)。
2.1 轮询


  • 轮询是客户端和服务器之间会一直举行连接,每隔一段时间就扣问一次
  • 这种方式连接数会许多,一个担当,一个发送。而且每次发送哀求都会有Http的Header,会很耗流量,也会消耗CPU的利用率

  1. **index.html**
  2. <!DOCTYPE html>
  3. <html lang="en">
  4.   <head>
  5.     <meta charset="UTF-8" />
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7.     <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  8.     <title>Document</title>
  9.   </head>
  10.   <body>
  11.     <div id="clock"></div>
  12.     <script>
  13.       let clock = document.querySelector("#clock");
  14.       setInterval(function () {
  15.         let xhr = new XMLHttpRequest();
  16.         xhr.open("GET", "/clock", true);
  17.         xhr.onreadystatechange = function () {
  18.           if (xhr.readyState === 4 && xhr.status === 200) {
  19.             clock.innerHTML = xhr.responseText;
  20.           }
  21.         };
  22.         xhr.send();
  23.       });
  24.     </script>
  25.   </body>
  26. </html>
复制代码
  1. **app.js**
  2. let express = require("express");
  3. let app = express();
  4. // http://localhost:8000/
  5. app.use(express.static(__dirname));
  6. app.get("/clock", function (req, res) {
  7.   res.send(new Date().toLocaleString());
  8. });
  9. app.listen(8000);
复制代码
2.2 长轮询


  • 长轮询是对轮询的改进版,客户端发送HTTP给服务器之后,看有没有新消息,假如没有新消息,就一直等待
  • 当有新消息的时候,才会返回给客户端。在某种程度上减小了网络带宽和CPU利用率等问题。
  • 由于http数据包的头部数据量每每很大(通常有400多个字节),但是真正被服务器必要的数据却很少(偶尔只有10个字节左右),这样的数据包在网络上周期性的传输,难免对网络带宽是一种浪费
  1. **index.html**
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5.     <meta charset="UTF-8">
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7.     <meta http-equiv="X-UA-Compatible" content="ie=edge">
  8.     <title>Document</title>
  9. </head>
  10. <body>
  11.     <div id="clock"></div>
  12. <script>
  13. let clock = document.querySelector('#clock');
  14. function send(){
  15.     let xhr = new XMLHttpRequest;
  16.     xhr.open('GET','/clock',true);
  17.     xhr.onreadystatechange = function(){
  18.         if(xhr.readyState == 4 && xhr.status == 200){
  19.             clock.innerHTML = xhr.responseText;
  20.             send();
  21.         }
  22.     }
  23.     xhr.send();
  24. }
  25. send();
  26. </script>   
  27. </body>
  28. </html>
复制代码
  1. **app.js**
  2. let express = require("express");
  3. let app = express();
  4. // http://localhost:8000/
  5. app.use(express.static(__dirname));
  6. app.get("/clock", function (req, res) {
  7.   let $timer = setInterval(function () {
  8.     let date = new Date();
  9.     let seconds = date.getSeconds();
  10.     if (seconds % 5 === 0) {
  11.       res.send(date.toLocaleString());
  12.       clearInterval($timer);
  13.     }
  14.   }, 1000);
  15. });
  16. app.listen(8000);
复制代码
2.3 iframe流
通过在HTML页面里嵌入一个隐蔽的iframe,然后将这个iframe的src属性设为对一个长连接的哀求,服务器端就能源源不断地往客户推送数据。
  1. **index.html**
  2. <!DOCTYPE html>
  3. <html lang="en">
  4.   <head>
  5.     <meta charset="UTF-8" />
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7.     <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  8.     <title>Document</title>
  9.   </head>
  10.   <body>
  11.     <div id="clock" style="border: 1px solid red; height: 200px"></div>
  12.     <iframe src="/clock" style="border: none; height: 0"></iframe>
  13.     <script>
  14.       //window.setTime =
  15.       function setTime(ts) {
  16.         document.querySelector("#clock").innerHTML = ts;
  17.       }
  18.     </script>
  19.   </body>
  20. </html>
复制代码
  1. **app.js**
  2. let express = require("express");
  3. let app = express();
  4. // http://localhost:8000/
  5. app.use(express.static(__dirname));
  6. app.get("/clock", function (req, res) {
  7.   res.header("Content-Type", "text/html");
  8.   setInterval(function () {
  9.     res.write(`
  10.       <script>
  11.          parent.setTime("${new Date().toLocaleString()}")
  12.       </script>`);
  13.   }, 1000);
  14. });
  15. app.listen(9001);
复制代码
2.4 EventSource流


  • HTML5规范中提供了服务端事件EventSource,欣赏器在实现了该规范的条件下创建一个EventSource连接后,便可收到服务端的发送的消息,这些消息必要遵照一定的格式,对于前端开辟人员而言,只需在欣赏器中侦听对应的事件皆可
  • SSE的简朴模子是:一个客户端去从服务器端订阅一条流,之后服务端可以发送消息给客户端直到服务端大概客户端关闭该“流”,以是eventsource也叫作"server-sent-event`
  • EventSource流的实现方式对客户端开辟人员而言非常简朴,兼容性良好
  • 对于服务端,它可以兼容老的欣赏器,无需upgrade为其他协议,在简朴的服务端推送的场景下可以满足需求
2.4.1 欣赏器端 #


  • 欣赏器端,必要创建一个EventSource对象,并且传入一个服务端的接口URI作为参数
  • 默认EventSource对象通过侦听message事件获取服务端传来的消息
  • open事件则在http连接建立后触发
  • error事件会在通讯错误(连接中断、服务端返回数据失败)的环境下触发
  • 同时EventSource规范允许服务端指定自定义事件,客户端侦听该事件即可
  1. **index.html**
  2. <!DOCTYPE html>
  3. <html lang="en">
  4.   <head>
  5.     <meta charset="UTF-8" />
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7.     <title>Document</title>
  8.   </head>
  9.   <body>
  10.     <div id="clock"></div>
  11.     <script>
  12.       let eventSource = new EventSource("/clock");
  13.       let clock = document.querySelector("#clock");
  14.       eventSource.onmessage = function (event) {
  15.         let message = event.data;
  16.         clock.innerHTML = message;
  17.       };
  18.       eventSource.onopen = function (event) {
  19.         console.log("open");
  20.       };
  21.       eventSource.onerror = function (event) {
  22.         console.log("error");
  23.       };
  24.     </script>
  25.   </body>
  26. </html>
复制代码
2.4.2 服务端 #
事件流的对应MIME格式为text/event-stream,而且其基于HTTP长连接。针对HTTP1.1规范默认采用长连接,针对HTTP1.0的服务器必要特殊设置。
event-source必须编码成utf-8的格式,消息的每个字段利用"\n"来做分割,并且必要下面4个规范定义好的字段:
Event: 事件范例
Data: 发送的数据
ID: 每一条事件流的ID
Retry: 告知欣赏器在所有的连接丢失之后重新开启新的连接等待的时间,在主动重新连接的过程中,之前收到的末了一个事件流ID会被发送到服务端
  1. **app.js**
  2. let express = require("express");
  3. let app = express();
  4. app.use(express.static(__dirname));
  5. app.get("/clock", function (req, res) {
  6.   res.header("Content-Type", "text/event-stream");
  7.   let counter = 0;
  8.   let $timer = setInterval(function () {
  9.     res.write(
  10.       `id:${counter++}\nevent:abc\ndata:${new Date().toLocaleTimeString()}\n\n`
  11.     );
  12.   }, 1000);
  13.   res.on("close", function () {
  14.     clearInterval($timer);
  15.   });
  16. });
  17. app.listen(7777);
复制代码
  1. **app2.js** 使用ssestream库
  2. let express = require("express");
  3. let app = express();
  4. app.use(express.static(__dirname));
  5. //passThrough 通过流,它是一转换流
  6. const SseStream = require("ssestream");
  7. app.get("/clock", function (req, res) {
  8.   let counter = 0;
  9.   const sseStream = new SseStream(req);
  10.   sseStream.pipe(res);
  11.   const pusher = setInterval(function () {
  12.     sseStream.write({
  13.       id: counter++,
  14.       event: "message",
  15.       retry: 2000,
  16.       data: new Date().toString(),
  17.     });
  18.     // 他内部会帮你转换成:event:message\nid:0\nretry:2000\ndata:2019年3月23日17:45:51\n\n
  19.   }, 1000);
  20.   res.on("close", function () {
  21.     clearInterval(pusher);
  22.     sseStream.unpipe(res);
  23.   });
  24. });
  25. app.listen(7777);
复制代码
3. websocket


  • WebSockets_API 规范定义了一个 API 用以在网页欣赏器和服务器建立一个 socket 连接。普通地讲:在客户端和服务器保有一个持久的连接,两边可以在恣意时间开始发送数据。
  • HTML5开始提供的一种欣赏器与服务器举行全双工通讯的网络技能
  • 属于应用层协议,它基于TCP传输协议,并复用HTTP的握手通道。
3.1 websocket优势


  • 支持双向通讯,实时性更强。
  • 更好的二进制支持。
  • 较少的控制开销。连接创建后,ws客户端、服务端举行数据交换时,协议控制的数据包头部较小。
3.2 websocket实战
3.2.1 服务端
  1. **app.js**
  2. let express = require("express");
  3. let app = express();
  4. app.use(express.static(__dirname));
  5. app.listen(3000);
  6. let websocketServer = require("ws").Server;
  7. let server = new websocketServer({ port: 8888 });
  8. server.on("connection", (socket) => {
  9.   console.log("2.服务器监听到了客户端请求");
  10.   socket.on("message", (message) => {
  11.     console.log("4.客户端连接过来的消息", message);
  12.     socket.send("5.服务器说" + message);
  13.   });
  14. });
复制代码
3.2.2 客户端
  1. **index.html**
  2. <!DOCTYPE html>
  3. <html lang="en">
  4.   <head>
  5.     <meta charset="UTF-8" />
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7.     <title>Document</title>
  8.   </head>
  9.   <body>
  10.     <script>
  11.       let socket = new WebSocket("ws://localhost:8888");
  12.       socket.onopen = function () {
  13.         console.log("1. 客户端连接上了服务器");
  14.         socket.send("hello");
  15.       };
  16.       socket.onmessage = function (event) {
  17.         console.log(event.data);
  18.       };
  19.     </script>
  20.   </body>
  21. </html>
复制代码
3.3. 如何建立连接
WebSocket复用了HTTP的握手通道。详细指的是,客户端通过HTTP哀求与WebSocket服务端协商升级协议。协议升级完成后,后续的数据交换则遵照WebSocket的协议。
3.3.1 客户端:申请协议升级
首先,客户端发起协议升级哀求。可以看到,采用的是标准的HTTP报文格式,且只支持GET方法。
哀求头:
GET ws://localhost:8888/ HTTP/1.1
Host: localhost:8888
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: IHfMdf8a0aQXbwQO1pkGdA==


  • Connection: Upgrade:表现要升级协议
  • Upgrade: websocket:表现要升级到websocket协议
  • Sec-WebSocket-Version: 13:表现websocket的版本
  • Sec-WebSocket-Key:与后面服务端相应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的连接,大概无意的连接。
3.3.2 服务端:相应协议升级
服务端返回内容如下,状态代码101表现协议切换。到此完成协议升级,后续的数据交互都按照新的协议来。
相应头:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: aWAY+V/uyz5ILZEoWuWdxjnlb7E=
3.3.3 Sec-WebSocket-Accept的盘算
Sec-WebSocket-Accept根据客户端哀求首部的Sec-WebSocket-Key盘算出来。 盘算公式为:


  • 将Sec-WebSocket-Key跟258EAFA5-E914-47DA-95CA-C5AB0DC85B11(固定是这个)拼接。
  • 通过SHA1盘算出择要,并转成base64字符串
  1. const crypto = require('crypto');
  2. const CODE = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
  3. const webSocketKey = 'IHfMdf8a0aQXbwQO1pkGdA==';
  4. let websocketAccept = require('crypto').createHash('sha1').update(webSocketKey + CODE ).digest('base64');
  5. console.log(websocketAccept);//aWAY+V/uyz5ILZEoWuWdxjnlb7E=
复制代码
3.3.4 Sec-WebSocket-Key/Accept的作用


  • 避免服务端收到非法的websocket连接
  • 确保服务端理解websocket连接
  • 用欣赏器里发起ajax哀求,设置header时,Sec-WebSocket-Key以及其他相关的header是被禁止的
  • Sec-WebSocket-Key主要目的并不是确保数据的安全性,因为Sec-WebSocket-Key、Sec-WebSocket-Accept的转换盘算公式是公开的,而且非常简朴,最主要的作用是预防一些常见的意外环境(非故意的)
3.4 数据帧格式
WebSocket客户端、服务端通讯的最小单元是帧,由1个或多个帧组成一条完整的消息(message)。


  • 发送端:将消息切割成多个帧,并发送给服务端
  • 接收端:接收消息帧,并将关联的帧重新组装成完整的消息
3.4.1 数据帧格式
单元是比特。比如FIN、RSV1各占据1比特,opcode占据4比特
  1. 0                   1                   2                   3
  2.   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  3. +-+-+-+-+-------+-+-------------+-------------------------------+
  4. |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
  5. |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
  6. |N|V|V|V|       |S|             |   (if payload len==126/127)   |
  7. | |1|2|3|       |K|             |                               |
  8. +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
  9. |     Extended payload length continued, if payload len == 127  |
  10. + - - - - - - - - - - - - - - - +-------------------------------+
  11. | Extended payload length       | Masking-key, if MASK set to 1 |
  12. +-------------------------------+-------------------------------+
  13. | Masking-key (continued)       |          Payload Data         |
  14. +-------------------------------- - - - - - - - - - - - - - - - +
  15. :                     Payload Data continued ...                :
  16. + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
  17. |                     Payload Data continued ...                |
  18. +---------------------------------------------------------------+
复制代码


  • FIN:1个比特 假如是1,表现这是消息(message)的末了一个分片(fragment),假如是0,表现不是是消息(message)的末了一个分片(fragment)
  • RSV1, RSV2, RSV3:各占1个比特。一样平常环境下全为0。当客户端、服务端协商采用WebSocket扩展时,这三个标志位可以非0,且值的寄义由扩展举行定义。假如出现非零的值,且并没有采用WebSocket扩展,连接出错。
  • Opcode: 4个比特。操纵代码,Opcode的值决定了应该如何解析后续的数据载荷(data payload)。假如操纵代码是不认识的,那么接收端应该断开连接(fail the connection)

    • %x0:表现一个延续帧。当Opcode为0时,表现本次数据传输采用了数 据分片,当前收到的数据帧为此中一个数据分片。
    • %x1:表现这是一个文本帧(frame)
    • %x2:表现这是一个二进制帧(frame)
    • %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位。以是一个数据帧最大数据载荷是(2^64-1)字节(B)。

    • 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字节。
    • 扩展数据:假如没有协商利用扩展的话,扩展数据数据为0字节。所有的扩展都必须声明扩展数据的长度,大概可以如何盘算出扩展数据的长度。别的,扩展如何利用必须在握手阶段就协商好。假如扩展数据存在,那么载荷数据长度必须将扩展数据的长度包罗在内。
    • 应用数据:恣意的应用数据,在扩展数据之后(假如存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度。

3.4.2 掩码算法
掩码键(Masking-key)是由客户端挑选出来的32位的随机数。掩码操纵不会影响数据载荷的长度。掩码、反掩码操纵都采用如下算法:


  • 对索引i模以4得到j,因为掩码一共就是四个字节
  • 对原来的索引举行异或对应的掩码字节
  • 异或就是两个数的二进制情势,按位对比,相同取0,不同取1
  1. function maskOrUnmask(buffer, mask) {
  2.   const length = buffer.length;
  3.   for (let i = 0; i < length; i++) {
  4.     buffer[i] ^= mask[i % 4];
  5.   }
  6.   return buffer;
  7. }
  8. const mask = Buffer.from([0x12, 0x34, 0x56, 0x78]); // 随机写的字节数组
  9. const buffer = Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]); // 随机写的字节数组
  10. const masked = maskOrUnmask(buffer, mask); // 掩码
  11. console.log(masked); // <Buffer 7a 51 3a 14 7d>
  12. const unmasked = maskOrUnmask(buffer, mask); // 反掩码
  13. console.log(unmasked); // <Buffer 68 65 6c 6c 6f>,和buffer一致
复制代码
3.4.3 服务器实战
  1. **app2.js**
  2. let net = require("net"); // net模块用于创建tcp服务
  3. let CODE = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
  4. let crypto = require("crypto");
  5. // 实现http协议升级到ws协议。模拟服务端响应头返回
  6. /**
  7. *
  8. GET ws://localhost:8888/ HTTP/1.1/r/n
  9. Connection: Upgrade/r/n
  10. Upgrade: websocket/r/n
  11. Sec-WebSocket-Version: 13/r/n
  12. Sec-WebSocket-Key: O/SldTn2Th7GfsD07IxrwQ==/r/n
  13. /r/n
  14. */
  15. /**
  16. *
  17. HTTP/1.1 101 Switching Protocols
  18. Upgrade: websocket
  19. Connection: Upgrade
  20. Sec-WebSocket-Accept: H8BlFmSUnXVpM4+scTXjZIwFjzs=
  21. */
  22. let server = net.createServer((socket) => {
  23.   socket.once("data", (data) => {
  24.     // 建立连接,使用once
  25.     data = data.toString(); // buffer转字符串
  26.     if (data.match(/Connection: Upgrade/)) {
  27.       let rows = data.split("\r\n");
  28.       //   rows格式:
  29.       //   'GET / HTTP/1.1',
  30.       //   'Host: localhost:9999',
  31.       //   'Connection: Upgrade',
  32.       //   'Pragma: no-cache',
  33.       //   'Cache-Control: no-cache',
  34.       //   'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
  35.       //   'Upgrade: websocket',
  36.       //   'Origin: null',
  37.       //   'Sec-WebSocket-Version: 13',
  38.       //   'Accept-Encoding: gzip, deflate, br, zstd',
  39.       //   'Accept-Language: zh-CN,zh;q=0.9',
  40.       //   'Sec-WebSocket-Key: MwGApjw5wYjUrCrC2Rr1Cg==',
  41.       //   'Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits',
  42.       //   '',
  43.       //   ''
  44.       rows = rows.slice(1, -2);
  45.       let headers = {};
  46.       rows.reduce((memo, item) => {
  47.         let [key, value] = item.split(": ");
  48.         memo[key] = value;
  49.         return memo;
  50.       }, headers);
  51.       if (headers["Sec-WebSocket-Version"] == "13") {
  52.         let secWebSocketKey = headers["Sec-WebSocket-Key"];
  53.         let secWebSocketAccept = crypto
  54.           .createHash("sha1")
  55.           .update(secWebSocketKey + CODE)
  56.           .digest("base64");
  57.         let response = [
  58.           "HTTP/1.1 101 Switching Protocols",
  59.           "Upgrade: websocket",
  60.           "Connection: Upgrade",
  61.           `Sec-WebSocket-Accept: ${secWebSocketAccept}`,
  62.           "\r\n",
  63.         ].join("\r\n");
  64.         socket.write(response);
  65.         //后面所有的格式都是基于websocket协议的
  66.         socket.on("data", (buffers) => {
  67.           // 通讯,使用on
  68.           // data默认是一个Buffer
  69.           let fin = buffers[0] & (0b10000000 === 0b10000000); // 结束位是true还是false,第0个字节第一位
  70.           let opcode = buffers[0] & 0b00001111; // 操作码,第0个字节后4位
  71.           let isMask = buffers[1] & (0b10000000 == 0b10000000); // 是否进行了掩码
  72.           let payloadLength = buffers[1] & 0b01111111; // 获得第1个字节后7位
  73.           let mask = buffers.slice(2, 6); // 掩码键,这里假设payloadLength是7位,下面根据这个来写代码
  74.           let payload = buffers.slice(6); // 携带的真实数据
  75.           payload = maskOrUnmask(payload, mask);
  76.           let response = Buffer.alloc(2 + payload.length);
  77.           response[0] = 0b10000000 | opcode;
  78.           response[1] = payloadLength;
  79.           payload.copy(response, 2); // 将 payload 的内容复制到 response 的第二个字节开始的位置,等于把客户端的消息又传了回去
  80.           socket.write(response);
  81.         });
  82.       }
  83.     }
  84.   });
  85. });
  86. server.listen(9999);
  87. function maskOrUnmask(buffer, mask) {
  88.   const length = buffer.length;
  89.   for (let i = 0; i < length; i++) {
  90.     buffer[i] ^= mask[i % 4];
  91.   }
  92.   return buffer;
  93. }
复制代码
  1. **index.html**
  2. <!DOCTYPE html>
  3. <html lang="en">
  4.   <head>
  5.     <meta charset="UTF-8" />
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7.     <title>Document</title>
  8.   </head>
  9.   <body>
  10.     <script>
  11.       let socket = new WebSocket("ws://localhost:9999");
  12.       socket.onopen = function () {
  13.         console.log("1. 客户端连接上了服务器");
  14.         socket.send(
  15.           "hello from the other side, I must've called a thousand times"
  16.         );
  17.       };
  18.       socket.onmessage = function (event) {
  19.         console.log(event.data);
  20.       };
  21.     </script>
  22.   </body>
  23. </html>
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

没腿的鸟

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表