在网络通信的复杂架构里,“三次握手”与“四次挥手”仿若一座无形的桥梁,它们是连接客户端与服务器的关键纽带。这座“桥梁”不仅确保了连接的稳固建立,还保障了连接的有序结束,使得网络世界中的信息能够顺畅、准确地流动。
在面试过程中,TCP 三次握手、四次挥手也经常被问到的问题。本文就来快速、详细的介绍下 TCP 三次握手、四次挥手的全部过程。
TCP 的三次握手和四次挥手实质就是 TCP 通信的连接和断开:
- **三次握手:**同步双方的初始序列号(ISN),确认双方收发能力正常;
- **四次挥手:**双方独立关闭数据通道,确保数据完整传输。
TCP头格式组成
为了使你更好的理解 TCP 三次握手和四次挥手过程,本小节先介绍下 TCP 头部格式。
- 源端口号和目的端口号:代表连接发起方和连接接收方
- 序号:在建立连接时,由计算机生产的随机数作为初始值,通过SYN包传给接收端主机,每发送一次数据,就累加一次该数据字节数的大小。用来解决网络包乱序问题。
- 确认序号:指下一次期望收到的数据的序号,发送端收到这个确认应答以后,可以认为在这个序号以前的数据,都已经被正常接收。 用来解决网络丢包的问题
- 标志位,如上图,一共6个
- URG
- ACK:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1
- PSH
- RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。
- SYN:该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。
- FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。
- 数据:连接需要发送的内容。
建立连接:三次握手
三次握手(Three-way Handshake)是 TCP 协议中用于建立连接的一个重要环节。在这一过程中,客户端和服务器需要互相发送三个数据包,以确保双方的接收和发送能力均正常,并为后续的数据传输指定初始化序列号,从而确保数据传输的可靠性。
TCP 三次握手流程图如下所示:
图中字符详解:
SYN
:代表连接请求或接收的报文段。seq
:指发送的第一个字节的序号。ACK
:确认报文段,用于回应 SYN。ack
:确认号,表示希望收到的下一个数据的第一个字节的序号。
在 TCP 协议中,主动发起连接请求的一方被称为客户端,而被动等待连接的一方则被称为服务端。无论是客户端还是服务端,一旦 TCP 连接成功建立,双方均可进行数据的发送与接收。
连接建立之初,服务器和客户端都处于 CLOSED 状态。在通信正式开始前,双方需要分别创建自己的传输控制块(TCB)。服务器完成 TCB 创建后,会进入 LISTEN 状态,随时准备接收客户端发来的连接请求。
第一次握手
客户端向服务端发送一个 SYN 报文(SYN=1),并指明客户端的初始化序列号 ISN(x
),即图中的 seq=x
,它表示本报文段所发送的数据的第一个字节的序号。在发送 SYN 报文后,客户端进入 SYN_SENT
状态,意味着它正在等待服务端的连接确认。
SYN_SENT
状态解释:当客户端发送连接请求后,它进入 SYN_SENT
状态,等待服务端的响应。在这个状态下,客户端准备好了接受服务端的连接确认。
TCP 协议规定:SYN=1
的报文段是用于建立连接的请求,它不携带任何数据,但会消耗一个序号。这是 TCP 协议确保连接建立过程中的有序性和可靠性的一种方式。
第二次握手
服务器在接收到客户端的 SYN 报文后,会以 SYN 报文作为回应(SYN=1
),并赋予自己独特的初始化序列号ISN(y),即图中的 seq=y
。同时,服务器将客户端的 ISN+1 设置为确认号 ack 的值,以此确认已收到客户端的 SYN 报文,并期待接收到的下一个数据报的起始序号为 x+1。在此之后,服务器会进入 SYN-RCVD
状态,等待对连接请求的进一步确认。
**SYN-RCVD 状态解析:**当服务器在收到并发送连接请求后,会进入 SYN-RCVD
状态,此时它正在等待对初始连接请求的确认。在这个状态下,服务器已经准备好接受来自客户端的进一步通信。
TCP 协议规定:SYN=1 且 ACK=1 的报文段是用于确认连接的应答,它同样不携带任何数据,但通过确认号的使用,确保了连接建立过程中的有序性和可靠性。
第三次握手
在收到服务器发送的 SYN 报文后,客户端会回应一个 ACK 报文。这个 ACK 报文将服务器的 ISN+1 作为 ack 的值,表明客户端已经收到了服务器的 SYN 报文,并期待接收到的下一个数据报的起始序号为 y+1。
同时,客户端将自己的序列号 seq 设置为 x+1,即初始序列号 seq=x 增加 1。完成这些操作后,客户端进入 ESTABLISHED
状态,表示连接已成功建立。服务器在收到这个 ACK 报文后,也会转入 ESTABLISHED
状态,此时双方连接的建立工作全部完成。
**ESTABLISHED 状态解释:**当一个 TCP 连接进入 ESTABLISHED 状态时,它意味着连接已经打开,数据可以开始在双方之间传送。
断开连接:四次挥手
TCP 连接的终止需要经过四次包的交换,因此被称为四次挥手。在这四次交换中,客户端或服务器都可以主动发起连接的释放动作。值得注意的是,TCP 连接是双向的,因此四次挥手中,前两次主要用于断开一个方向的连接,后两次则用于断开另一方向的连接。
第一次挥手
客户端首先发送一个 FIN 报文,其中包含一个序列号 seq=u,表示请求连接终止。在发送完毕后,客户端停止数据发送,并主动关闭 TCP 连接。此时,客户端进入 FIN_WAIT_1 状态,等待服务器的确认。
**FIN_WAIT_1 状态解析:**该状态表示客户端正在等待远程 TCP 的连接中断请求,或者等待先前连接中断请求的确认。FIN=1 标志着该报文段是一个连接释放请求。而 seq=u 则代表客户端向服务器发送的最后一个字节的序号。
第二次挥手
服务端在收到客户端的 FIN 报文后,会发送一个 ACK 报文作为回应。这个 ACK 报文中,序列号值设为客户端序号值加 1,意在确认已收到客户端的报文。随后,服务端进入 CLOSE_WAIT 状态,等待本地用户的连接中断请求。
CLOSE_WAIT 状态解析:在此状态下,服务端等待来自本地用户的连接释放请求。ACK 报文中的 ACK=1 表示应答,而 seq=v 则指明了服务端释放应答报文段的首字节序号。同时,ack=u+1 表明服务端希望从第 u+1 个字节开始接收报文段,并已成功接收了前 u 个字节。
完成第二次挥手后,客户端到服务端的连接已释放,服务端不再接收客户端数据,而客户端也已无数据待发送。然而,服务端到客户端的连接仍保持开启,若服务端在此期间发送数据,客户端仍需正常接收。此状态将持续一段时间,直至整个 CLOSE-WAIT 状态结束。
第三次挥手
服务端在完成数据的发送后,会向客户端发送一个连接释放报文。这个报文头包含 FIN 标志位为 1,以及 ack 序号值为 u+1。由于在 CLOSE_WAIT 状态期间,服务端可能又发送了一些数据,假设此时的序列号为 seq=w。发送完毕后,服务端进入 LAST_ACK 状态,等待来自客户端的连接中断确认。
第四次挥手
客户端在收到服务端的 FIN 报文后,会响应一个 ACK 报文,其中 ack 序号值为 w+1,同时将自己的序列值加 1作为 ACK 报文的 seq 序号值,即 seq=u+1。此后,客户端进入 TIME_WAIT 状态。
**TIME_WAIT:**确保远程 TCP 收到连接中断请求的确认状态会持续 2MSL(最长报文段寿命)的时间。在此期间, TCP 连接并未完全释放。若在这段时间内未收到服务端的重发请求,客户端将进入 CLOSED 状态,并撤销 TCB。
服务端在收到客户端的确认 ACK 报文后,会立即进入 CLOSED 状态,并撤销 TCB,从而结束此次 TCP 连接。值得注意的是,服务端结束 TCP 连接的时间点通常早于客户端。
四次挥手 vs 三次握手
阶段 | 三次握手 | 四次挥手 |
---|---|---|
目的 | 建立连接,同步序列号 | 安全关闭双向数据通道 |
交互次数 | 3次 | 4次 |
关键标志位 | SYN 、ACK | FIN 、ACK |
状态复杂度 | 简单(直接建立) | 复杂(需处理半关闭和TIME_WAIT ) |
关联面试题
1. 为何 TCP 建立连接时采用三次握手而非两次或四次?
- 两次不够:无法确认客户端的接收能力(若服务端的
SYN-ACK
丢失,客户端不知服务端已就绪)。 - 四次冗余:三次已能确保双向通信能力,无需额外交互。
具体原因如下:
- 确保双方都能发送和接收:三次握手可以确保双方都具备发送和接收数据的能力。在第一次握手时,客户端发起请求,第二次握手时,服务器确认收到了请求并表示自己也可以通信,第三次握手时,客户端确认收到了服务器的响应。这保证了通信的可靠性。
- 防止重复连接初始化问题:假设没有三次握手,而是两次握手,那么可能会出现一种情况:客户端发送了一个连接请求(SYN),但由于网络问题,服务器没有及时收到或者延迟了。客户端在等待了一段时间后认为请求失败,重新发送了一个新的 SYN,而此时第一个 SYN 报文延迟到达服务器,服务器误认为客户端又发起了一次新的连接请求,从而产生混乱。通过三次握手,能够有效避免这种问题,确保连接的一致性。
- 同步初始序列号:三次握手过程中,客户端和服务器会相互交换各自的初始序列号,以保证接下来的数据传输能够按顺序接收和处理。这是实现TCP可靠传输的关键步骤之一。
三次握手的设计主要是为了确保在不可靠的网络环境中,TCP连接的建立过程能够具有可靠性和一致性,并能够防止潜在的错误连接。
2. 为何 TCP 关闭连接时需要四次挥手?
- 保证双方数据完整性:在关闭连接之前,双方需要确认所有数据已经被成功接收。第一次挥手时,客户端发送 FIN 报文,表示自己不再发送数据,但仍然可以接收数据。第二次挥手时,服务器确认收到这个 FIN,并且可以继续发送数据。第三次挥手时,服务器发送自己的 FIN 报文,表示自己也不再发送数据。最后,客户端确认服务器的 FIN,确保双方都完成了数据传输。
- 分半关闭(Half-Close):TCP 连接是全双工的,这意味着数据可以双向流动。四次挥手允许连接的一方关闭数据发送通道,但仍然可以接收数据,直到另一方也关闭发送通道。因此,四次挥手过程允许双向关闭连接,确保双方都能完成数据传输。
- 确保完整关闭:客户端在进入
TIME-WAIT
状态后,会等待一段时间以确保服务器收到了最后的 ACK 报文。这段时间可以避免因网络延迟等问题导致的重复数据问题,确保连接完全关闭后再释放资源。
3. 为何 TIME_WAIT 状态需持续 2MSL 后才能转为 CLOSE 状态?
当 TCP 连接的一方完成连接释放后,会进入 TIME_WAIT 状态。这个状态需要持续 2 倍的最大段寿命(Maximum Segment Lifetime,MSL)的时间,这是为了确保在传输过程中可能存在的延迟数据包能够被对方完全接收。只有当 2MSL 时间过去后,确认对方已收到所有数据,该 TCP 连接才能完全关闭,进 入CLOSE 状态。
1、确保服务端能够接收到客户端的确认应答。
如果客户端在发送完确认应答后立即进入 CLOSED 状态,而该应答不幸丢失,服务端在等待超时后将尝试重新发送连接释放请求。但此时,由于客户端已经关闭,无法再作出响应,这会导致服务端无法正常关闭 TCP 连接。因此,TIME_WAIT 状态的持续存在,是为了保证服务端能够接收到并处理客户端的确认应答,从而确保连接的平滑关闭。
2、防止“三次握手”中提及的“已失效的连接请求报文段”干扰当前连接。
当客户端发送完最后一个确认报文后,经过 2MSL 的时间间隔,可以确保在本连接持续时间内产生的所有报文段都已从网络中清除。这样,新建立的连接就不会受到旧连接请求报文的影响。
3、为什么是 2MSL?
2MSL 的时间是从客户端收到 FIN 报文段后发送给服务器 ACK 开始计时的,考虑到重传的因素,那么就需要服务器再次给客户端传 FIN+ACK 报文段。
保证在两个传输方向上的尚未被接收或迟到的报文段都消失,理论上保证最后一个报文可靠到达,就需要 2MSL,一个方向一个 1MSL。
4. CLOSE_WAIT 状态有什么影响?
当服务器收到客户端的 FIN,并回复了 ACK 后,会进入 CLOSE_WAIT 状态,此时 TCP 链接处于半关闭状态。CLOSE_WAIT 状态一直存在就说明服务器没有调用 close 并没有发送 FIN 报文段。而服务期长期保持这个状态,就会一直占用这大量的 socket 文件描述符,大量的 CLOSE_WAIT 状态存在就会导致文件描述符被占用,一些客户端无法连接。
5. TIME_WAIT和CLOSE_WAIT的区别?
CLOSE_WAIT 状态是被动关闭的一端在接收到另一端关闭请求过后并将 ACK 发送出去后所处的状态。这种状态表示:收到了对端关闭的情况,但是本端还没有完成工作,未关闭。
TIME_WAIT 状态是主动关闭一端在本端已经关闭的前提下,收到对端的关闭请求并且将 ACK 发送出去所处的状态。这种状态表示:双方都已经完成工作,只是为了确保迟来的数据报能被识别丢弃,可靠的终止 TCP 连接。
6. 为什么连接的时候是三次握手,关闭的时候却是四次挥手?
因为当服务端收到客户端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文。其中 ACK 报文是用来应答的,SYN 报文是用来同步的。
但是关闭连接时,当服务端收到 FIN 报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉客户端,“你发的 FIN 报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送 FIN 报文,因此不能一起发送。故需要四步握手。
7. 如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP 设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为 2 小时,若 2 小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔 75 分钟发送一次。若一连发送 10 个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
8. 在三次握手中,SYN 和 ACK 的作用是什么?
在 TCP 的三次握手中,SYN 和 ACK 确保了连接的建立和通信的同步。
- SYN(Synchronize)是 TCP 协议中的一个标志位,用于在建立连接时进行通信的同步。在三次握手的第一次交互中,客户端发送一个 SYN=1,ACK=0 标志的数据包给服务端,请求建立连接。这个 SYN 包的作用是向服务端发起连接请求,并附带一个序列号(Sequence Number),用于标识后续发送的数据包。SYN 标志位的设置表示客户端希望建立一个新的连接或确认一个连接请求;
- ACK(Acknowledgement)是确认标志,用于确认接收到的数据包。在第二次握手中,服务端收到客户端的 SYN 包后,会发送一个 SYN=1,ACK=1 标志的数据包给客户端。这个 SYN+ACK 包的作用是告诉客户端,服务端已经收到了连接请求,并允许建立连接。同时,ACK=1 表示服务端对客户端发送的 SYN 包进行了确认。此外,服务端也会发送自己的序列号给客户端,用于后续的数据传输。
- 在第三次握手中,客户端收到服务端的 SYN+ACK 包后,会发送一个 SYN=0,ACK=1 的数据包给服务端。这个 ACK 包的作用是告诉服务端,客户端已经收到了 SYN+ACK 包,并对服务端的 SYN 包进行了确认。
- 至此,三次握手完成,TCP 连接建立成功,双方可以开始进行数据传输。
在整个过程中,SYN 和 ACK 标志位确保了连接的建立和通信的同步。SYN 用于发起连接请求和标识序列号,而ACK 用于确认接收到的数据包。这种机制可以有效地确保数据的可靠传输和连接的稳定性。
9. 三次握手过程中可以携带数据吗?
在 TCP 的三次握手过程中,SYN 和 SYN+ACK 报文段是不携带数据的,它们仅仅用于建立连接时的同步和确认。但是,最后一次的 ACK 报文段是可以携带数据的。这是因为当发送方收到对方的 SYN+ACK 报文段后,连接就已经建立了,此时发送方就可以立即发送数据,而这个数据就可以和 ACK 报文段一起发送,从而提高了效率。
虽然第三次握手可以携带数据,但在实际网络编程中,并不推荐这样做。因为这样做可能会带来一些问题,比如接收方可能无法及时准备好接收数据,导致数据丢失或乱序。
因此,通常建议将数据的发送放在三次握手完成之后进行,以确保数据的可靠传输。
10. TCP 连接中的半连接队列和全连接队列是什么?
TCP 连接中的半连接队列(也称为 SYN 队列)用于存储处于 TCP 三次握手过程中第一步的连接请求。
当服务端收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列中,等待完成三次握手的过程。此时,连接请求还没有完成握手,因此被认为是“半连接”。如果半连接队列满了,新来的连接请求可能会被丢弃或者根据系统配置发送 RST 报文。
全连接队列就是已经完成三次握手,建立起连接的就会放在全连接队列中。
半连接队列的主要作用是管理并跟踪那些尚未完全建立的连接,确保在三次握手完成之前,这些连接请求能够得到妥善的处理。它是 TCP 协议保证连接可靠性和性能的重要机制之一。
需要注意的是,当服务端并发处理大量请求时,如果 TCP 半连接队列过小,就容易出现溢出的情况,导致后续的请求被丢弃,从而影响服务端的请求处理能力。因此,合理设置和调整半连接队列的大小对于优化网络性能和提升系统稳定性具有重要意义。
11. TCP 连接中 ISN(Initial Sequence Number)是什么?
在 TCP 连接中,ISN(Initial Sequence Number,初始序列号)是每个 TCP 连接在建立时由 TCP 协议为每一个数据包所赋予的序列号。它是 TCP 可靠传输的一个重要组成部分,用于确保数据的顺序性和完整性。
- 当 TCP 连接建立时,客户端和服务器都会选择一个初始序列号(ISN)作为它们发送的第一个数据包的序列号。这个序列号是一个随机值,通常不会重复,用于标识该连接中的每一个数据包的顺序。
- 在数据传输过程中,TCP 协议会根据数据包的发送顺序为每个数据包分配一个递增的序列号。ISN 可以看作是一个 32 比特的计数器,每 4ms 加 1 。
- 通过序列号,接收方可以准确地按照发送方的发送顺序来重组数据,从而确保数据的顺序性。
- 此外,序列号还可以用于检测丢失或重复的数据包。当接收方发现数据包的序列号不连续时,它会向发送方发送一个 ACK(确认)消息,请求发送方重传丢失的数据包。同样地,如果接收方接收到一个重复的数据包(即具有相同序列号的数据包),它可以通过序列号来识别并丢弃这个重复的数据包。
因此,ISN 在 TCP 连接确保了数据的顺序性和完整性,为 TCP 的可靠传输提供了基础。
12. 为什么 TCP 连接建立需要发送序列号?
TCP 连接建立需要发送序列号的原因主要有以下几点:
- 确保数据的顺序性: TCP 是一个面向连接的、可靠的、基于字节流的传输层通信协议。在 TCP 通信中,发送方和接收方都需要按照数据的发送顺序来接收和处理数据。序列号用于标识每一个发送的数据包,确保接收方能够按照正确的顺序重组数据。
- 实现可靠传输: TCP 通过序列号来实现数据的可靠传输。当接收方收到数据包后,会向发送方发送一个确认(ACK)消息,告知已经成功接收到的数据包的序列号。如果发送方在某个时间点内没有收到某个数据包的 ACK,它会认为该数据包丢失,并重新发送该数据包。序列号使得发送方能够准确地知道哪些数据包已经成功发送并被接收,哪些数据包需要重传。
- 处理网络中的数据包乱序: 在网络传输过程中,由于网络拥塞、路由变化等原因,数据包可能会乱序到达接收方。通过序列号,接收方能够识别并重新排序这些乱序的数据包,确保数据的完整性和正确性。
- 流量控制和拥塞控制: TCP 还利用序列号来实现流量控制和拥塞控制。发送方会根据接收方的确认消息和当前的网络状况来调整发送速率,以避免网络拥塞和数据丢失。序列号在这个过程中起到了关键作用,帮助发送方和接收方协调数据的发送和接收。
TCP 连接建立需要发送序列号是为了确保数据的顺序性、实现可靠传输、处理网络中的数据包乱序以及实现流量控制和拥塞控制。这些功能共同保证了 TCP 能够提供高效、可靠的数据传输服务。
13. 在 TCP 通信中,如果一方突然崩溃,另一方如何知道?
在 TCP 通信中,如果一方突然崩溃(例如,由于硬件故障、操作系统崩溃或应用程序异常终止),另一方通常通过以下几种机制来检测这种情况:
- 心跳机制(Keep-Alive): TCP 本身并没有一个显式的“心跳”机制,但许多操作系统和应用程序层协议实现了这种机制。通过定期发送小的数据包(通常称为“探测包”或“心跳包”),接收方可以通知发送方它仍然存活并且连接仍然有效。如果发送方在一段时间内没有收到响应,它可能会认为接收方已经崩溃,并关闭连接。
- 超时重传和重试: TCP 使用超时重传机制来处理丢失的数据包。当发送方发送一个数据包后,它会等待一个确认(ACK)。如果在一定的超时时间内没有收到 ACK,发送方会重传该数据包。如果经过多次重传仍然未收到确认,发送方会认为连接已经中断,并关闭连接。同样,接收方在收到乱序的数据包或数据包丢失时,也会通过发送重复 ACK 或通知发送方进行快速重传来处理。
- 应用层协议: 除了 TCP 本身的机制外,应用层协议(如 HTTP、FTP 等)通常也会实现自己的连接管理和错误处理机制。这些协议可能会定义特定的消息或命令来通知对方连接已经中断,或者通过超时和重试策略来处理突然的崩溃。
- 操作系统和网络栈的通知: 在某些情况下,当一方崩溃时,操作系统或网络栈可能会向另一方发送一个特殊的信号或错误消息。例如,当 TCP 连接的一方异常终止时,操作系统可能会向另一方发送一个 RST(重置)数据包来关闭连接。
由于网络的复杂性和不确定性,有时候即使一方崩溃,另一方也可能无法立即检测到。这取决于网络状况、操作系统的实现以及应用层协议的设计。因此,在设计和实现基于 TCP 的应用程序时,应该考虑到这种可能性,并采取相应的错误处理和恢复策略。
- 知识星球:云原生AI实战营。10+ 高质量体系课( Go、云原生、AI Infra)、15+ 实战项目,P8 技术专家助你提高技术天花板,入大厂拿高薪;
- 公众号:令飞编程,分享 Go、云原生、AI Infra 相关技术。回复「资料」免费下载 Go、云原生、AI 等学习资料;
- 哔哩哔哩:令飞编程 ,分享技术、职场、面经等,并有免费直播课「云原生AI高新就业课」,大厂级项目实战到大厂面试通关;