38 | WebSocket:沙盒里的TCP

在之前讲TCP/IP协议栈的时候,我说过有“TCP Socket”,它实际上是一种功能接口,通过这些接口就可以使用TCP/IP协议栈在传输层收发数据。

那么,你知道还有一种东西叫“WebSocket”吗?

单从名字上看,“Web”指的是HTTP,“Socket”是套接字调用,那么这两个连起来又是什么意思呢?

所谓“望文生义”,大概你也能猜出来,“WebSocket”就是运行在“Web”,也就是HTTP上的Socket通信规范,提供与“TCP Socket”类似的功能,使用它就可以像“TCP Socket”一样调用下层协议栈,任意地收发数据。

更准确地说,“WebSocket”是一种基于TCP的轻量级网络通信协议,在地位上是与HTTP“平级”的。

为什么要有WebSocket

不过,已经有了被广泛应用的HTTP协议,为什么要再出一个WebSocket呢?它有哪些好处呢?

其实WebSocket与HTTP/2一样,都是为了解决HTTP某方面的缺陷而诞生的。HTTP/2针对的是“队头阻塞”,而WebSocket针对的是“请求-应答”通信模式。

那么,“请求-应答”有什么不好的地方呢?

“请求-应答”是一种“半双工”的通信模式,虽然可以双向收发数据,但同一时刻只能一个方向上有动作,传输效率低。更关键的一点,它是一种“被动”通信模式,服务器只能“被动”响应客户端的请求,无法主动向客户端发送数据。

虽然后来的HTTP/2、HTTP/3新增了Stream、Server Push等特性,但“请求-应答”依然是主要的工作方式。这就导致HTTP难以应用在动态页面、即时消息、网络游戏等要求“实时通信”的领域。

在WebSocket出现之前,在浏览器环境里用JavaScript开发实时Web应用很麻烦。因为浏览器是一个“受限的沙盒”,不能用TCP,只有HTTP协议可用,所以就出现了很多“变通”的技术,“轮询”(polling)就是比较常用的的一种。

简单地说,轮询就是不停地向服务器发送HTTP请求,问有没有数据,有数据的话服务器就用响应报文回应。如果轮询的频率比较高,那么就可以近似地实现“实时通信”的效果。

但轮询的缺点也很明显,反复发送无效查询请求耗费了大量的带宽和CPU资源,非常不经济。

所以,为了克服HTTP“请求-应答”模式的缺点,WebSocket就“应运而生”了。它原来是HTML5的一部分,后来“自立门户”,形成了一个单独的标准,RFC文档编号是6455。

WebSocket的特点

WebSocket是一个真正“全双工”的通信协议,与TCP一样,客户端和服务器都可以随时向对方发送数据,而不用像HTTP“你拍一,我拍一”那么“客套”。于是,服务器就可以变得更加“主动”了。一旦后台有新的数据,就可以立即“推送”给客户端,不需要客户端轮询,“实时通信”的效率也就提高了。

WebSocket采用了二进制帧结构,语法、语义与HTTP完全不兼容,但因为它的主要运行环境是浏览器,为了便于推广和应用,就不得不“搭便车”,在使用习惯上尽量向HTTP靠拢,这就是它名字里“Web”的含义。

服务发现方面,WebSocket没有使用TCP的“IP地址+端口号”,而是延用了HTTP的URI格式,但开头的协议名不是“http”,引入的是两个新的名字:“ws”和“wss”,分别表示明文和加密的WebSocket协议。

WebSocket的默认端口也选择了80和443,因为现在互联网上的防火墙屏蔽了绝大多数的端口,只对HTTP的80、443端口“放行”,所以WebSocket就可以“伪装”成HTTP协议,比较容易地“穿透”防火墙,与服务器建立连接。具体是怎么“伪装”的,我稍后再讲。

下面我举几个WebSocket服务的例子,你看看,是不是和HTTP几乎一模一样:

ws://www.chrono.com
ws://www.chrono.com:8080/srv
wss://www.chrono.com:445/im?user_id=xxx

要注意的一点是,WebSocket的名字容易让人产生误解,虽然大多数情况下我们会在浏览器里调用API来使用WebSocket,但它不是一个“调用接口的集合”,而是一个通信协议,所以我觉得把它理解成“TCP over Web”会更恰当一些。

WebSocket的帧结构

刚才说了,WebSocket用的也是二进制帧,有之前HTTP/2、HTTP/3的经验,相信你这次也能很快掌握WebSocket的报文结构。

