Skip to content

4.1 TCP 三次握手与四次挥手面试题


TCP 基本认识

什么是 TCP?

一种传输层通信协议。

  • 面向连接:一对一连接
  • 可靠的:通过序列号和确认应答号保证报文无差错、不重复、有序到达接收端;
  • 字节流:消息被编码成字节报文帧,一个用户消息会被拆分成多个报文或者多个消息一个报文,流式传输
    • 可能出现粘包:多个消息的某个部分在同一个TCP报文中,消息间无明显边界
    • 可以通过以下方式避免
      • 1、特殊字符作为边界:如回车或换行,会导致消息中无法使用这类特殊字符
      • 2、自定义消息结构,如json结构
  • 通过以下四元组确定一个 TCP 连接
    • 源地址、目的地址(IP头部中,确定主机)
    • 源端口、目的端口(TCP头部中,确定进程)
    • 最大连接数就是 IP 数 ✖️ 端口数,但是每个连接都要占用内存,且系统有最大文件打开数量,所以很难达到理论值
  • 适用于文件传输、HTTP等

UDP

  • 无连接:支持多对多通信
  • 不可靠传输:尽最大可能交付数据,无序,可能丢包
  • 面向报文:每个 UDP 报文就是一个用户完整消息,由IP层进行分片,TCP在传输层分片
  • 只需要确定目标端口和源端口(确定对方的进程即可)
  • 适用于DNS、视频、广播等

TCP 和 UDP 允许使用同一个端口,因为同一个进程可以同时有可靠和不可靠传输,直播软件等直播是UDP、用户信息、聊天时TCP这种,在内核中时完全独立的,其实是在内核中两个进程

一般因为 UDP 不需要保证可靠性,所以 UDP 更快,但是对于一些需要可靠的,比如语音电话(需要保证有序),那么丢包也需要重传 但是 UDP 本身不会分片,依赖 IP 层的分片,所以如果丢包后,会重传一个大的数据包,而 TCP 本身会分片,丢包后,会重传对应的分段就行,这个时候 UDP 可能就会更慢了,如果这个时候 UDP 也实现了分段,那其实就差不多的。

TCP 连接建立

