之前自己一个人负责完成了公司的消息推送服务, 和移动端配合完成了扫码登录订单消息推送活动消息广播等功能为了加深自己对 websocket 协议的理解, 自己通过进行抓包的方式学习了一番现在分享出来, 希望对大家能有所帮助
Chrome 控制台:
(1)F12 进入控制台, 点击 Network, 选中 ws 栏, 注意选中 Filter
(2)刷新页面会得到一个 ws 链接
(3)点击链接可以查看链接详情
注意红框标出的信息, 后面会详细说明
(4)当然也可以切换到 Frames 查看发出和接收的消息, 但是非常的简陋, 只能看到消息内容, 数据长度和时间
Fiddler: 抓包调试利器
(1)打开 Fiddler, 点开菜单栏的 Rules, 选择 Customize Rules...
(2)这时会打开 CustomRules.js 文件, 在 class Handlers 中加入以下代码
- static function OnWebSocketMessage(oMsg: WebSocketMessage) {
- // Log Message to the LOG tab
- FiddlerApplication.Log.LogString(oMsg.ToString());
- }
(3)保存后就可以在 Fiddler 右边栏的 Log 标签里, 看到 WebSocket 的数据包
下列图中红框标出的 Client.1 代表客户端发出的第一条消息; 对应的 Server.1 代表服务端发出的第一条消息 MessageType:Text 代表正常的通话消息; Close 代表会话关闭
客户端发出的消息:
服务端发出的消息:
然后我们会发现每次会话关闭都是由客户端发起的:
相对于 Chrome 控制台来说 Fiddler 抓包更加详细一些, 能知道会话消息是由客户端还是服务端发出并且能知道消息类型但是这仍然满足不了深入理解学习 Websocket 协议的目的如果是处理 HTTPHTTPS, 还是用 Fiddler 其他协议比如 TCP,UDP 就用 WireSharkTPC/IP 协议是传输层协议, 主要解决数据如何在网络中传输, 而 HTTPWebsocket 是应用层协议, 主要解决如何包装数据因为应用层是在传输层的基础上包装数据, 所以我们还是从底层开始了解 Websocket 到底是个啥? 是如何工作的?
WireShark
WireShark(前称 Ethereal)是一个网络封包分析软件网络封包分析软件的功能是撷取网络封包, 并尽可能显示出最为详细的网络封包资料 WireShark 抓包是根据 TCP/IP 五层协议来的, 也就是物理层数据链路层网络层传输层应用层我们主要关注传输层和应用层
TCP 三次握手
我们都知道, TCP 建立连接时, 会有三次握手过程下图是 WireShark 截获到的三次握手的三个数据包(虽然叫数据包, 但是三次握手包是没有数据的)
点击上图中的数据包就可以查看每个数据包的详情, 这里我们需要明确几个概念才能看懂每个数据包代表啥意义:
SYN: 同步比特, 建立连接
ACK: 确认比特, 置 1 表示这是一个确认的 TCP 包, 0 则不是
PSH: 推送比特, 当发送端 PSH=1 时, 接收端应尽快交付给应用进程
第一次握手
可以看到我们打开的 Transmission Control Protocol 即为传输层(Tcp)
SYN 置为 1, 客户端向服务端发送连接请求包
第二次握手
服务器收到客户端发过来的 TCP 报文, 由 SYN=1 知道客户端要求建立联机, 向客户端发送一个 SYN=1,ACK=1 的 TCP 报文, 将确认序号设置为客户端的序列号加 1
第三次握手
客户端接收到服务器发过来的包后检查确认序列号是否正确, 即第一次发送的序号 + 1, 以及标志位 ACK 是否为 1 若正确则再次发送确认包, ACK 标志为 1 链接建立成功, 可以发送数据了
一次特殊的 HTTP 请求
紧接着是一次 Http 请求(第四个包), 说明 Http 的确是使用 Tcp 建立连接的
先来看传输层 (Tcp): PSH(推送比特) 置 1,ACK 置 1,PSH 置 1 说明开始发送数据, 同时发送数据 ACK 要置 1, 因为需要接收到这个数据包的端给予确认 PSH 为 1 的情况, 一般只出现在 DATA 内容不为 0 的包中, 也就是说 PSH 为 1 表示的是有真正的 TCP 数据包内容被传递
再来看应用层(Http): 这是一次特殊的 Http 请求, 为什么是一次特殊的 Http 请求呢? Http 请求头中 Connection:Upgrade Upgrade:websocket,Upgrade 代表升级到较新的 Http 协议或者切换到不同的协议很明显 WebSocket 使用此机制以兼容的方式与 HTTP 服务器建立连接 WebSocket 协议有两个部分: 握手建立升级后的连接, 然后进行实际的数据传输首先, 客户端通过使用 Upgrade: WebSocket 和 Connection: Upgrade 头部以及一些特定于协议的头来请求 WebSocket 连接, 以建立正在使用的版本并设置握手服务器, 如果它支持协议, 回复与相同 Upgrade: WebSocket 和 Connection: Upgrade 标题, 并完成握手握手完成后, 数据传输开始这些信息在前面的 Chrome 控制台中也可以看到
请求:
响应:
响应状态码 101 表示服务器已经理解了客户端的请求, 在发送完这个响应后, 服务器将会切换到在 Upgrade 请求头中定义的那些协议
由此我们可以总结出:
Websocket 协议本质上是一个基于 TCP 的协议建立连接需要握手, 客户端 (浏览器) 首先向服务器 (web server) 发起一条特殊的 http 请求, web server 解析后生成应答到浏览器, 这样子一个 websocket 连接就建立了, 直到某一方关闭连接
Websocket 的世界
通信协议格式是 WebSocket 格式, 服务器端采用 Tcp Socket 方式接收数据, 进行解析, 协议格式如下:
首先我们需要知道数据在物理层, 数据链路层是以二进制进行传递的, 而在应用层是以 16 进制字节流进行传输的
第一个字节:
FIN:1 位, 用于描述消息是否结束, 如果为 1 则该消息为消息尾部, 如果为零则还有后续数据包;
RSV1,RSV2,RSV3: 各 1 位, 用于扩展定义的, 如果没有扩展约定的情况则必须为 0
OPCODE:4 位, 用于表示消息接收类型, 如果接收到未知的 opcode, 接收端必须关闭连接
Webdocket 数据帧中 OPCODE 定义:
0x0 表示附加数据帧
0x1 表示文本数据帧
0x2 表示二进制数据帧
0x3-7 暂时无定义, 为以后的非控制帧保留
0x8 表示连接关闭
0x9 表示 ping
0xA 表示 pong
0xB-F 暂时无定义, 为以后的控制帧保留
第二个字节:
MASK:1 位, 用于标识 PayloadData 是否经过掩码处理, 客户端发出的数据帧需要进行掩码处理, 所以此位是 1 数据需要解码
PayloadData 的长度: 7 位, 7+16 位, 7+64 位
如果其值在 0-125, 则是 payload 的真实长度
如果值是 126, 则后面 2 个字节形成的 16 位无符号整型数的值是 payload 的真实长度
如果值是 127, 则后面 8 个字节形成的 64 位无符号整型数的值是 payload 的真实长度
上图是客户端发送给服务端的数据包, 其中 PayloadData 的长度为二进制: 01111110 > 十进制: 126; 如果值是 126, 则后面 2 个字节形成的 16 位无符号整型数的值是 payload 的真实长度也就是圈红的十六进制: 00C1 > 十进制: 193 byte 所以 PayloadData 的真实数据长度是 193 bytes;
根据我们的分析, 客户端到服务端数据包的 websocket 帧图应该为:
我们再来抓包分析一下服务器到客户端的数据包:
可以发现服务器发送给客户端的数据包中第二个字节中 MASK 位为 0, 这说明服务器发送的数据帧未经过掩码处理, 这个我们从客户端和服务端的数据包截图中也可以发现, 客户端的数据被加密处理, 而服务端的数据则没有(如果服务器收到客户端发送的未经掩码处理的数据包, 则会自动断开连接; 反之, 如果客户端收到了服务端发送的经过掩码处理的数据包, 也会自动断开连接)
掩码处理:
未掩码处理:
根据我们的分析, 服务端到客户端数据包的 websocket 帧图应该为:
TCP KeepAlive
如上图所示, TCP 保活报文总是成对出现, 包括 TCP 保活探测报文和 TCP 保活探测确认报文
TCP 保活探测报文是将之前 TCP 报文的确认序列号减 1, 并设置 1 个字节, 内容为 00 的应用层数据, 如下图所示:
TCP 保活探测确认报文就是对保活探测报文的确认, 其报文格式如下:
因为 Websocket 通过 Tcp Socket 方式工作, 现在考虑一个问题, 在一次长连接中, 服务器怎么知道消息的顺序呢? 这就涉及到 tcp 的序列号 (Sequence Number) 和确认号 (Acknowledgment Number) 我的理解是序列号是发送的数据长度; 确认号是接收的数据长度这样讲比较抽象, 我们从 TCP 三次握手开始 (结合下图) 详细分析一下
包 1:
TCP 会话的每一端的序列号都从 0 开始, 同样的, 确认号也从 0 开始, 因为此时通话还未开始, 没有通话的另一端需要确认
包 2:
服务端响应客户端的请求, 响应中附带序列号 0(由于这是服务端在该次 TCP 会话中发送的第一个包, 所以序列号为 0)和相对确认号 1(表明服务端收到了客户端发送的包 1 中的 SYN)需要注意的是, 尽管客户端没有发送任何有效数据, 确认号还是被加 1, 这是因为接收的包中包含 SYN 或 FIN 标志位
包 3:
和包 2 中一样, 客户端使用确认号 1 响应服务端的序列号 0, 同时响应中也包含了客户端自己的序列号 (由于服务端发送的包中确认收到了客户端发送的 SYN, 故客户端的序列号由 0 变为 1) 此时, 通信的两端的序列号都为 1
包 4: 客户端>服务器
这是流中第一个携带有效数据的包(确切的说, 是客户端发送的 HTTP 请求), 序列号依然为 1, 因为到上个包为止, 还没有发送任何数据, 确认号也保持 1 不变, 因为客户端没有从服务端接收到任何数据需要注意的是, 包中有效数据的长度为 505 字节
包 5: 服务器>客户端
当上层处理 HTTP 请求时, 服务端发送该包来确认客户端在包 4 中发来的数据, 需要注意的是, 确认号的值增加了 505(505 是包 4 中有效数据长度), 变为 506, 简单来说, 服务端以此来告知客户端端, 目前为止, 我总共收到了 506 字节的数据, 服务端的序列号保持为 1 不变
包 6: 服务器>客户端
这个包标志着服务端返回 HTTP 响应的开始, 序列号依然为 1, 因为服务端在该包之前返回的包中都不带有有效数据, 该包带有 129 字节的有效数据
包 7:
由于上个数据包的发送, TCP 客户端的确认序列号增长至 130, 从服务端接收了 129 字节的数据, 客户端的确认号由 1 增长至 130
理解了序列号和确认序列号是怎么工作的之后, 我们也就知道 TCP 保活探测报文是将之前 TCP 报文的确认序列号减 1, 并设置 1 个字节为什么要这么搞了减一再加一, 是为了保证一次连接中 keep alive 不影响序列号和确认序列号 Keep alive 中的 1byte 00 的数据并不是真正要传递的数据, 而是 tcp keep alive 约定俗称的规则
总结:
WebSocket 是一个独立的基于 TCP 的协议, 它与 HTTP 之间的唯一关系就是它的握手请求可以作为一个升级请求 (Upgrade request) 经由 HTTP 服务器解释再严谨一点: WebSocket 是一个网络通讯协议, 只要理解上面的数据帧格式和握手流程, 都可以完成基于 websokect 的即时通讯
来源: https://www.cnblogs.com/songwenjie/p/8575579.html