【计网】WebSocket
Dandelion 10/15/2022 network
# 概述
# WebSocket 简介
WebSocket 协议运行在 TCP 协议之上,与 HTTP 协议同属于应用层网络数据传输协议。
最大的特点:允许服务端主动向客户端推送数据。
解决的问题:解决 HTTP/1.1 协议实现中客户端只能通过轮询方式获取服务端推送数据造成的资源消耗和消息延时等问题。
客户端和服务端只需要完成一次握手,两者之间就可以建立持久性的连接,并进行双向数据传输。
WebSocket 握手阶段采用的是 HTTP 协议;完成协议握手后,后续的数据通信采用 WebSocket 数据格式通信。
WebSocket 的协议标识符是 ws(WebSocket Over SSL 为 wss)。
- 协议传输形式
- 单工:只能单向传输数据
- 半双工:可以双向传输,但同一时刻只能一个方向传输,如 HTTP/1.x
- 全双工:允许数据在两个方向上同时传输,如 TCP/UDP、HTTP/2、WebSocket
- 协议特点
- 与 HTTP 协议有着良好的兼容性
- WebSocket 默认端口是
80
,WebSocket Over SSL 的默认端口是443
- 由于握手阶段采用的是 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器
- WebSocket 默认端口是
- 数据格式比较轻量,性能开销小
- WebSocket 数据帧格式相对简单,用于协议控制的数据包头部相对较小
- 在不包含扩展的情况下,对于服务端到客户端的内容,此头部大小只有 2 至 10 字节
- 数据实时性较高
- 由于 WebSocket 是全双工的,所以服务端可以随时主动给客户端下发数据
- 相对于 HTTP 请求需要待客户端发起请求服务端才能响应,延迟明显更少,用户体验更好
- 可以发送文本数据,也可以发送二进制数据。数据量较大时,还可以分片多帧进行数据发送与接收
- 与 HTTP 协议有着良好的兼容性
# HTTP 轮询
- 短轮询
- 一般是由客户端每隔一段时间(如每隔 5s)向服务器发起一次普通 HTTP 请求
- 服务端查询当前接口是否有数据更新,若有数据更新则向客户端返回最新数据,若无则提示客户端无数据更新
- 长轮询
- 一般是由客户端向服务端发出一个设置较长网络超时时间的 HTTP 请求,并在 HTTP 连接超时前,不主动断开连接
- 待客户端超时或有数据返回后,再次建立一个同样的 HTTP 请求,重复以上过程
- 缺点
- 客户端需要不断的向服务器发出请求,在消耗较多客户端资源的情况下,服务端并不一定有新的数据下发
- HTTP 请求与响应消息中,需包含较长的头部信息,其中真正有效的数据有可能只占较小的一部分,带来较多的带宽资源消耗
- 若服务端存在连续频繁的数据变化(如聊天室、弹幕),客户端获知数据更新相对较慢,无法保证客户端的用户体验
- 如何在减少资源消耗的同时,提高客户端的用户体验?WebSocket!
# 协议握手阶段
- WebSocket 协议握手复用了 HTTP 协议:
- 客户端通过 HTTP 请求与服务端协商升级协议到 websocket 协议
- 服务端通过 HTTP 响应数据回应客户端的升级请求,完成协议握手
- 握手请求
- 客户端通过 HTTP/1.1 协议,发送 GET 请求发起协议升级请求
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Version: 13 Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
1
2
3
4
5
6 - 请求 Header 头字段说明
Connection: Upgrade
:表示要升级协议Upgrade: websocket
:表示要升级到 websocket 协议Sec-WebSocket-Version: 13
:表示 websocket 的版本Sec-WebSocket-Key
:与服务端响应首部的Sec-WebSocket-Accept
是配套的,提供基本的防护,比如恶意或无意连接
- 客户端通过 HTTP/1.1 协议,发送 GET 请求发起协议升级请求
- 握手响应
- 服务端通过在 HTTP 响应中返回
101
状态码返回响应数据,完成协议握手HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
1
2
3
4 - 响应 Header 头字段说明
101 Switching Protocols
:HTTP 协议切换为 WebSocket 协议Sec-WebSocket-Accept
:与请求数据中的Sec-WebSocket-Key
数据对应,根据请求首部计算
- 服务端通过在 HTTP 响应中返回
- Sec-WebSocket-Accept 计算
Sec-WebSocket-Accept
根据客户端请求首部的Sec-WebSocket-Key
进行计算- 计算公式:
- 将
Sec-WebSocket-Key
跟258EAFA5-E914-47DA-95CA-C5AB0DC85B11
拼接 - 通过
SHA1
计算出摘要,并转成base64
字符串
- 将
# 数据传输阶段
- 在完成协议握手后,后续客户端与服务端数据交换均需要遵循 WebSocket 协议进行
- WebSocket 客户端和服务端通信的最小单位是帧,由 1 个或多个帧组成一条完整的消息
- 发送端:将消息切割成多个帧,发送给服务端
- 服务端:接收消息帧,并将关联的帧重新组装成完整的消息数据
- RFC6455 (opens new window) 定义了
WebSocket 数据帧
的统一格式- 单位为比特,从左到右 FIN、RSV 等各占据 1 比特位,opcode 占据 4 比特
FIN(Final)
:是否为最后一个分片,1 为是,0 为否RSV1/RSV2/RSV3(Reserved)
:扩展字段,一般情况下全为 0- 当客户端、服务端协商采用 WebSocket 扩展时,这三个标志位可以非 0,且值的含义由扩展进行定义
- 详细说明可见:RFC6455 (opens new window)
Opcode
:操作码值,决定了应该如何解析后续的数据载荷Mask
:掩码,表示是否要对数据载荷进行掩码操作- 从客户端向服务端发送数据时,需要对数据进行掩码操作
- 从服务端向客户端发送数据时,不需要对数据进行掩码操作
- 若 Mask 是 1,则在
Masking-key
中会定义一个掩码键,并用该掩码键来对数据载荷进行反掩码
# 关闭连接阶段
- 当 WebSocket 不再需要时,客户端或服务端可以选择关闭 WebSocket 连接
- 连接关闭状态码(Status Code)
1000
:正常关闭连接1001
:表示某个端正在离开,例如服务器关闭或客户端已离开页面1002
:websocket 协议错误1003
:正在关闭连接,某个端接受了不支持的数据格式
# 心跳信息
- WebSocket 是长连接,需要间隔一段发送 Ping/Pong 心跳,以抵挡运营商的 Nat 超时,来维持 TCP 连接不断开
- 客户端需每隔一段时间(心跳间隔时间)发送一个 Ping 消息,到远端服务器侧
- 服务端收到客户端的 Ping 消息后,需在 10s 内回复一个 Pong 消息
# WebSocket 具体实践
var ws = new WebSocket('wss://echo.websocket.org');
ws.onopen = function (evt) {
console.log('Connection open ...');
ws.send('Hello WebSockets!');
};
ws.onmessage = function (evt) {
console.log('Received Message: ' + evt.data);
ws.close();
};
ws.onclose = function (evt) {
console.log('Connection closed.');
};
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 与 WebSocket 相关的问题
- WebSocket 和 Socket 的区别
- WebSocket 和 HTTP 的区别
- WebSocket 的应用场景
- WebSocket 频繁掉线怎么办