🔗 抓包分析 TCP 建立和断开连接的流程
发布于 4 个月前 作者 oliyg 529 次浏览 来自 分享

20190331104134.png

⭐️ 更多前端技术和知识点,搜索订阅号 JS 菌 订阅

HTTP 协议是基于 TCP 协议的。大家都知道发送 HTTP 报文需要首先建立客户端和服务端之间的 TCP 连接。TCP 三次握手建立连接,四次挥手断开连接,再熟悉不过。本文实践一下 TCP 建立和断开的整个流程,并通过抓包工具进行逐一分析。

开始之前呢,先安装抓包工具,这里用的是 Wireshark 正常下载安装,不再赘述

然后我们还需要 curl 如果在 windows 中没有这个模块,可以通过 Chocolatey 去安装,或者用 wget、浏览器啥的

两个准备工作做好了,就可以开始分析工作了。

TCP 建立连接

首先回顾一下 HTTP 请求是怎么发送的:

  • 先是建立 TCP 连接
    • 首先,服务端准备接收客户端请求,状态变为 LISTEN;客户端发送建立连接请求包,携带一个 SYNSeq=x;此时客户端状态为 SYN_SENT 状态
    • 服务端收到请求后,同意连接返回一个同意连接的包,携带一个 SYN,ACKSeq=yAck=x+1;服务端状态变为 SYN_RCVD
    • 客户端收到确认后,还要发送一个确认的确认连接包,携带一个 ACKAck=y+1;服务端收到后,客户端服务端都为 ESTABLISHED 状态;连接建立成功
  • 客户端发送 HTTP 请求
    • 组装 HTTP 请求行、请求首部和实体

⚠️ 一定要注意 ACK 和 Ack 是不同的概念,前者是 Acknowledgement 确认值,后者是 Acknowledgement Number 确认编号

开始抓包:

打开 Wireshark,左上角鲨鱼鳍标志,然后在终端中使用 curl 给发送一个 GET 请求,这里以 http://httpbin.org/json 为例

20190331223151.png

回到 Wireshark,在过滤器中输入 http,只查看 http 应用层的信息:

20190331223300.png

然后我们选择明显是 /json 网址的记录,右键选择 follow 子菜单中的 HTTP Stream:

20190331223430.png

弹出的窗口是 HTTP 请求报文,先关闭窗口暂时用不到这些东西

20190331121320.png

此时面板中就是整个 TCP 建立、发送 HTTP 请求并获取响应以及断开 TCP 连接的过程

客户端发送请求建立连接

第一条记录显示了我的电脑端口发送了一个 TCP 连接的包,这个包携带了一个 SYN flag,Seq 被设置为 0;这就是请求建立 TCP 连接的包

20190331224046.png

所以客户端请求建立 TCP 连接时是发送 SYN 的包,其中 Seq 被设置为 0(实际上有可能不为 0)

服务端返回确认信息

第二条是第一条包的确认信息:

20190331224337.png

看到这是一个确认包,这里的 flag 是 SYN 和 ACK,其中 Seq 为新设置的值为 0( ⚠️ 注意这里的 Seq 与此前发送的 Seq 不是一个值)

另外确认序号 Ack 是之前为 0 的,接收到的那个序号 Seq + 1,值为 1

客户端发送确认信息

第三条就是第二条包的确认信息,表示确认收到服务端的确认信息

20190331224714.png

第三个包可以看到有一个 ACK,同时序号 Seq 为第一次发送请求建立连接时候的 Seq + 1,值为 1( ⚠️ 注意这里的 Seq 与服务端返回的 Seq 不是一个值),Ack 确认序号就是收到的服务端发送的包 Seq + 1,值为 1

至此 TCP 连接成功

20190331201124.png

⚠️ 一定要注意区别开双方发送的 Seq 不是一个东西,Ack 是确认收到对方的包,在对方发送的这个包的 Seq 基础上增加 1。自己发送接下来的包,则是在自己发送的上一个包的 Seq 基础上增加 1;另外还要区别 Ack 和 ACK 是不同的;

TCP 断开连接

客户端主动断开 TCP 连接的过程如下:

  • 客户端发送断开连接的请求包,携带一个 FIN, ACKSeq=xAck=y;此时客户端状态为 FIN_WAIT_1
  • 服务端同意断开连接,返回一个 ACKAck=x+1;服务端可能还有数据需要传送,继续传送并将状态变为 CLOSE_WAIT 状态;客户端收到并将状态变为 FIN_WAIT_2;继续接收数据。
  • 数据传输完毕,服务端发送一个 FINSeq=z+1(这里的 z 是最后一次服务端发送的 Seq 序号);服务端状态变为 LAST_ACK;客户端收到并将状态变为 TIME_WAIT
  • 数据接收到之后,客户端发送一个 ACK,这里的 Ack=z+2(就是最后一次接收到的序号 Seq 加一)

Wireshark 抓包记录继续分析:

20190331232257.png

首先客户端发送一个 FIN, ACK,切序号 Seq 为 80,Ack 为 650,请求断开连接

20190331232646.png

服务端返回一个 ACK 和一个 FIN,因为没有更多数据传输,所以原本两个数据包被合并成一个,因此这里四次挥手因合并而变为“三次挥手”

这里的 Seq 为 650,确认序号 Ack 为收到序号加一也就是 80 + 1 = 81

20190331233025.png

最后客户端发送一个 ACK,就代表 TCP 连接正式断开,Ack 为收到序号加一也就是 650 + 1 = 651

整个 TCP 通信过程就是这样

⚠️ Seq 序号和 Ack 确认序号比较乱;这里提个醒 Ack 始终为最后收到包的序号 Seq + 1;而 Seq 则是上一个发送出去的包的 Seq + 1

有哪里有讲的不准确的地方也请指正谢谢

JS 菌公众账号

请关注我的订阅号,不定期推送有关 JS 的技术文章,只谈技术不谈八卦 😊

回到顶部