WebSocket协议概述

Web 应用采用 HTTP 协议进行通信:客户端向服务器发送 HTTP 请求,服务器对请求进行处理后,向客户端回复 HTTP 响应。换句话讲,HTTP 协议只能客户端主动发起请求,服务器被动进行响应。

不少应用场景要求服务主动向客户端进行推送,这时 HTTP 协议就有点捉襟见肘了。举个例子,为实现 Web 聊天室功能,当新消息到达时,服务器必须向客户端推送通知。只用 HTTP 协议来实现,我们必须在客户端做轮询。

轮询有个致命的缺陷——性能比较差:如果轮询频率很高,服务器要消耗很多资源;但如果控制轮询频率,应用消息通知的实时性又大打折扣。

很显然,服务器主动向客户端推送数据,也是一个非常常见的应用场景,最好能从网络协议层面进行支持。为此,计算机网络先驱们设计了 WebSocket 协议。

WebSocket 协议,顾名思义为 Web 应用引入了 套接字socket )通信能力。Websocket 是一种应用层协议,以 TCP 为底层传输协议,为通信双方提供了一个 全双工 的信道。

为了兼容 Web 主流应用协议 HTTPWeSocket 复用 80443 端口,并使用 HTTP 请求来建立连接(配合 Upgrade 头部)。因此,WebSocket 可以兼容现有的 HTTP代理 和中间件,例如 Nginx

URL

HTTP 协议一样,WebSocket 服务器地址也用 URL 表示,只是协议部分为 wswss 。例如:

1
2
3
4
5
# ws代表WebSocket协议,端口为80
ws://api.fasionchan.com/chat

# wss代表WebSocket安全协议,与https类似,端口为443
wss://api.fasionchan.com/chat

连接建立

客户端先通过 TCP 协议连到服务器,然后通过 TCP 连接向服务器发送 HTTP 请求。请注意,HTTP 请求头中要带 Upgrade 头部,告诉服务器将连接升级到 WebSocket 协议:

1
2
3
4
5
6
7
8
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 状态码,表示同意切换协议:

1
2
3
4
5
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: lShvB7NL9TbGxezz+KUd5ee6jhA=

HTTP 请求和响应交互完毕后,通信双方就可以在 TCP 连接上互相发送 WebSocket 报文了。

数据帧

连接建好后,通信双方就可以用 WebSocket 协议来发送数据了。WebSocket 将数据组织成一系列帧( frame )来传输,一条应用层消息可以封装成一个或多个数据帧。数据帧报文结构如下所示:

  • 标志位 ,占 4 位,每个标识占一位;

    • FIN 标识表示这是一条消息的最后一帧;
    • RSV1RSV2RSV3 预留给协议拓展用,本文不讨论;
  • 操作代码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 ),占用 028 字节,由前一个字段决定;

  • 掩码Keymasking key ),数据帧开启掩码处理时( MASK=1 )才有,占用 4 个字节,用于掩码计算;

  • 承载数据payload data ),即数据帧承载的应用层数据;

数据长度字段比较复杂,需要分三种情况讨论,分别举个例子帮助理解:

注意到,为了简化报文,我们假设 MASK=0 ,未启动掩码处理。

WebSocket 帧结构看似复杂,但无非还是先分成 头部数据 两大部分,其中头部保存 数据类型(操作码)和 数据长度 ,而操作码又分成控制和非控制两种。

控制帧则继续分为 closepingpong 三种:close 用于关闭连接;pingpong 用于检测连接状态,检测方发 ping ,被检测方回复 pong 。这样当应用暂时没有数据要发送时,ping/pong 可让连接保持活跃。当连接断开时,也能及时检测到。

而非控制帧则分为 textbinary 两种,上层应用使用文本协议,则选 text ;使用二进制协议,则选 binary

总结

  1. WebSocket 兼容 HTTP 协议,借助 HTTP 请求建立连接;
  2. WebSocket 通信分为两个阶段:
    • 连接建立阶段:使用 HTTP 协议完成协议升级;
    • 数据通信阶段:使用 WebSocket 协议互发数据;
  3. WebSocket 通信报文为 ,一个帧由 头部数据 两部分组成;
  4. WebSocket 帧头部保存 操作码数据长度 等字段;
  5. 根据操作码不同,WebSocket 帧可以分成 控制帧非控制帧 两类;
  6. WebSocket 控制帧分为 closepingpong 三种;
    • ping / pong 控制帧用于连接保活和状态检测;
    • close 控制帧用于关闭连接;
  7. WebSocket 非控制帧分为 文本帧二进制帧 两种;

小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注:

【小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注: