ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

WebSocket 理解

2021-08-02 08:02:05  阅读:181  来源: 互联网

标签:const 理解 Sec WebSocket bit data 服务端


HTTP 协议在设计上就是一个单向的网络协议,服务器只能被动的接收请求,然后返回相应的数据。对于需要双向通信的场景,虽然可以通过轮询,Comet 等方式实现,但每次链接都要三次握手,效率低下。

 

与http比较:

   1.都基于 TCP 的、应用层的可靠性传输协议

   2.WebSocket 在握手时的数据是通过 HTTP 传输的,一旦连接建立后就不再依赖 HTTP 了

社区开源方案

    socket.io,  ws 等

webSocket的应用场景

  • 通知: 由业务服务端发起,由客户端接收的场景,这类场景下业务通常会有兜底逻辑
  • 聊天:服务端和客户端发双向消息进行交互,用在聊天场景
  • 游戏:服务端和客户端做高频消息交互
  • 语音:从客户端持续不断产生语音包,语音包由大语音包切分而来,需要在服务端重新做组合,要求大包传输 + 顺序性保证
  • 直播:大量用户加入同一个直播间,同一直播间内的用户可发弹幕,礼物
  • ioT:边缘节点设备,如单车,共享充电宝等
  • 数据上报:从客户端持续上报数据到服务端

通信建立:

  • cloent向服务端发出一个 Upgrade: WebSocket 的协议升级 HTTP 请求,该请求附带了一个标识 Sec-WebSocket-Key; 
  • 服务端接收到协议升级请求后返回,其状态码为 101 ,表明服务端已经成功升级为 WebSocket 协议了。该信息中同样也包含了一个标识 Sec-WebSocket-Accept,该标识符是服务端根据客户端发请求中的 Sec-WebSocket-Key 值计算出来的;
  • 客户端接受到服务器的返回后,会判断服务端返回的 Sec-WebSocket-Accept 标识是否和发出 Sec-WebSocket-Key 对应,如果不是就会抛出一个 “Error during WebSocket handshake” 的错误并关闭连接。

Sec-WebSocket-Accept 值的计算:

  • Sec-WebSocket-Key258EAFA5-E914-47DA-95CA-C5AB0DC85B11 做字符串拼接;
  • 通过 SHA1 计算出摘要,并转成 base64 字符串。

 

上代码

 client 建立socket链接

 

...
<script>
  const host = '127.0.0.1';
  const port = 8001;
  const ws = new WebSocket(`ws://${host}:${port}`);
</script>

  

node端监听 WebSocket 升级请求,返回升级成功的数据:

server.on('upgrade', (req, socket) => {
  if (req.headers['upgrade'] !== 'websocket') {
    res.end('HTTP/1.1 400 Bad Request');
    return;
  }
  const secWsKey = req.headers['sec-websocket-key'];
  const secWsAccept = generateSecWsAccept(secWsKey);
  const responseHeaders = [
    'HTTP/1.1 101 Web Socket Protocol Handshake',
    'Upgrade: WebSocket',
    'Connection: Upgrade',
    'Sec-WebSocket-Accept: ' + secWsAccept
  ];
  socket.write(responseHeaders.join('\r\n') + '\r\n\r\n');
}

  

Sec-WebSocket-Accept值生成函数

function generateSecWsAccept (secWsKey) {
  return crypto
    .createHash('SHA1')
    .update(secWsKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
    .digest('base64');
}

 

开浏览器调试工具,可以看到 WebSocket 通信确实建立起来了。

 

 

 

 前端发送消息

<script>
  ...
  ws.addEventListener('open', () => {
    ws.send('I am client.');
  });
</script>

服务端监听消息

server.on('upgrade', (req, socket) => {
  ...
  socket.on('data', (data) => {
    console.log(data.toString());
  })
});

数据帧解析

此时发现服务端监听打印的信息是乱码,因为这里接收的 data 并不完全等同于消息的信息,拿到的是 WebSocket 的数据帧。

WebSocket 通信的最小信息单位就是帧,一个或多个帧构成一条完成的消息。
 0                   1                   2                   3
 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
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

 

字段名

长度.                  

说明

FIN

1bit

表示是否是信息的最后一帧

RVS

3 bit

默认都是 0

opcode

4 bit

表示帧的类型。例如文本或者二进制

MASK

1 bit

表示是否加密数据。默认为 1

Payload len

7 / 7+16 / 7+64 bit

表示数据长度(Bytes)。因为 7 位无符号整数的最大值为 127,为了能表示更大的数据长度,这里规定当这 7 位表示的值等于 126 时,后 16 bit 表示数据长度;当这 7 位表示的值等于 127 时,后 64 bit 表示数据长度

Marking-Key

32 bit

加密掩码,当 MASK 的值为 1 时存在

Payload Data

 

数据

      假定数据的长度一定是小于 126 Byte 的,忽略 Payload len 长度的各种判断
socket.on('data', (data) => {
  // Receive message
  const payloadLen = data[1] & parseInt(1111111, 2); // 假设发送的数据长度小于 125
  const maskingKey = data.slice(2, 6);
  const payloadData = new Buffer(payloadLen);
  for (let i = 0; i < payloadLen; i++) {
    let j = i % 4;
    payloadData[i] = data[6 + i] ^ maskingKey[j];
  }
  console.log(payloadData.toString());
});

  

标签:const,理解,Sec,WebSocket,bit,data,服务端
来源: https://www.cnblogs.com/breakdown/p/15088414.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有