Skip to Content
计算机网络TCP 协议

TCP 协议

可靠传输的实现

  1. 滑动窗口(字节为单位)

  2. 超时重传

  3. 选择确认

拥塞控制

  1. 慢开始:(起始或超时后)由 1 开始,成倍增加

  2. 拥塞避免:达到阈值启用,每次加 1,线性增加

  3. 快重传:若连续收到 3 个同一报文段的重复确认,立即进行重传该报文段

  4. 快恢复:仅丢失个别报文段时,不重新进行慢开始,而是调整阈值且直接进行拥塞避免算法

变化情况图如下:

拥塞控制

(1) 进入拥塞避免算法

(2) 调整阈值,重新进入慢开始算法

(3) 重新进入拥塞避免算法

(4) 进入快重传算法

(5) 调整阈值,进入拥塞避免算法


连接:三次握手

在连接阶段主要解决以下三个问题

  1. 要使每一方能够给确知对方的存在

  2. 要允许双方协商一些参数(如:最大窗口值、是否使用窗口扩大选项和时间戳选项以及服务质量等)

  3. 能够对运输实体资源(如缓存大小、连接表中的项目等)进行分配

连接过程

三次握手

  1. 第一次握手:建立连接时,客户端向服务器发送 SYN 包(seq=x),请求建立连接,等待确认

  2. 第二次握手:服务端收到客户端的 SYN 包,回一个 SYN + ACK 包(ack=x+1,seq=y)给客户端

  3. 第三次握手:客户端收到 SYN + ACK 包,再回一个 ACK 包(ack=y+1)告诉服务端已经收到

  4. 三次握手完成,成功建立连接,开始传输数据


关闭:四次挥手

四次挥手

  1. 客户端发送 FIN 包(FIN=1)给服务端,告诉它自己的数据已经发送完毕,请求终止连接,此时客户端不发送数据,但还能接收数据

  2. 服务端收到 FIN 包,回一个 ACK 包给客户端告诉它已经收到包了,此时还没有断开 socket 连接,而是等待剩下的数据传输完毕

  3. 服务端等待数据传输完毕后,向客户端发送 FIN 包,表明可以断开连接

  4. 客户端收到后,回一个 ACK 包表明确认收到,等待一段时间,确保服务端不再有数据发过来,然后彻底断开连接


连接与关闭的状态转移

状态转移

上半部分是 TCP 三路握手过程的状态变迁,下半部分是 TCP 四次挥手过程的状态变迁。

  1. CLOSED:起始点,在超时或者连接关闭时候进入此状态,这并不是一个真正的状态,而是这个状态图的假想起点和终点。

  2. LISTEN:服务器端等待连接的状态。服务器经过 socket,bind,listen 函数之后进入此状态,开始监听客户端发过来的连接请求。此称为应用程序被动打开(等到客户端连接请求)。

  3. SYN_SENT:第一次握手发生阶段,客户端发起连接。客户端调用 connect,发送 SYN 给服务器端,然后进入 SYN_SENT 状态,等待服务器端确认(三次握手中的第二个报文)。如果服务器端不能连接,则直接进入 CLOSED 状态。

  4. SYN_RCVD:第二次握手发生阶段,跟 3 对应,这里是服务器端接收到了客户端的 SYN,此时服务器由 LISTEN 进入 SYN_RCVD 状态,同时服务器端回应一个 SYN + ACK 给客户端。状态图中还描绘了这样一种情况,当客户端在发送 SYN 的同时也收到服务器端的 SYN 请求,即两个同时发起连接请求,那么客户端就会从 SYN_SENT 转换到 SYN_REVD 状态。

  5. ESTABLISHED:第三次握手发生阶段,客户端接收到服务器端的 ACK 包(ACK,SYN)之后,也会发送一个 ACK 确认包,客户端进入 ESTABLISHED 状态,表明客户端这边已经准备好,但 TCP 需要两端都准备好才可以进行数据传输。服务器端收到客户端的 ACK 之后会从 SYN_RCVD 状态转移到 ESTABLISHED 状态,表明服务器端也准备好进行数据传输了。这样客户端和服务器端都是 ESTABLISHED 状态,就可以进行后面的数据传输了。所以 ESTABLISHED 也可以说是一个数据传送状态。

下面看看TCP四次挥手过程的状态变迁。

  1. FIN_WAIT_1:第一次挥手。主动关闭的一方(执行主动关闭的一方既可以是客户端,也可以是服务器端,这里以客户端执行主动关闭为例),终止连接时,发送 FIN 给对方,然后等待对方返回 ACK 。调用 close() 第一次挥手就进入此状态。

  2. CLOSE_WAIT:接收到 FIN 之后,被动关闭的一方进入此状态。具体动作是接收到 FIN,同时发送 ACK。之所以叫 CLOSE_WAIT 可以理解为被动关闭的一方此时正在等待上层应用程序发出关闭连接指令。TCP 关闭是全双工过程,这里客户端执行了主动关闭,被动方服务器端接收到 FIN 后也需要调用 close 关闭,这个 CLOSE_WAIT 就是处于这个状态,等待发送 FIN,发送了 FIN 则进入 LAST_ACK 状态。

  3. FIN_WAIT_2:主动端(这里是客户端)先执行主动关闭发送 FIN,然后接收到被动方返回的 ACK 后进入此状态。

  4. LAST_ACK:被动方(服务器端)发起关闭请求,由状态 2 进入此状态,具体动作是发送 FIN 给对方,同时在接收到 ACK 时进入 CLOSED 状态。

  5. CLOSING:两边同时发起关闭请求时(即主动方发送 FIN,等待被动方返回 ACK,同时被动方也发送了 FIN,主动方接收到了 FIN 之后,发送 ACK 给被动方),主动方会由 FIN_WAIT_1 进入此状态,等待被动方返回 ACK。

  6. TIME_WAIT:从状态变迁图会看到,四次挥手操作最后都会经过这样一个状态然后进入 CLOSED 状态。


常见面试题

1. TCP 两次次握手行不行?为什么要三次?

  1. 为了实现可靠数据传输,TCP 协议的通信双方,都必须维护一个序列号,以标识发送出去的数据包中,哪些是已经被对方收到的。三次握手的过程即是通信双方相互告知序列号起始值,并确认对方已经收到了序列号起始值的必经步骤

  2. 如果只是两次握手,至多只有连接发起方的起始序列号能被确认,另一方选择的序列号则得不到确认

2. 如果三次握手时每次握手信息对方均为收到,会发生什么?

  1. 如果第一次握手消息丢失,那么请求方(客户端)不会得到 ack 消息,超时后进行重传

  2. 如果第二次握手消息丢失,那么请求方(服务端)不会得到 ack 消息,超时后进行重传

  3. 如果第三次握手消息丢失,那么服务端将该 TCP 连接的状态修改为 SYN_RECV(SYN-RCVD),并且会根据 TCP 的超时重传机制,会等待 3、6、12 秒后重新发送 SYN + ACK 包,以便客户端重新发送 ACK 包

    • 服务端可以设置发送的最大次数,如 /proc/sys/net/ipv4/tcp_synack_retries,默认为 5 次

    • 客户端一般通过 connect 函数连接服务端,connect 函数是在第二次握手完成后就成功返回值,即客户端接受 SYN + ACK 包之后,状态变为 ESTABLISHED。如果第三次握手的 ACK 包丢失,客户端向服务端发送数据,服务端将以 RST 包响应,便能感知到服务端的错误

3. 为什么客户端连接需要等待 2 MSL 的时间才能完全关闭?

  1. 保证客户端发送的最后一个 ACK 报文段能够到达服务端

    • 如果最后一个 ACK 丢失,服务端会重新发送 FIN + ACK 包,则客户端能在 2 MSL 时间内收到重传的 FIN + ACK 包

    • 如果最后一个 ACK 丢失,客户端不进行等待,则服务端无法进入 CLOSED 状态

  2. 防止“已失效的连接请求报文段”出现在本连接中

    • 在最后的 2 MSL 时间内,可以使本连接持续时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现旧的连接请求报文段

    • 如果客户端只等待 1 MSL 立即建立新连接,服务端在客户端刚好关闭时发送了数据包,在 1 MSL 发送到客户端,此时客户端处于新连接,会误将旧报文认为是新报文,导致数据错误

4. TCP 与 UDP 的区别?

  1. TCP 是面向连接的;UDP 是面向无连接的

  2. TCP 是可靠的,提供交付保证,传输中丢失会重发;UDP 是不可靠的,不提供任何交付保证

  3. TCP 保证了消息的有序性,接受到数据会进行排序;UPD 不提供有序性保证

  4. TCP 不保存数据边界,基于流;UDP 保存数据边界,有明确的包的概念

  5. TCP 速度慢,发送消耗高;UDP 速度快,发送消耗低

  6. TCP 有流量和拥塞控制;UDP 没有流量和拥塞控制

  7. TCP 更适合需要高可靠但对传输时间要求不高的应用,如金融等;UDP 适合不需要高可靠,但需要快速、高效的传输数据,如游戏等

  8. 基于 TCP 的协议:Telnet、FTP、SMTP 等;基于 UDP 的协议:DHCP、DNS、SNMP、TFTP、BOOTP 等

5. 从系统层面上,UDP 如何保证尽量可靠?

  1. 增加 SYN、ACK 机制,确保数据能发送到对端

  2. 增加发送和接收缓冲区(发送时保留旧数据),主要是用于超时重传

  3. 增加超时重传机制

6. 服务端怎么判断客户端断开了连接

  1. TCP 内部机制采用 keepalive,此链接一段时间内没有活动,然后发出数据段,经过多次尝试后,如果仍没有响应,则判断连接中断

  2. 应用层实现,如 heart-beat

最近更新于