⭐️ 更多前端技术和知识点,搜索订阅号 JS 菌 订阅
HTTP 协议是基于 TCP 协议的。大家都知道发送 HTTP 报文需要首先建立客户端和服务端之间的 TCP 连接。TCP 三次握手建立连接,四次挥手断开连接,再熟悉不过。本文实践一下 TCP 建立和断开的整个流程,并通过抓包工具进行逐一分析。
开始之前呢,先安装抓包工具,这里用的是 Wireshark 正常下载安装,不再赘述
然后我们还需要 curl 如果在 windows 中没有这个模块,可以通过 Chocolatey 去安装,或者用 wget、浏览器啥的
两个准备工作做好了,就可以开始分析工作了。
TCP 建立连接
首先回顾一下 HTTP 请求是怎么发送的:
先是建立 TCP 连接
首先,服务端准备接收客户端请求,状态变为 LISTEN;客户端发送建立连接请求包,携带一个 SYN,Seq=x;此时客户端状态为 SYN_SENT 状态
服务端收到请求后,同意连接返回一个同意连接的包,携带一个 SYN,ACK,Seq=y,Ack=x+1;服务端状态变为 SYN_RCVD
客户端收到确认后,还要发送一个确认的确认连接包,携带一个 ACK,Ack=y+1;服务端收到后,客户端服务端都为 ESTABLISHED 状态;连接建立成功
客户端发送 HTTP 请求
组装 HTTP 请求行、请求首部和实体
⚠️ 一定要注意 ACK 和 Ack 是不同的概念,前者是 Acknowledgement 确认值,后者是 Acknowledgement Number 确认编号
开始抓包:
打开 Wireshark,左上角鲨鱼鳍标志,然后在终端中使用 curl 给发送一个 GET 请求,这里以 http://httpbin.org/json 为例
回到 Wireshark,在过滤器中输入 http,只查看 http 应用层的信息:
然后我们选择明显是 /json 网址的记录,右键选择 follow 子菜单中的 HTTP Stream:
弹出的窗口是 HTTP 请求报文,先关闭窗口暂时用不到这些东西
此时面板中就是整个 TCP 建立、发送 HTTP 请求并获取响应以及断开 TCP 连接的过程
客户端发送请求建立连接
第一条记录显示了我的电脑端口发送了一个 TCP 连接的包,这个包携带了一个 SYN flag,Seq 被设置为 0;这就是请求建立 TCP 连接的包
所以客户端请求建立 TCP 连接时是发送 SYN 的包,其中 Seq 被设置为 0(实际上有可能不为 0)
服务端返回确认信息
第二条是第一条包的确认信息:
看到这是一个确认包,这里的 flag 是 SYN 和 ACK,其中 Seq 为新设置的值为 0(⚠️ 注意这里的 Seq 与此前发送的 Seq 不是一个值)
另外确认序号 Ack 是之前为 0 的,接收到的那个序号 Seq + 1,值为 1
客户端发送确认信息
第三条就是第二条包的确认信息,表示确认收到服务端的确认信息
第三个包可以看到有一个 ACK,同时序号 Seq 为第一次发送请求建立连接时候的 Seq + 1,值为 1(⚠️ 注意这里的 Seq 与服务端返回的 Seq 不是一个值),Ack 确认序号就是收到的服务端发送的包 Seq + 1,值为 1
至此 TCP 连接成功
⚠️ 一定要注意区别开双方发送的 Seq 不是一个东西,Ack 是确认收到对方的包,在对方发送的这个包的 Seq 基础上增加 1。自己发送接下来的包,则是在自己发送的上一个包的 Seq 基础上增加 1;另外还要区别 Ack 和 ACK 是不同的;
TCP 断开连接
客户端主动断开 TCP 连接的过程如下:
客户端发送断开连接的请求包,携带一个 FIN, ACK,Seq=x,Ack=y;此时客户端状态为 FIN_WAIT_1
服务端同意断开连接,返回一个 ACK,Ack=x+1; 服务端可能还有数据需要传送,继续传送并将状态变为 CLOSE_WAIT 状态;客户端收到并将状态变为 FIN_WAIT_2;继续接收数据。
数据传输完毕,服务端发送一个 FIN,Seq=z+1(这里的 z 是最后一次服务端发送的 Seq 序号);服务端状态变为 LAST_ACK;客户端收到并将状态变为 TIME_WAIT
数据接收到之后,客户端发送一个 ACK,这里的 Ack=z+2(就是最后一次接收到的序号 Seq 加一)
Wireshark 抓包记录继续分析:
首先客户端发送一个 FIN, ACK,切序号 Seq 为 80,Ack 为 650,请求断开连接
服务端返回一个 ACK 和一个 FIN,因为没有更多数据传输,所以原本两个数据包被合并成一个,因此这里四次挥手因合并而变为“三次挥手”
这里的 Seq 为 650,确认序号 Ack 为收到序号加一也就是 80 + 1 = 81
最后客户端发送一个 ACK,就代表 TCP 连接正式断开,Ack 为收到序号加一也就是 650 + 1 = 651
整个 TCP 通信过程就是这样
⚠️ Seq 序号和 Ack 确认序号比较乱;这里提个醒 Ack 始终为最后收到包的序号 Seq + 1;而 Seq 则是上一个发送出去的包的 Seq + 1
有哪里有讲的不准确的地方也请指正谢谢
请关注我的订阅号,不定期推送有关 JS 的技术文章,只谈技术不谈八卦 ????