面试大厂WebSocket 🤔 需要懂一点嘛

782 阅读6分钟
最近可能会更多的偏向于面试方面的一些思考,记录之前面试被问到的一些问题,会慢慢的进行回顾。

今天想起来一个问题,当时被问到的时候,我"义愤填膺"的说: 我不会,我不懂,我... 似乎那会并不觉得有些尴尬。我只想说我真的不会,你让我说啥;难道马上去搜一下吗?

那今天可能就是随便搜了一下,主要目的是为了和大家唠唠,学东西就是一起学才是真的香。

那么要进入今天的主题了,我不看题目似乎连主题也没记住。可能年纪大了,记性也不好了。你说我看过WebSocket嘛, 我看过;你说我让我说说这是啥?能用来干嘛? 我似乎忘了我的嘴是用来干嘛的了,可能是用来吃饭的.那么WebSocket是用来干嘛的,且听我细细道来。

WebSocket 怎么就出现了?

这可能就会涉及到客户端服务端那点事, 如果你想建立一个在服务端和客户端的双向数据通信的Web应用,你会想到什么方法呢?在WebSocket还没有的时候,总得有解决方案啊; 轮询是一种比较常用的方案。

轮询

轮询 就是不断地向服务端发起请求,询问现在还有没有数据啊,有数据的话服务端就会用响应报文来回应。其实,前端可能会有类似的场景,需要不停查询某个阶段的计算结果;当然需要你去设置一个很小的查询间隔去增加查询的频率, 似乎就是处于一种实时通信的状态。

但是,由此会带来一系列的问题:

  • 服务端:大量TCP潜在的连接交互;既要接收数据;又要处理数据返回
  • 应用层(HTTP)开销过大
  • 客户端:需要对发送和接收的数据处理如何处理也同样是一个问题

那么,我们的主角开始登上了历史舞台。

WebSocket是什么?

其实到这里,笔者同样也是刚刚步入WebSocket学习的大门;可能大伙需要对HTTP/TCP的相关知识要有一些理解。

全双工

WebSocket是一个全双工的通信协议。

TCP一样,客户端和服务端都可以随时向对方发送数据, 而不是像HTTP那样客套"今天来我家吃饭;好,今天来你家吃饭",一点都不主动真的是。那么,现在就变成了服务端不用等客户端一直催你回家吃饭,服务端一旦肚子饿了,就会主动跑到客户端那里说: "我饿了"。不需要客户端一直问你想来我家吃饭吗?主动一些多好, 是不是提高了"实时通信"的效率呢。

帧结构

WebSocket 用的是二进制帧。如果,大家伙之前对HTTP有一些深入的了解,对WebSocket的报文结构会理解的刚快一些。

下面让我们看一下 WebSocket的帧结构定义,长度是不固定的,在 2 ~ 14字节之间浮动。

image.png

是不是看到这张图的时候,有种我是谁,我在哪里的错觉呢?

不要慌, 跟着我一起来慢慢梳理:

  • 第一个字节的第一个字段FIN是消息结束的标志位,表示数据发送完毕。一个消息可以拆成桢,接收方在看到FIN字段之后呢,就会把前面拆的帧组合起来,变为完整的信息。

  • FIN后面的三个位是保留位,目前没啥意义,但必须是0.

  • 第一个字节的后4位 opcode: 操作码,其实就是帧类型

    • 1: 桢内容是纯文本
    • 2: 桢内容是二进制数据
    • 8: 是关闭连接
    • 9: 连接保活的PING
    • 10: 连接保活的PONG
  • 第二个字节的第一位是掩码标志位:MASK,表示桢内容是否使用异或操作做加密。目前的WebSocket标准规定,客户端发送数据必须使用掩码,而服务器发送则必须不使用掩码

  • 第二字节的后7位PayLoad len,表示桢的内容长度。它是另一种变长编码,长度 7 ~ 7+64 位,也就是额外增加 8个字节。所以一个WebSocket桢最大长度为 2^64。

  • 长度之后的字段是Masking-key,掩码密钥,由上面的标志位“MASK”决定的。

经过短暂的梳理, 我们可以看到其实 WebSocket 的帧头就四个部分:

  1. 结束标志位
  2. 操作码
  3. 帧长度
  4. 掩码
WebSocket 的握手

TCP类似,WebSocket同样需要一个握手的过程,才可以正常的收发数据。

其实这里利用了HTTP,利用了它本身协议升级的特性,"伪装"成HTTP绕过了浏览器沙盒等的限制,这是 WebSocketHTTP的一个关联点。有兴趣的朋友还可以去找一找还有没有别的关联点。

对于WebSocket 握手来说: 是一个标准的HTTP GET请求,但是这里需要带上两个字段

  • Connection: Upgrade 表示的含义是: 协议要升级
  • Upgrade: WebSocket 表示要升级WebSocket

当然为了避免一些问题的出现: 一些普通的HTTP请求会被意外识别成 WebSocket 又增加了两个额外的认证头的字段:

  • Sec-WebSocket-Key: 一个Base64编码的16字节随机数,作为简单的认证密匙
  • Sec-WebSocket-Version: 协议的版本号

下面我们来看一下 握手的具体细节:

客户端握手:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

服务端握手:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

我们在服务端的响应报文中可以看到, 响应头HTTP/1.1 101 Switching Protocols, 就是告诉客户端以后都用 WebSocket协议通信。

当然, WebSocket 的握手响应报文也是具有特殊格式的。要用Sec-WebSocket-Accept 字段来验证客户端的请求报文, 同样也是为了防止误连接。 具体做法是把请求头里“Sec-WebSocket-Key”的值,加上一个专用的UUID,再计算摘要。

当涉及到如何去生成UUID时,生成 UUID 的三种方式及测速对比。这篇文章已经介绍的非常详细我就不进行复述了。

客户端收到响应报文,就可以用同样的算法,确认是否是刚才连接的服务器。

今天从WebSocket出现的时机, 解决什么样的问题去慢慢地去分析它的特点和具体内部的结构。以及在实际握手时,和HTTP不一样的地方,暂时先整理到这里好了。我们可以共同讨论相关的问题,感谢大家的支持。

碰巧看到有位大佬做了相关的实践,大家可以作为参看,更好的理解整个过程。

参考资料:

# 云享专家韦世东:开发者必知必会的 WebSocket 协议

【译】WebSocket协议第一章——介绍(Introduction)