引言 在TCP/IP协议栈中,TCP(传输控制协议)就像“网络世界的可靠信使”——它不像UDP那样“丢三落四”,而是通过严谨的“握手建连”和“挥手断连”,确保数据能从发送端完整、有序地抵达接收端。本文将结合文档内容,用“生活场景类比”拆解三次握手与四次挥手的细节,同时梳理关键技术点,帮你彻底搞懂这两个TCP核心机制。

TCP的核心特性是面向连接、可靠传输。就像现实中“寄重要快递”:
如果没有这两步:要么“快递寄了没人收”(数据丢失),要么“收件人一直等包裹”(资源浪费)。文档中也明确提到,TCP的连接管理是其可靠性的关键组成部分,而三次握手与四次挥手正是连接管理的核心流程🔶1-129🔶。
三次握手的本质是**“双向确认”** ——不仅要确认“我能发给你”,还要确认“你能发给我,且我能收到你的回复”。我们用“两个人打电话”的场景类比,同时结合文档中的状态变化和数据交互细节拆解。
CLOSED,通过socket()创建通信文件描述符后,调用connect()函数向服务器发起连接请求,进入SYN_SENT状态。CLOSED,通过socket()创建listenfd(监听描述符),再经bind()绑定IP和端口、listen()开启监听后,进入LISTEN状态,等待客户端的连接请求。
步骤 | 类比场景(你→朋友) | 技术层面(客户端→服务器) | 核心数据与标志位 | 双方状态变化 |
|---|---|---|---|---|
第一次握手(“喂,你在吗?”) | 你拨打朋友电话,等待对方接听 | 客户端发送SYN报文段(同步报文),表示“我想和你建立连接” | - 客户端初始序列号(Seq = x,比如x=100)- 标志位SYN=1(SYN=Synchronize,同步请求) | 客户端:CLOSED → SYN_SENT(等待服务器回复)服务器:LISTEN → SYN_RCVD(收到请求,准备回复) |
第二次握手(“我在!你能听到吗?”) | 朋友接起电话,回复“我在”,同时确认“能听到你的声音” | 服务器收到SYN后,回复SYN+ACK报文段,既同步自己的信息,又确认客户端的请求 | - 服务器初始序列号(Seq = y,比如y=200)- 确认号(Ack = x+1 = 101,表示“已收到你Seq=100的报文”)- 标志位SYN=1(服务器同步)+ ACK=1(ACK=Acknowledgment,确认有效) | 服务器:SYN_RCVD(等待客户端最终确认)客户端:收到后验证Ack=101(正确),进入ESTABLISHED(连接初步建立,可准备发数据) |
第三次握手(“能听到!那我们开始说吧”) | 你回复“能听到”,确认双方沟通通道已通 | 客户端收到SYN+ACK后,发送ACK报文段,最终确认连接 | - 确认号(Ack = y+1 = 201,表示“已收到你Seq=200的报文”)- 标志位ACK=1(最终确认) | 客户端:ESTABLISHED(连接完全建立,可发数据)服务器:收到Ack=201(正确),进入ESTABLISHED(连接完全建立,可收/发数据) |
很多人会问:第二次握手时服务器已经回复了SYN+ACK,为什么客户端还要再发一次ACK?
举个反例:如果只有两次握手,假设客户端早期发送的“失效SYN报文”(因网络延迟滞留)突然到达服务器,服务器会误以为是新的连接请求,回复SYN+ACK后直接进入ESTABLISHED状态,等待客户端发数据。但客户端知道这是失效请求,不会理会,导致服务器一直占用资源等待,造成浪费。
而三次握手的第三次ACK,能让服务器确认“客户端确实是要建立新连接”,避免这种“失效连接请求”的干扰——这是TCP可靠性的重要细节。
TCP连接是全双工的——就像电话两端可以同时说话,客户端和服务器也可以同时向对方发送数据。因此关闭连接时,需要双方分别确认“我这边没有数据要发了”,这就导致了“四次挥手”(三次握手因仅需同步序列号,可合并一次SYN+ACK)。
双方均处于ESTABLISHED状态(正常通信状态),某一方(通常是客户端,比如你关闭浏览器)先发起关闭请求,成为“主动关闭方”;另一方(服务器)为“被动关闭方”。

