⭐️ 更多前端技术和知识点,搜索订阅号
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 的技术文章,只谈技术不谈八卦 😊