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