不过WebSocket和HTTP/2的关注点不同,WebSocket更侧重于“实时通信”,而HTTP/2更侧重于提高传输效率,所以两者的帧结构也有很大的区别。

WebSocket虽然有“帧”,但却没有像HTTP/2那样定义“流”,也就不存在“多路复用”“优先级”等复杂的特性,而它自身就是“全双工”的,也就不需要“服务器推送”。所以综合起来,WebSocket的帧学习起来会简单一些。

下图就是WebSocket的帧结构定义,长度不固定,最少2个字节,最多14字节,看着好像很复杂,实际非常简单。

开头的两个字节是必须的,也是最关键的。

第一个字节的第一位“FIN”是消息结束的标志位,相当于HTTP/2里的“END_STREAM”,表示数据发送完毕。一个消息可以拆成多个帧,接收方看到“FIN”后,就可以把前面的帧拼起来,组成完整的消息。

“FIN”后面的三个位是保留位,目前没有任何意义,但必须是0。

第一个字节的后4位很重要,叫“Opcode”,操作码,其实就是帧类型,比如1表示帧内容是纯文本,2表示帧内容是二进制数据,8是关闭连接,9和10分别是连接保活的PING和PONG。

第二个字节第一位是掩码标志位“MASK”,表示帧内容是否使用异或操作(xor)做简单的加密。目前的WebSocket标准规定,客户端发送数据必须使用掩码,而服务器发送则必须不使用掩码。

第二个字节后7位是“Payload len”,表示帧内容的长度。它是另一种变长编码,最少7位,最多是7+64位,也就是额外增加8个字节,所以一个WebSocket帧最大是2^64。

长度字段后面是“Masking-key”,掩码密钥,它是由上面的标志位“MASK”决定的,如果使用掩码就是4个字节的随机数,否则就不存在。

这么分析下来,其实WebSocket的帧头就四个部分:“结束标志位+操作码+帧长度+掩码”,只是使用了变长编码的“小花招”,不像HTTP/2定长报文头那么简单明了。

我们的实验环境利用OpenResty的“lua-resty-websocket”库,实现了一个简单的WebSocket通信,你可以访问URI“/38-1”,它会连接后端的WebSocket服务“ws://127.0.0.1/38-0”,用Wireshark抓包就可以看到WebSocket的整个通信过程。

下面的截图是其中的一个文本帧,因为它是客户端发出的,所以需要掩码,报文头就在两个字节之外多了四个字节的“Masking-key”,总共是6个字节。

而报文内容经过掩码,不是直接可见的明文,但掩码的安全强度几乎是零,用“Masking-key”简单地异或一下就可以转换出明文。

WebSocket的握手

和TCP、TLS一样,WebSocket也要有一个握手过程,然后才能正式收发数据。

这里它还是搭上了HTTP的“便车”,利用了HTTP本身的“协议升级”特性,“伪装”成HTTP,这样就能绕过浏览器沙盒、网络防火墙等等限制,这也是WebSocket与HTTP的另一个重要关联点。

WebSocket的握手是一个标准的HTTP GET请求,但要带上两个协议升级的专用头字段:

  • “Connection: Upgrade”,表示要求协议“升级”;
  • “Upgrade: websocket”,表示要“升级”成WebSocket协议。

另外,为了防止普通的HTTP消息被“意外”识别成WebSocket,握手消息还增加了两个额外的认证用头字段(所谓的“挑战”,Challenge):

  • Sec-WebSocket-Key:一个Base64编码的16字节随机数,作为简单的认证密钥;
  • Sec-WebSocket-Version:协议的版本号,当前必须是13。

服务器收到HTTP请求报文,看到上面的四个字段,就知道这不是一个普通的GET请求,而是WebSocket的升级请求,于是就不走普通的HTTP处理流程,而是构造一个特殊的“101 Switching Protocols”响应报文,通知客户端,接下来就不用HTTP了,全改用WebSocket协议通信。(有点像TLS的“Change Cipher Spec”)

WebSocket的握手响应报文也是有特殊格式的,要用字段“Sec-WebSocket-Accept”验证客户端请求报文,同样也是为了防止误连接。

具体的做法是把请求头里“Sec-WebSocket-Key”的值,加上一个专用的UUID “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,再计算SHA-1摘要。

