最近可能会更多的偏向于面试方面的一些思考,记录之前面试被问到的一些问题,会慢慢的进行回顾。
今天想起来一个问题,当时被问到的时候,我"义愤填膺"的说: 我不会,我不懂,我... 似乎那会并不觉得有些尴尬。我只想说我真的不会,你让我说啥;难道马上去搜一下吗?
那今天可能就是随便搜了一下,主要目的是为了和大家唠唠,学东西就是一起学才是真的香。
那么要进入今天的主题了,我不看题目似乎连主题也没记住。可能年纪大了,记性也不好了。你说我看过WebSocket
嘛, 我看过;你说我让我说说这是啥?能用来干嘛? 我似乎忘了我的嘴是用来干嘛的了,可能是用来吃饭的.那么WebSocket
是用来干嘛的,且听我细细道来。
WebSocket 怎么就出现了?
这可能就会涉及到客户端
和服务端
那点事, 如果你想建立一个在服务端和客户端的双向数据通信的Web应用
,你会想到什么方法呢?在WebSocket
还没有的时候,总得有解决方案啊; 轮询
是一种比较常用的方案。
轮询
轮询 就是不断地向服务端发起请求,询问现在还有没有数据啊,有数据的话服务端就会用响应报文来回应。其实,前端可能会有类似的场景,需要不停查询某个阶段的计算结果;当然需要你去设置一个很小的查询间隔去增加查询的频率, 似乎就是处于一种实时通信
的状态。
但是,由此会带来一系列的问题:
- 服务端:大量
TCP
潜在的连接交互;既要接收数据;又要处理数据返回 - 应用层(HTTP)开销过大
- 客户端:需要对发送和接收的数据处理如何处理也同样是一个问题
那么,我们的主角开始登上了历史舞台。
WebSocket是什么?
其实到这里,笔者同样也是刚刚步入WebSocket
学习的大门;可能大伙需要对HTTP/TCP
的相关知识要有一些理解。
全双工
WebSocket是一个全双工
的通信协议。
和TCP
一样,客户端和服务端都可以随时向对方发送数据, 而不是像HTTP
那样客套"今天来我家吃饭;好,今天来你家吃饭",一点都不主动真的是。那么,现在就变成了服务端
不用等客户端
一直催你回家吃饭,服务端
一旦肚子饿了,就会主动
跑到客户端
那里说: "我饿了"。不需要客户端
一直问你想来我家吃饭吗?主动一些多好, 是不是提高了"实时通信"的效率呢。
帧结构
WebSocket 用的是二进制帧。如果,大家伙之前对HTTP
有一些深入的了解,对WebSocket的报文结构会理解的刚快一些。
下面让我们看一下 WebSocket
的帧结构定义,长度是不固定的,在 2 ~ 14字节之间浮动。
是不是看到这张图的时候,有种我是谁,我在哪里的错觉呢?
不要慌, 跟着我一起来慢慢梳理:
-
第一个字节的第一个字段
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 的帧头就四个部分:
- 结束标志位
- 操作码
- 帧长度
- 掩码
WebSocket 的握手
和TCP
类似,WebSocket同样需要一个握手的过程,才可以正常的收发数据。
其实这里利用了HTTP
,利用了它本身协议升级
的特性,"伪装"成HTTP
绕过了浏览器沙盒等的限制,这是 WebSocket
和HTTP
的一个关联点。有兴趣的朋友还可以去找一找还有没有别的关联点。
对于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
不一样的地方,暂时先整理到这里好了。我们可以共同讨论相关的问题,感谢大家的支持。
碰巧看到有位大佬做了相关的实践,大家可以作为参看,更好的理解整个过程。
参考资料: