文章目录
TCP三次握手和四次挥手
一、什么是TCP
TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。
- 面向连接:一定是一对一才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;
- 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;
- 字节流:用户消息通过 TCP 协议传输时,消息可能会被操作系统分组成多个的 TCP 报文,如果接收方的程序如果不知道消息的边界,是无法读出一个有效的用户消息的。并且 TCP 报文是有序的,当前一个TCP 报文没有收到的时候,即使它先收到了后面的 TCP 报文,那么也不能扔给应用层去处理,同时对重复的 TCP 报文会自动丢弃。
二、什么是 TCP 连接?
简单来说就是,用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括Socket、序列号和窗口大小称为连接。
所以我们可以知道,建立一个 TCP 连接是需要客户端与服务端端达成上述三个信息的共识。
- Socket:由 IP 地址和端口号组成
- 序列号:用来解决乱序问题等
- 窗口大小:用来做流量控制
1、TCP运输连接有以下三个阶段
- 建立TCP连接,也就是通过三报文握手来建立TCP连接。
- 数据传送,也就是基于已建立的TCP连接进行可靠的数据传输。
- 释放连接,也就是在数据传输结束后,还要通过四报文挥手来释放TCP连接。
TCP的运输连接管理就是使运输连接的建立和释放都能正常的进行。
2、TCP的连接建立要解决以下三个问题
- 使TCP双方能够确知对方的存在。
- 使TCP双方能够协商一些参数(如最大窗口值、是否使用窗口扩大选项和时间戳选项以及服务质量等)。
- 使TCP双方能够对运输实体资源(如缓存大小、连接表中的项目等)进行分配。
3、TCP标志位
标 志 位 | 含义 |
---|---|
SYN | 用于建立连接时同步序列编号。当设置SYN = 1 标志位时,表示希望建立一个连接。 |
ACK | 确认应答标志位。当设置ACK标志位时,表示TCP段中的确认号字段是有效的,用于确认收到的数据。 |
FIN | 结束连接标志位。当设置FIN标志位时,表示发送方已经发送完所有的数据,请求释放连接。 |
RST | 重置连接标志位。当设置RST标志位时,需要释放连接并重新建立。 |
URG | 紧急指针标志位。它指示TCP报文段中的紧急数据字段(Urgent Pointer)有效。 |
PSH | 推送标志位。当设置PSH标志位时,接收方需尽快将该报文段中的数据传送给应用程序,而不是等待缓冲区满。 |
三、TCP的三次握手
TCP(Transmission Control Protocol)三次握手是建立一个TCP连接的过程中所采用的机制,用于在客户端和服务器之间建立可靠的通信链路。
0 | 标志位状态 | 简写 | 客户端/服务端 |
---|---|---|---|
1 | SYN=1,ACK=0 | SYN=1 | 客户端发送连接请求 |
2 | SYN=1,ACK=1 | SYN=1,ACK=1 | 服务端发送请求确认 |
3 | SYN=0,ACK=1 | ACK=1 | 客户端发送连接确认 |
以下是TCP三次握手的详细步骤及其解释:
-
第一次握手(SYN)
客户端发送同步序列编号(SYN):
- 客户端选择一个初始序列号(Seq = x),然后发送一个带有SYN = 1标志的TCP数据包到服务器。
- 这个数据包表明客户端想要建立连接,并且包含了客户端选择的初始序列号x。
- 随后客户端进入 SYN-SENT 阶段。
-
第二次握手(SYN-ACK)
服务器回应同步序列编号(SYN)-- 确认序列号(ACK):
- 标志位为 SYN 和 ACK,表示确认客户端的报文 Seq 序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接,同时提出了自己的初始序列号y(Seq = y)。
- 确认号为 ack = x + 1,表示收到客户端的序号 Seq 并将其值加 1 作为自己确认号 ack 的值
- 随后服务器端进入 SYN-RECV 阶段。
-
第三次握手(ACK)
客户端**发送确认序列号(ACK)**确认连接:
- 标志位为 ACK,表示确认收到服务器端同意连接的信号。
- 序号为 Seq = x + 1,表示收到服务器端的确认号 Ack,并将其值作为自己的序号值。
- 确认号为 Ack= y + 1,表示收到服务器端序号 seq,并将其值加 1 作为自己的确认号 Ack 的值。
- 随后客户端进入 ESTABLISHED。
通过这个过程,双方确认了对方的存在,并就序列号达成一致,这样就可以开始可靠的数据传输了。
TCP 建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。
不使用「两次握手」和「四次握手」的原因:
- 两次握手:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;
- 四次握手:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。
四、TCP的四次挥手
TCP四次挥手(Four-Way Handshake)是TCP协议中用于关闭一个已建立的连接的过程。由于TCP是一种全双工的协议,意味着数据可以双向流动,因此在关闭连接时,每个方向上的连接都需要单独关闭。
0 | 标志位状态 | 简写 | 客户端/服务端 |
---|---|---|---|
1 | FIN=1,ACK=1 | FIN=1,ACK=1 | 客户端发送断开请求 |
2 | FIN=0,ACK=1 | ACK=1 | 服务端发送确认收到请求 |
3 | FIN=1,ACK=1 | FIN=1,ACK=1 | 服务端再次发送确认断开请求 |
4 | FIN=0,ACK=1 | ACK=1 | 客户端发送断开确认 |
下面是TCP四次挥手的详细步骤:
-
第一次挥手
客户端发送FIN报文表明其想要释放 TCP 连接:
- 标记位为 FIN,表示请求释放连接。
- 序号为 Seq = u。
- 随后客户端进入 FIN-WAIT-1 阶段,即半关闭阶段,并且停止向服务端发送通信数据。
-
第二次挥手
服务器接收到客户端请求断开连接的 FIN 报文后,结束 ESTABLISHED 阶段,进入 CLOSE-WAIT 阶段并返回一段 TCP 报文:
-
标记位为 ACK,表示接收到客户端释放连接的请求。
-
序号为 Seq = v。
-
确认号为 Ack = u + 1,表示是在收到客户端报文的基础上,将其序号值加 1 作为本段报文确认号Ack 的值。
-
服务端就进入CLOSE-WAIT(关闭等待)状态,TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
-
客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
-
-
第三次挥手
服务器端在发出 ACK 确认报文后,服务器端会将遗留的待传数据传送给客户端,待传输完成后即经 过 CLOSE-WAIT 阶段,便做好了释放服务器端到客户端的连接准备,再次向客户端发出一段 TCP 报文:
- 标记位为 FIN 和 ACK,表示已经准备好释放连接了。
- 序号为 Seq = w。
- 确认号 Ack = u + 1,表示是在收到客户端报文的基础上,将其序号 Seq 的值加 1 作为本段报文确认号 Ack 的值。
- 服务器就进入LAST-ACK(最后确认)状态,等待客户端的确认。
-
第四次挥手
客户端收到从服务器发来的 TCP 报文,确认了服务器已经做好释放连接的准备,于是结束 FIN-WAIT-2 阶段,进入 TIME-WAIT 阶段,并向服务器发送一段报文:
-
标记位为 ACK,表示接收到服务器准备好释放连接的信号。
-
序号为 Seq= u + 1,表示是在已收到服务器报文的基础上,将其确认号 Ack 值作为本段序号的值。
-
确认号为 Ack= w + 1,表示是在收到了服务器报文的基础上,将其序号 Seq 的值作为本段报文确认号的值。
-
注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
-
服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
-
需要注意的是,在某些情况下,如果服务器没有数据需要发送,它可以将第二次和第三次挥手合并,即在响应客户端的FIN时同时发送自己的FIN和ACK,这样就变成了三次挥手。但是,标准的四次挥手过程是为了确保在全双工连接中,每个方向的数据流都能被正确地关闭和确认。
五、常见面试题
【问题1】为什么连接的时候是三次握手,关闭的时候却是四次挥手?
答1:因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四次挥手。
答2:释放 TCP 连接时之所以需要四次挥手,是因为 FIN 释放连接报文和 ACK 确认接收报文是分别在两次握手中传输的。 当主动方在数据传送结束后发出连接释放的通知,由于被动方可能还有必要的数据要处理,所以会先返回 ACK 确认收到报文。当被动方也没有数据再发送的时候,则发出连接释放通知,对方确认后才完全关闭TCP连接。
【问题2】为什么不直接进入关闭状态?为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答1:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可能最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在客户端发送出最后的ACK回复,但该ACK可能丢失。服务端如果没有收到ACK,将不断重复发送FIN片段。所以客户端不能立即关闭,它必须确认服务端接收到了该ACK。客户端会在发送出ACK之后进入到TIME_WAIT状态。客户端会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么客户端会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,客户端都没有再次收到FIN,那么客户端推断ACK已经被成功接收,则结束TCP连接。
答2:先看这种状况,TCP服务器进程发送TCP连接释放报文段后进入最后确认状态。TCP客户进程收到该报文段后,发送普通的TCP确认报文段,并进入关闭状态而不是时间等待状态。然后,该TCP确认报文段丢失了,这必然会造成TCP服务器进程对之前所发送的TCP连接释放报文段的超时重传,并仍处于最后确认状态。
重传的TCP连接释放报文段到达TCP客户进程。由于TCP客户进程属于关闭状态,因此不理睬该报文段。这必然会造成TCP服务器进程反复重传TCP连接释放报文段, 并一直处于最后确认状态而无法进入关闭状态。
因此时间等待状态以及处于该状态2MSL时长,可以确保TCP服务器进程可以收到最后一个TCP确认报文段而进入关闭状态。另外,TCP客户进程在发送完最后一个TCP确认报文段后,在经过2MSL时长,就可以使本次连接持续时间内所产生的所有报文段都从网络中消失,这样就可以使下一个新的TCP连接中,不会出现旧连接中的报文段。
【问题3】为什么不能用两次握手进行连接?
答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
答:TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
【问题5】CLOSE-WAIT 和TIME-WAIT 的状态和意义
答:在服务器收到客户端关闭连接的请求并告诉客户端自己已经成功收到了该请求之后,服务器进入了CLOSE-WAIT 状态,然而此时有可能服务端还有一些数据没有传输完成,因此不能立即关闭连接,而CLOSE-WAIT 状态就是为了保证服务器在关闭连接之前将待发送的数据发送完成。
TIME-WAIT 发生在第四次挥手,当客户端向服务端发送 ACK 确认报文后进入该状态,若取消该状态,即客户端在收到服务端的 FIN 报文后立即关闭连接,此时服务端相应的端口并没有关闭,若客户端在相同的端口立即建立新的连接,则有可能接收到上一次连接中残留的数据包,可能会导致不可预料的异常出现。
深入浅出TCP三次握手 (多图详解)
深入浅出TCP四次挥手 (多图详解)