2小时快速搭建一个高可用的IM系统( 二 )


到我们后边说到 WebSocket 协议数据帧时 , 大家可能就会明白 , 维持一条“长连接”服务端和客户端需要做的事情太多了 。
说完了握手通道 , 我们再来看 HTTP 协议如何升级到 WebSocket 协议的 。
②HTTP 协议升级为 WebSocket 协议
升级协议需要客户端和服务端交流 , 服务端怎么知道要将 HTTP 协议升级到 WebSocket 协议呢?它一定是接收到了客户端发送过来的某种信号 。
下面是我从谷歌浏览器中截取的“客户端发起协议升级请求的报文” , 通过分析这段报文 , 我们能够得到有关 WebSocket 中协议升级的更多细节 。

2小时快速搭建一个高可用的IM系统

文章插图
 
首先 , 客户端发起协议升级请求 。采用的是标准的 HTTP 报文格式 , 且只支持 GET 方法 。
下面是重点请求的首部的意义:
Connection:Upgrade:表示要升级的协议 。
Upgrade: websocket:表示要升级到 WebSocket 协议 。
Sec-WebSocket-Version: 13:表示 WebSocket 的版本 。
Sec-WebSocket-Key:UdTUf90CC561cQXn4n5XRg==:与 Response Header 中的响应首部 Sec-WebSocket-Accept: GZk41FJZSYY0CmsrZPGpUGRQzkY= 是配套的 , 提供基本的防护 , 比如恶意的连接或者无意的连接 。
其中 Connection 就是我们前边提到的 , 客户端发送给服务端的信号 , 服务端接受到信号之后 , 才会对 HTTP 协议进行升级 。
那么服务端怎样确认客户端发送过来的请求是否是合法的呢?在客户端每次发起协议升级请求的时候都会产生一个唯一码:Sec-WebSocket-Key 。
服务端拿到这个码后 , 通过一个算法进行校验 , 然后通过 Sec-WebSocket-Accept 响应给客户端 , 客户端再对 Sec-WebSocket-Accept 进行校验来完成验证 。
这个算法很简单:
  • 将 Sec-WebSocket-Key 跟全局唯一的(GUID , [RFC4122])标识:258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接 。
  • 通过 SHA1 计算出摘要 , 并转成 base64 字符串 。
258EAFA5-E914-47DA-95CA-C5AB0DC85B11 这个字符串又叫“魔串" , 至于为什么要使用它作为 WebSocket 握手计算中使用的字符串 , 这点我们无需关心 , 只需要知道它是 RFC 标准规定就可以了 。
官方的解析也只是简单的说此值不大可能被不明白 WebSocket 协议的网络终端使用 。
我们还是用世界上最好的语言来描述一下这个算法吧:
public function dohandshake($sock, $data, $key) {if (preg_match("/Sec-WebSocket-Key: (.*)rn/", $data, $match)) {$response = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));$upgrade= "HTTP/1.1 101 Switching Protocolrn" ."Upgrade: websocketrn" ."Connection: Upgradern" ."Sec-WebSocket-Accept: " . $response . "rnrn";socket_write($sock, $upgrade, strlen($upgrade));$this->isHand[$key] = true;}} 服务端响应客户端的头部信息和 HTTP 协议的格式是相同的 , HTTP1.1 协议是以换行符(rn)分割的 , 我们可以通过正则匹配解析出 Sec-WebSocket-Accept 的值 , 这和我们使用 curl 工具模拟 get 请求是一个道理 。
这样展示结果似乎不太直观 , 我们使用命令行 CLI 来根据上图中的 Sec-WebSocket-Key 和握手算法来计算一下服务端返回的 Sec-WebSocket-Accept 是否正确:
2小时快速搭建一个高可用的IM系统

文章插图
 
从图中可以看到 , 通过算法算出来的 base64 字符串和 Sec-WebSocket-Accept 是一样的 。
那么假如服务端在握手的过程中返回一个错误的 Sec-WebSocket-Accept 字符串会怎么样呢?
当然是客户端会报错 , 连接会建立失败 , 大家可以尝试一下 , 例如将全局唯一标识符 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 改为 258EAFA5-E914-47DA-95CA-C5AB0DC85B12 。
③WebSocket 的帧和数据分片传输 。
下图是我做的一个测试:将小说《飘》的第一章内容复制成文本数据 , 通过客户端发送到服务端 , 然后服务端响应相同的信息完成了一次通信
2小时快速搭建一个高可用的IM系统

文章插图
 
可以看到一篇足足有将近 15000 字节的数据在客户端和服务端完成通信只用了 150ms 的时间 。
我们还可以看到浏览器控制台中 Frame 栏中显示的客户端发送和服务端响应的文本数据 , 你一定惊讶 WebSocket 通信强大的数据传输能力 。


推荐阅读