websocket 调研

2022-09-30  本文已影响0人  给艺艺一个未来

websocket 介绍

背景

在 Websocket 诞生之前,服务器的数据需要传达至客户端,通常需要由客户端轮询或长轮询(Long-Polling)的方式获取。

WebSocket 协议主要为了解决基于 HTTP/1.x 的 Web 应用无法实现服务端向客户端主动推送的问题, 为了兼容现有的设施, WebSocket 协议使用与 HTTP 协议相同的端口, 并使用 HTTP Upgrade 机制来进行 WebSocket 握手, 当握手完成之后, 通信双方便可以按照 WebSocket 协议的方式进行交互。

简介

WebSocket 使用 TCP 作为传输层协议, WebSocket 使得客户端和服务器之间保持长连接,通过 Websocket 握手建立 Websocket 连接,通过 Websocket 挥手 关闭 Websocket 连接,可采用 ping-pong 保活,使用 帧 传输数据。

在 WebSocket 协议中, 帧 (frame) 是通信双方数据传输的基本单元, 与其它网络协议相同, frame 由 Header 和 Payload 两部分构成, frame 有多种类型, frame 的类型由其头部的 Opcode 字段来指示。

WebSocket 的 frame 可以分为两类:

  1. 用于传输控制信息的 frame (如通知对方关闭 WebSocket 连接),
  2. 用于传输应用数据的 frame, 使用 WebSocket 协议通信的双方都需要首先进行握手,

注意:只有当握手成功之后才开始使用 frame 传输数据

WebSocket 支持在 TCP 上层引入 TLS,ws 和 wss 的关系就类似 http 和 https 的关系。

websocket 握手

客户端发起:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: chat, superchat

服务器响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: chat

服务器若支持 WebSocket 协议, 并同意与客户端握手, 则应返回 101 的 HTTP 状态码, 表示同意协议升级, 同时设置 Upgrade 字段的值为 websocket, 并将 Connection 字段的值设置为 Upgrade 。

Sec-WebSocket-Accept 的计算方法:

客户端对服务器响应的校验:

  1. 检查服务端返回的状态码是否为 101
  2. 检查服务端返回的响应是否包含 Upgrade 字段
  3. 检查 Upgrade 字段的值是否为 websocket(大小写不敏感)
  4. 校验服务端返回的 Sec-WebSocket-Accept 字段的值是否合法(注:采用相同的Sec-WebSocket-Accept 的计算方法,确保服务器返回的 Sec-WebSocket-Accept 和 客户端本地生成的 Sec-WebSocket-Accept 一致)
  5. 若 Sec-WebSocket-Protocol 存在,则校验服务端返回的 Header 中包含的 Sec-WebSocket-Protocol 中的值是否属于客户端发起的 Sec-WebSocket-Protocol 的值列表中的值
  6. 若 Sec-WebSocket-Extensions 存在,则校验服务端返回的 Header 中包含的 Sec-WebSocket-Extensions 中的值是否属于客户端发起的 Sec-WebSocket-Extensions 的值列表中的值

注意:Sec-WebSocket-Key/Sec-WebSocket-Accept 的换算,只能带来基本的保障,但连接是否安全、数据是否安全、客户端 / 服务端是否合法的 ws 客户端、ws 服务端,其实并没有实际性的保证。

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 ...                |
     +---------------------------------------------------------------+

continuation frame 举例:在传递数据时, 会先收到一个 binary frame, 它的 FIN 是0, 后续的数据会以 continuation frame 的形式发送, 直到最后一个frame的 FIN位是 1 的 continuation frame 结束, 中间不会穿插其它的 frame 。同理, text frame 也是如此实现。

关于 Payload Len 如何实现:先读 7 bit 识别是以上3种的哪一种长度类型,再决定是否向后读 16 bit 或 64 bit 。

WebSocket closing handshake

  1. websocket handshake 从一端发送一个 close control frame 开始。
  2. 发送一个 close control frame 或收到一个 close control frame 都意味着 websocket handshake 开始,并且 websocket connection 进入 CLOSING 状态。
  3. 收到 close control frame 的一端需答复对端一个 close control frame,并关闭 TCP connection。
  4. 收到 close control frame 答复的端,关闭 TCP connection,完成 TCP 挥手后,TCP connection 关闭。
  5. 当 TCP connection 关闭后,websocket connection 关闭,并且 websocket connection 进入 CLOSED 状态。
  6. 如果 TCP conncetion 是在 WebSocket closing handshake 之后完成,那么 Websocket connection 可以说是 干净地关闭的。

websocket server

nodejs 的 websocket server

ws 是一个第三方的 websocket 通信模块,需要安装 npm i ws

const WebSocket = require('ws')
const WebSocketServer = WebSocket.Server;

// wss is WebSocket.Server
wss.on("connection", function(ws, request) {
    // ws is WebSocket
    ws.on("message", (data, isBinary) => {
        if (isBinary) {
            console.log("recv binary data");
            ws.send("recv binary data success", {mask: false, binary: true, compress: false, fin: true}, (error) => {
                if (error) {
                    console.log("send data callback : error = ${error}");
                }
            });
        } else {
            console.log("recv text data");
            ws.send("recv text data success", {mask: false, binary: false, compress: false, fin: true}, (error) => {
                if (error) {
                    console.log("send data callback : error = ${error}");
                }
            });
        }
    });

    ws.on("ping", (data) => {
        // keepalive
        ws.pong(data, false, (err) => {
            if (err) {
                console.log("pong error=${err}");
            }
        });
        console.log("pong");
    });

    ws.on("pong", (data) => {
        // keepalive
        console.log("ping");
    });

    ws.on("close", (code, reason) => {
        console.log("websocket close");
    });
});

nginx 反向代理

《Nginx官方文档:WebSocket proxying》

为了将客户机和服务器之间的连接从 HTTP/1.1转换为 WebSocket,使用 HTTP/1.1中提供的协议切换机制。

HTTP/1.1中提供的协议切换机制:客户端通过请求中的 "Upgrade" header请求协议切换。

本来在 nginx 中,如上所述,包括“Upgrade” 和 “Connection”在内的 hop-by-hop headers 不会从客户机传递到被代理的服务器,因此,为了让被代理的服务器知道客户机将协议切换到 WebSocket 的意图,必须显式地传递这些消息头:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

nginx 配置:


# upstream in http config field
upstream WebsocketServer {
    127.0.0.1:1106;
}

# location in server config field
location /webscoket_test/ {
    proxy_pass WebsocketServer;
    proxy_http_version 1.1;
    proxy_read_timeout  3600s; # 超时设置,可采用心跳ping/pong保活
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

扩展知识

End-to-end headers

Hop-by-hop headers

来源:hop-by-hop

HTTP/1.1 中 hop-to-hop 类型的消息头:

注意

测试工具

wscat

oktools

http://oktools.net/websocket

websocket test

websocket client

JavaScript 的 websocket client

<!DOCTYPE html>
<html>
<head>
  <title>websocket</title>
</head>
<body>

  <script type="text/javascript">
    // 浏览器提供 WebSocket 对象
    var ws = new WebSocket('ws://localhost:1106')

    // 发送
    ws.onopen = function() {
      ws.send('hello')
    }

    // 接收
    ws.onmessage = function(message) { 
      alert(message.data) 
      if (message.data === 'hello') {
          ws.close()
      }
    }
  </script>

</body>
</html>
上一篇下一篇

猜你喜欢

热点阅读