步骤 | 类比场景(你→朋友) | 技术层面(客户端→服务器) | 核心数据与标志位 | 双方状态变化 |
|---|---|---|---|---|
第一次挥手(“我说完了,你还有要说的吗?”) | 你先说完事情,告诉朋友“我这边没话说了” | 客户端调用close()函数,发送FIN报文段(结束报文),表示“我已无数据可发” | - 客户端当前序列号(Seq = u,比如u=500,即最后一次发数据的Seq+1)- 标志位FIN=1(FIN=Finish,结束请求) | 客户端:ESTABLISHED → FIN_WAIT_1(等待服务器确认关闭)服务器:ESTABLISHED → CLOSE_WAIT(确认收到关闭请求,此时服务器仍可向客户端发数据) |
第二次挥手(“好的,我知道你说完了,我再想想还有没有”) | 朋友回复“听到了”,但可能还有事情要跟你说 | 服务器收到FIN后,发送ACK报文段,确认客户端的关闭请求 | - 确认号(Ack = u+1 = 501,表示“已收到你Seq=500的FIN”)- 标志位ACK=1(确认有效) | 服务器:CLOSE_WAIT(继续发送剩余数据,比如服务器还没传完的网页内容)客户端:收到Ack=501后,进入FIN_WAIT_2(等待服务器说“我也说完了”) |
第三次挥手(“我也说完了,那挂了吧”) | 朋友说完剩余事情,告诉“我也没话说了” | 服务器发送完所有数据后,调用close(),发送FIN报文段,表示“我也无数据可发” | - 服务器当前序列号(Seq = v,比如v=600,即最后一次发数据的Seq+1)- 标志位FIN=1(结束请求) | 服务器:CLOSE_WAIT → LAST_ACK(等待客户端最终确认关闭)客户端:收到FIN后,进入TIME_WAIT(关键状态,等待2MSL时间) |
第四次挥手(“好的,挂吧”) | 你回复“好的”,确认双方都没话说了 | 客户端收到FIN后,发送ACK报文段,最终确认关闭 | - 确认号(Ack = v+1 = 601,表示“已收到你Seq=600的FIN”)- 标志位ACK=1(最终确认) | 客户端:TIME_WAIT(等待2MSL后进入CLOSED)服务器:收到Ack=601后,进入CLOSED(连接完全关闭,释放资源) |
LAST_ACK状态。文档明确指出:服务器出现大量CLOSE_WAIT,本质是“服务器没有正确调用close()函数”——服务器收到客户端的FIN后进入CLOSE_WAIT,但因代码bug(比如忘记关闭socket),一直不发送自己的FIN,导致连接长期滞留在此状态。
解决方法很简单:检查服务器代码,确保在“无需继续发送数据”时主动调用close(),释放socket资源。
因为TCP是全双工的,关闭连接需要“双向确认”:
维度 | 三次握手(建立连接) | 四次挥手(关闭连接) |
|---|---|---|
核心目的 | 双向确认收发能力、协商序列号 | 双向确认无数据可发、释放资源 |
交互次数 | 3次(含1次SYN+ACK合并) | 4次(无合并,全双工需分别确认) |
关键标志位 | SYN(同步)、ACK(确认) | FIN(结束)、ACK(确认) |
主动方状态流 | CLOSED → SYN_SENT → ESTABLISHED | ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED |
被动方状态流 | CLOSED → LISTEN → SYN_RCVD → ESTABLISHED | ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED |
资源占用 | 建立后占用socket、缓冲区等资源 | 关闭过程中逐步释放资源(TIME_WAIT阶段仍占用少量资源) |
无论是三次握手还是四次挥手,TCP的设计始终围绕“可靠”和“高效”两个关键词:
希望通过本文的类比和拆解,你能彻底搞懂TCP三次握手与四次挥手——下次再遇到相关问题,不妨想想“打电话”的场景,很多复杂的技术细节都会变得通俗易懂!