encode_base64(
  sha1( 
    Sec-WebSocket-Key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' ))

客户端收到响应报文,就可以用同样的算法,比对值是否相等,如果相等,就说明返回的报文确实是刚才握手时连接的服务器,认证成功。

握手完成,后续传输的数据就不再是HTTP报文,而是WebSocket格式的二进制帧了。

小结

浏览器是一个“沙盒”环境,有很多的限制,不允许建立TCP连接收发数据,而有了WebSocket,我们就可以在浏览器里与服务器直接建立“TCP连接”,获得更多的自由。

不过自由也是有代价的,WebSocket虽然是在应用层,但使用方式却与“TCP Socket”差不多,过于“原始”,用户必须自己管理连接、缓存、状态,开发上比HTTP复杂的多,所以是否要在项目中引入WebSocket必须慎重考虑。

  1. HTTP的“请求-应答”模式不适合开发“实时通信”应用,效率低,难以实现动态页面,所以出现了WebSocket;
  2. WebSocket是一个“全双工”的通信协议,相当于对TCP做了一层“薄薄的包装”,让它运行在浏览器环境里;
  3. WebSocket使用兼容HTTP的URI来发现服务,但定义了新的协议名“ws”和“wss”,端口号也沿用了80和443;
  4. WebSocket使用二进制帧,结构比较简单,特殊的地方是有个“掩码”操作,客户端发数据必须掩码,服务器则不用;
  5. WebSocket利用HTTP协议实现连接握手,发送GET请求要求“协议升级”,握手过程中有个非常简单的认证机制,目的是防止误连接。

课下作业

  1. WebSocket与HTTP/2有很多相似点,比如都可以从HTTP/1升级,都采用二进制帧结构,你能比较一下这两个协议吗?
  2. 试着自己解释一下WebSocket里的”Web“和”Socket“的含义。
  3. 结合自己的实际工作,你觉得WebSocket适合用在哪些场景里?

欢迎你把自己的学习体会写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,也欢迎把文章分享给你的朋友。

unpreview

精选留言

  • 许童童

    2019-08-23 11:59:53

    思考题:
    1.WebSocket 和 HTTP/2 都是用来弥补HTTP协议的一些缺陷和不足,WebSocket 主要解决双向通信、全双工问题,HTTP/2 主要解决传输效率的问题,两者在二进制帧的格式上也不太一样,HTTP/2 有多路复用、优先级和流的概念。

    2.试着自己解释一下 WebSocket 里的”Web“和”Socket“的含义。
    Web就是HTTP的意思,Socket就是网络编程里的套接字,也就是HTTP协议上的网络套接字,可以任意双向通信。

    3.结合自己的实际工作,你觉得 WebSocket 适合用在哪些场景里?
    IM通信,实时互动,回调响应,数据实时同步。
    作者回复

    great。

    2019-08-23 14:54:07

  • cugphoenix

    2020-02-07 22:10:37

    是不是可以这样理解:HTTP是基于TCP的,通过TCP收发的消息用HTTP的应用层协议解析。WebSocket是首先通过HTTP协议把TCP链接建好,然后通过Upgrade字段进行协议转换,在收到服务器的101 Switching Protocols应答之后,后续的TCP消息就通过WebSocket协议解析。
    作者回复

    理解的基本正确,把WebSocket和http、tcp的关系理顺了。

    2020-02-08 11:41:22

  • geraltlaush

    2019-08-23 06:42:44

    工作场景遇到过用户订阅股票的股价,股价波动时实时推送给海量订阅的用户,面试场景被问到两次,一 千万粉丝的明星发布动态如何推送给粉丝 二 海量用户的主播直播如何推送弹幕 当时回答消息队列,其实web socket才是比较好的方案
    作者回复

    WebSocket适合实时通信交互的场景,和消息队列其实是两个领域,不冲突,可以互相结合使用。

    2019-08-23 08:52:55

  • 大土豆

    2020-02-28 12:37:29

    WebSocket定义为tcp over web,我个人感觉是不妥的,应该是"可靠有序的传输层 + 实现组包协议的应用层的长连接方案 over web"。WebSocket和HTTP已经有包的结构了,业务直接可以用了,浏览器A发一个websocket包,比如说数据是1234,给服务器,服务器可以获取这个包,数据是1234,和http一样了。而tcp还是原始的没头没尾的字节流,想要通讯,还得再自定义一个应用层的协议。
    作者回复

    说的很对。

    正文里的tcp over web只是一种比喻,强调了WebSocket与http的区别,与tcp的相似性,不是那么严谨。

    2020-02-28 16:53:14

  • 夏目

    2019-12-10 23:45:18

    两年前我实习的时候公司项目用过,到现在我才搞清楚和http的区别,惭愧…
    作者回复

    这两个确实很像,第一次接触的人(包括我)也是容易弄糊涂。

    2019-12-11 08:56:48

  • ccx

    2021-09-30 12:20:40

    在 FINTECH 领域工作了几年了,自研发的外汇/数字货币交易系统的行情模块基本都是 websocket 实现的;另外还遇到一个有意思的场景,就是 discord 的机器人 Slash Commands 的实现也是基于 websocket 的。
    作者回复

    websocket在如今的互联网大环境下非常有用,基于http握手建连让它可以很容易运行在各种web服务上。

    2021-09-30 18:52:10

  • chao

    2019-09-18 22:09:03

    1、第二个字节后 7 位是“Payload len”,表示帧内容的长度。它是另一种变长编码,最少 7 位,最多是 7+64 位,也就是额外增加 8 个字节,所以一个 WebSocket 帧最大是 2^64。
    2、如果数据的长度小于等于125个字节,则用默认的7个bit来标示数据的长度;
    如果数据的长度为126个字节,则用后面相邻的2个字节来保存一个16bit位的无符号整数作为数据的长度;
    如果数据的长度大于等于127个字节,则用后面相邻的8个字节来保存一个64bit位的无符号整数作为数据的长度;
    老师,2是其它地方看到的,Payload len 这样设计的原因是什么,以及没明白为啥126个字节的长度要用16bit来表示
    作者回复

    我个人也觉得WebSocket的变长编码设计的很奇怪。

    第二个字节最高位被mask占用,所以低7位表示长度,最多127。

    那么125一下在低7位就够了,126用作标志位,表示后续使用两个字节,127又是另外一个标志位,表示后面是四个字节。

    所以超过125后低7位就不再是长度的含义了,而是标志位:126=>2 bytes, 127=> 4 bytes。

    2019-09-19 08:34:28

  • Evan Xia

    2022-05-20 14:18:06

    我们的业务场景是在下黑白棋的过程双方都能实时收到对方的落子, 用的是一个封装好的Centrifugo库,期间遇到最多的就是网络不好重连的问题
    作者回复

     这个场景用websocket还是挺合适的,不过网络的问题就是协议之外的事情了。

    2022-05-22 12:02:31

  • Daiver

    2020-01-23 17:32:35

    还有socket.io,算是websocket的超集了。
    作者回复

    socket.io是一个开发框架,而WebSocket是传输协议,两者虽然有联系,但不能混在一起。

    2020-01-24 18:36:22

  • ╭(╯ε╰)╮

    2023-01-20 18:17:39

    有个问题 30课介绍http2的时候不能重复使用443端口,所以重新用了8443端口。这节课websocket为什么就能跟http一起复用443端口呢?
    作者回复

    websocket基于http/1.1,所以它可以和443,也就是https在一起。

    而http/2与http/1.1完全不同,就没有办法用同一个端口。

    2023-01-21 10:57:40

  • Jasmine

    2021-11-22 13:38:01

    第二个字节后 7 位是“Payload len”,表示帧内容的长度。它是另一种变长编码,最少 7 位,最多是 7+64 位,也就是额外增加 8 个字节,所以一个 WebSocket 帧最大是 2^64。——这里最大的帧为什么不说是2^71呢?
    作者回复

    前面的7位是标志位,已经被占用了,所以只能用64位。

    2021-11-23 10:00:08

  • 脱缰的野马__

    2021-03-20 21:48:12

    老师你好,请问服务端在使用websocket主动推送信息给客户端的时候,是如何知道客户端的呢?另外服务端要主动推送的客户端有成千上万个,哪又如何推送?不像客户端请求服务端,客户端请求服务端是使用http,有ip和端口。
    作者回复

    WebSocket和http一样,客户端要发起连接,接下来就和tcp一样了,服务器在内存里保持多个与客户端的连接。

    如果会Python,可以找相关的库试一试。

    2021-03-21 09:28:48

  • 我母鸡啊!

    2020-05-24 20:49:02

    3.结合自己的实际工作,你觉得 WebSocket 适合用在哪些场景里?
    最近在做直播功能的项目,就用到了websovket的去做用户评论的推送,观看人数等的功能。
    作者回复

    nice

    2020-05-25 08:26:37

  • 2020-04-05 16:43:38

    1:WebSocket 与 HTTP/2 有很多相似点,比如都可以从 HTTP/1 升级,都采用二进制帧结构,你能比较一下这两个协议吗?
    初心不一样,WebSocket核心是实现全双工通信,可以重分的利用网络的通信能力,实现全双工后服务器就不总是被动的响应了,也可以主动邀请浏览器喝咖啡。HTTP/2核心是提高数据的传输效率,通过多路复用的方式来实现。

    2:试着自己解释一下 WebSocket 里的”Web“和”Socket“的含义。
    Web主要强调浏览器或者网页相关的应用吧!
    Socket主要强调他是在TCP上的一层薄薄的封装,实现通信方式比较简单考向底层。

    3:结合自己的实际工作,你觉得 WebSocket 适合用在哪些场景里?
    目前还没用到,他核心解决的是全双工通信问题,HTTP早就就解决了浏览器侧的主动请求,那他的出现主要方便了想发起主动请求的服务器这一侧。只有是服务器想主动推送数据的场景也许都合适,比如:数据变动主动推送,不用客户端不断轮询。
    作者回复

    说的都很到位。

    WebSocket就是用来轻量级替代tcp的,可以通过http协议来建立连接,实现全双工通信。

    2020-04-07 09:12:01

  • Geek_5b0e47

    2020-03-27 15:10:11

    向客户端监控屏推送时实更新数据,可以使用websocket
    作者回复

    nice,实时推送用WebSocket很好使。

    2020-03-27 15:45:45

  • Cris

    2019-08-23 08:58:26

    老师,我想问下,uri里的端口号,有什么用?为什么它是和协议对应的(http默认80,https默认443),却又写在域名的后面?
    作者回复

    可以参考一下第6讲,端口号是跟tcp协议相关的概念。

    因为域名实际上是ip地址的等价替换,所以端口号就可以跟在域名后面。

    2019-08-23 09:47:54

  • 徐海浪

    2019-08-23 08:27:33

    1. WebSocket 与 HTTP/2 有很多相似点,比如都可以从 HTTP/1 升级,都采用二进制帧结构,你能比较一下这两个协议吗?
    差别:HTTP/2是请求与响应的模式,而WebSocket是双向的,服务器也可以主动向客户端发起请求。
    2. 试着自己解释一下 WebSocket 里的”Web“和”Socket“的含义。
    是基于web服务器,类似于tcp的socket方式来使用的协议。
    3. 结合自己的实际工作,你觉得 WebSocket 适合用在哪些场景里?
    我在实际工作中还没有用到WebSocket,觉得适合服务器主动推送的客户端的场景,比如站内信或者站内聊天,或者在线页游?
    作者回复


    1.在WebSocket里没有请求响应的概念,收发的都是数据帧,通信的双方可以自己解释帧的含义。

    2.应该是基于web,也就是http协议。

    3.对。

    2019-08-23 09:41:25

  • -W.LI-

    2019-08-23 08:00:17

    老师好!websocket单机服务器能支持多少链接啊?之前没用过websocket。看帖子好像是通过key-value形式存储所有链接。需要用得时候通过key拿到链接往外写数据。希望老师科普下web socket的简单应用和实现,性能分析。
    需要服务器主动推的感觉都可以用websocket做。
    聊天工具:用户A,用户B,
    A->服务器(保存聊天记录)->B;B->服务器(保存聊天记录)->A;是这样么?
    作者回复


    WebSocket其实就是给tcp加了一层简单的包装,所以它的并发能力取决于服务器,并不是kv的形式,你应该把它理解成运行在http上的tcp,用tcp的思路去考虑它。

    2019-08-23 09:38:15

  • Snooker

    2024-07-01 14:55:20

    1.除了双向推送消息外,websocket创建链接后的消息收发,相比http是可以省去头部等字段信息?
    3.工作场景:主要是直播场景下的一些信息交互,例如:消息的主动推送,像直播间主播推送的商品信息、全频道的消息广播:榜一大哥刷🚀、状态维护:上下麦状态、音视频开关状态等
    作者回复

    great

    2024-07-03 09:02:27

  • 随心而至

    2024-06-14 17:03:52

    如果是想用 web socket 做大模型的流式输出,就不要设置 fin 为 0,否则 web socket client 会默认等拿到 fin 为 1 的 fragement 并且合并好,再给应用。
    作者回复

    great

    2024-06-21 11:43:10