建立过程

  • 1、服务端主动监听某个端口,处于 LISTEN 状态
  • 2、客户端:发送给服务端请求连接,之后客户端处于 SYN-SENT 状态。
    • 随机初始化序号(client_isn),避免上一次连接的数据被下一次连接接收(序列号在下一次的有效范围内),造成混乱
    • 如果报文丢失,会触发超时重传,一般五次,每次等待时间翻倍(1s 开始),一直收不到断开本次连接
  • 3、服务端收到 SYN 报文后,发给客户端响应报文,之后服务端处于 SYN-RCVD 状态
    • 随机初始化序号(server_isn
    • 确认应答号: client_isn + 1
    • 如果报文丢失,客户端没有收到 ACK 报文,会重传,客户端没有收到 ACK 报文,也会重传
    • 这个是响应和请求报文合并了,所以少一次握手
  • 4、客户端收到服务端报文后,发送给服务端响应报文,之后客户端处于 ESTABLISHED 状态
    • 确认应答号: server_isn + 1
    • 可以携带客户到服务端的数据,因为大概率能成功
    • 如果报文丢失,服务器会一直收不到 ACK 报文,会重传 SKY-ACK 报文
      • ACK 报文是不会有重传的,只能等对方重传
  • 5、服务端收到客户端的应答报文后,也进入 ESTABLISHED 状态,可以正常响应数据
  • 之后相互发送数据,前面因为还没建立,发送数据容易出问题
  • 不需要 accept 进行建立,accept 只是负责从 TCP 全连接队列取出一个已经建立连接的 socket 给应用程序使用
  • 没有 listen 可以最按揭,但是没啥用

为什么三次握手:

  • 1、确保双方具备接受和发送的能力
  • 2、组织重复历史连接的初始化
    • 如果客户端发送 SYN 报文后宕机,重启后重新发送 SYN 报文
    • 两次报文都到达服务器,服务器回复两个 SYN 报文,但是序列号不一样
    • 客户端对于旧的报文的回复会因为匹配不上自己预期的序列号,回复 RST,服务器释放旧的连接,避免了重复历史连接的初始化
    • 如果只有两次握手,服务器回复 SYN 报文就已经 established 了,可能会直接发送数据,浪费资源,如果一次握手,服务器可能都收不到 RST 报文
  • 3、同步双方的初始序列化
    • 双方初始化序列号后都需要得到对方的应答,保证双方可靠传输
  • 4、避免资源浪费
    • 如果少于三次握手:如果服务器没有收到 AKC 报文,会重复发送,服务器会在 ACK 发送之后就建立连接,造成资源浪费
  • 三次握手已经建立可靠连接,多于三次握手就没必要了

既然 IP 层会分片,为什么 TCP 层还需要 MSS 呢?

  • MTU(Maximum Transmit Unit):一个网络包的最大长度,以太网中一般为 1500 字节(这个是权衡得到的值);(ifconfig 可以进行查看)
    • 由数据链路层提供
  • MSS:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度;
    • 如果 TCP 不进行分段,只有 IP 层分段,那么如果一个 IP 分片丢失,整个 TCP 报文都要重传,效率很低。
    • TCP 协议在建立连接的时候要协商双方的 MSS 值,以双方允许的最小值作为发送的 MSS,每次发送的时候都会重新计算
    • 一般 TCP 分段后,IP 就不会分段了,但是如果链路上有设备有更小的 MTU,那么 IP 层可能还是会进行分段,当然如何可以预先知道链路上最小的 MTU,就可以避免 IP 层的二次分片

TCP 连接断开

TCP 四次挥手过程是怎样的?

双方都可以主动断开连接,如果客户端主动断开:

  • 1、客户端发送一个 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
    • 如果报文丢失,会触发重传
  • 2、服务端收到后
    • 为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,程序读到这个数据后,向客户端发送 ACK 应答报文,接着服务端进入 CLOSE_WAIT 状态。
    • 如果报文丢失,客户端没收到 ACK 报文,就会触发重传
    • 服务端还可以单向发送数据,客户端可以接收数据不能发送
  • 3、客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态
    • 服务端发送完数据后,向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
      • 这个阶段不会持续太久,一般超过 60s 没收到服务端的 FIN 报文,客户端直接关闭
      • 如果报文丢失,服务端会触发超时重传,一直没收到,服务端主动关闭(客户端保证了自己一段时间后自动关闭)
      • 如果服务端没有数据发送,那么 FIN 报文和 ACK 报文可以合并发送,成为三次挥手,或者将数据一次整合,和 FIN-ACK 报文一起发送,也是三次挥手
  • 4、客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
    • 如果报文丢失,服务端会触发 FIN 包重传
  • 服务端收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
  • 客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。
  • FIN 报文是需要进程主动发起的,ACK 报文是内核自动响应的
  • 挥手四次是因为被动断开方可能还需要发送数据

time_wait

  • 主动关闭方,才会有该状态
  • 为什么等待时间是 2MSL(Maximum Segment Lifetime,报文最大生存时间
    • 至少允许 ACK 报文丢失一次,当 ACK 丢失,服务端会重传 FIN 报文
    • 而如果网络不好,多次丢失,那么也没必要等待了,并且长时间 time_wait 会一直占用资源
    • 每次收到服务端的 FIN 报文,这个时间会重置
  • 为什么需要 time_wait 状态
    • 防止历史连接中的数据,被后面相同四元组的连接错误接受
      • 本次连接中数据,会在这段时间里面被丢弃或者被接收,不会保留到下一次连接中(避免有数据包在下一次连接的接受窗口内造成数据混乱)
    • 保证被动关闭连接以防,能被正确的关闭
      • 如果客户端的 ACK 报文丢失,服务端会重传 FIN 报文,如果没有 time_wait,客户端直接关闭连接,收到重传的 FIN 报文,会回 RST 报文,服务端收到后认为是一个错误,结束的不够优雅
  • time_wait 过多会占用系统资源,造成一些不可用的情况
  • 优化:由客户端持有 time_wait
    • 使用长连接,如果使用短连接,由于需要服务端主动关闭连接,服务端会出现大量的 TIME_WAIT
    • 客户端长时间不发送请求,主动断开连接,否则nginx 会在超时后调用服务端主动断开连接,导致服务端持有大理胖 time_wait,客户端需要发送时,重新建立连接

如果已经建立了连接,出现故障了怎么办?

  • 客户端:如宕机、断电
    • 服务端无法感知,连接会一直处于 ESTABLISH 状态
    • 服务端会触发保活机制,发送探测报文,如果多次没有收到响应,认为当前 TCP 连接 死亡,断开连接,如果得到响应,重置保活时间
      • 如果客户端没有启动,那么服务端收不到任何响应
      • 如果客户端重启了,那么因为本地找不到对应的 TCP 连接,会发送 RST 报文,服务端直接关闭
      • 由于时间比较长,可以在客户端增加探活
    • 如果是进程崩溃,操作系统在挥手资源时会发送 FIN 报文,主动断开连接
  • 服务端:

正在精进