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 。例如:
|
|
连接建立
客户端先通过 TCP 协议连到服务器,然后通过 TCP 连接向服务器发送 HTTP 请求。请注意,HTTP 请求头中要带 Upgrade 头部,告诉服务器将连接升级到 WebSocket 协议:
|
|
服务器接到请求后,检查 Upgrade 头部,发现客户端想将连接协议升级到 WebSocket 。如果应用服务器支持 WebSocket ,它便回复 101 状态码,表示同意切换协议:
|
|
HTTP 请求和响应交互完毕后,通信双方就可以在 TCP 连接上互相发送 WebSocket 报文了。
数据帧
连接建好后,通信双方就可以用 WebSocket 协议来发送数据了。WebSocket 将数据组织成一系列帧( frame )来传输,一条应用层消息可以封装成一个或多个数据帧。数据帧报文结构如下所示:
-
标志位 ,占 4 位,每个标识占一位;
- FIN 标识表示这是一条消息的最后一帧;
- RSV1 、RSV2 和 RSV3 预留给协议拓展用,本文不讨论;
-
操作代码 ( opcode ),占 4 位,表示帧的类型,其中 0~7 表示非控制( non-control )帧,8~15 表示控制( control )帧(非控制帧用于承载应用数据,控制帧用于连接控制);
-
0 这是前面数据帧的续帧(一个消息封装成多个帧时);
-
1 表示这是一个文本帧;
-
2 表示这是一个二进制( binary )帧;
-
3~7 保留,未来可以分配给新的非控制( non-control )帧;
-
8 表示这是一个连接关闭( close )帧;
-
9 表示这是一个 ping 帧;
-
10 表示这是一个 pong 帧;
-
11~15 保留,未来可以分配给新的控制( control )帧;
-
-
MASK 位,表示 承载数据( payload data )是否做掩码处理,1 表示掩码处理,0 表示不做掩码处理(客户端发的帧必须做掩码处理,主要出于避免网络中间件混淆和安全上的考虑);
-
数据长度( payload len ),占 7 位,用来表示数据负载的长度(以字节为单位);
- 该字段小于 126 时,该字段直接表示数据长度,其后的扩展字段为空( 0 字节),可表示 0~125 字节的数据;
- 当该字段等于 126 时,数据长度由其后的扩展字段表示,这是扩展字段为 2 字节,可表示长度为 126~65535 字节的数据;
- 当该字段等于 127 时,数据长度由其后的扩展字段表示,这时扩展字段为 8 字节,可表示长度为 $2^{16}$ 到 $2^{64}-1$ 字节的数据;
-
扩展数据长度( 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 非控制帧分为 文本帧 和 二进制帧 两种;
【小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注: