Web应用 采用 HTTP 协议进行通信:客户端向服务器发送 HTTP 请求,服务器对请求进行处理后,向客户端回复 HTTP 响应。换句话讲,HTTP 协议只能客户端主动发起请求,服务器被动进行响应。
图片
不少应用场景要求服务主动向客户端进行推送,这时 HTTP 协议就有点捉襟见肘了。举个例子,为实现 Web 聊天室功能,当新消息到达时,服务器必须向客户端推送通知。只用 HTTP 协议来实现,我们必须在客户端做轮询。
轮询有个致命的缺陷——性能比较差:如果轮询频率很高,服务器要消耗很多资源;但如果控制轮询频率,应用消息通知的实时性又大打折扣。
很显然,服务器主动向客户端推送数据,也是一个非常常见的应用场景,最好能从网络协议层面进行支持。为此,计算机网络先驱们设计了 WebSocket 协议。
WebSocket 协议,顾名思义为 Web 应用引入了 套接字( socket )通信能力。Websocket 是一种应用层协议,以 TCP 为底层传输协议,为通信双方提供了一个 全双工 的信道。
为了兼容 Web 主流应用协议 HTTP ,WeSocket 复用 80 和 443 端口,并使用 HTTP 请求来建立连接(配合 Upgrade 头部)。因此,WebSocket 可以兼容现有的 HTTP代理 和中间件,例如 Nginx 。
URL
和 HTTP 协议一样,WebSocket 服务器地址也用 URL 表示,只是协议部分为 ws 或 wss 。例如:
# ws代表WebSocket协议,端口为80
ws://api.fasionchan.com/chat
# wss代表WebSocket安全协议,与https类似,端口为443
wss://api.fasionchan.com/chat
连接建立
客户端先通过 TCP 协议连到服务器,然后通过 TCP 连接向服务器发送 HTTP 请求。请注意,HTTP 请求头中要带 Upgrade 头部,告诉服务器将连接升级到 WebSocket 协议:
GET /chat HTTP/1.1
Host: localhost:8080
User-Agent: Go-http-client/1.1
Connection: Upgrade
Sec-WebSocket-Key: bLPIf/xpAnrtCKuifPKTUg==
Sec-WebSocket-Version: 13
Upgrade: websocket
服务器接到请求后,检查 Upgrade 头部,发现客户端想将连接协议升级到 WebSocket 。如果应用服务器支持 WebSocket ,它便回复 101 状态码,表示同意切换协议:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: lShvB7NL9TbGxezz+KUd5ee6jhA=
图片
HTTP 请求和响应交互完毕后,通信双方就可以在 TCP 连接上互相发送 WebSocket 报文了。
数据帧
连接建好后,通信双方就可以用 WebSocket 协议来发送数据了。WebSocket 将数据组织成一系列帧( frame )来传输,一条应用层消息可以封装成一个或多个数据帧。数据帧报文结构如下所示:
图片
- 标志位 ,占 4每个标识占一位;
a.FIN
b.RSV1 、RSV2 和 RSV3
a.0 这是前面数据帧的续帧(一个消息封装成多个帧时);
b.1表示这是一个文本帧;
c.2 表示这是一个二进制( binary )帧;
d3~7 保留,未来可以分配给新的非控制( non-control )帧;
e.8 表示这是一个连接关闭( close )帧;
f.9 表示这是一个 ping 帧;
g.10 表示这是一个 pong 帧;
h.11~15 保留,未来可以分配给新的控制( control )帧;
- MASK 位,表示 承载数据( payload data )是否做掩码处理,1 表示掩码处理,0 表示不做掩码处理(客户端发的帧必须做掩码处理,主要出于避免网络中间件混淆和安全上的考虑);
- 数据长度( payload len ),占 7 位,用来表示数据负载的长度(以字节为单位);
a.该字段小于 126 时,该字段直接表示数据长度,其后的扩展字段为空( 0 字节),可表示 0~125 字节的数据;
b.当该字段等于 126 时,数据长度由其后的扩展字段表示,这是扩展字段为 2 字节,可表示长度为 126~65535 字节的数据;
c.
- 扩展数据长度( extended payload len ),占用 0 、2 或 8 字节,由前一个字段决定;
- 掩码Key( masking key ),数据帧开启掩码处理时( MASK=1 )才有,占用 4 个字节,用于掩码计算;
- 承载数据( payload data ),即数据帧承载的应用层数据;
数据长度字段比较复杂,需要分三种情况讨论,分别举个例子帮助理解:
图片
注意到,为了简化报文,我们假设 MASK=0 ,未启动掩码处理。
WebSocket 帧结构看似复杂,但无非还是先分成 头部 和 数据 两大部分,其中头部保存 数据类型(操作码)和 数据长度 ,而操作码又分成控制和非控制两种。
控制帧则继续分为 close 、ping 和 pong 三种:close 用于关闭连接;ping 和 pong 用于检测连接状态,检测方发 ping ,被检测方回复 pong 。这样当应用暂时没有数据要发送时,ping/pong 可让连接保持活跃。当连接断开时,也能及时检测到。
而非控制帧则分为 text 和 binary 两种,上层应用使用文本协议,则选 text ;使用二进制协议,则选 binary 。
总结
- WebSocket 兼容 HTTP 协议,借助 HTTP 请求建立连接;
- WebSocket 通信分为两个阶段:
- 连接建立阶段:使用 HTTP
- 数据通信阶段:使用 WebSocket
- WebSocket 通信报文为 帧 ,一个帧由 头部 和 数据 两部分组成;
- WebSocket 帧头部保存 操作码 和 数据长度 等字段;
- 根据操作码不同,WebSocket 帧可以分成 控制帧 和 非控制帧 两类;
- WebSocket 控制帧分为 close 、ping 和 pong 三种;
- ping / pong 控制帧用于连接保活和状态检测;
- close 控制帧用于关闭连接;
- WebSocket 非控制帧分为 文本帧 和 二进制帧 两种;