计算机网络之TCP/UDP篇(上)

计算机网络之TCP/UDP篇(上)

大家好,这里是编程Cookbook。本文详细介绍计算机网络中的TCP/UDP协议相关的内容,包括单不限于基础概念、连接的建立与断开、TCP可靠传输的实现等。


@[toc]


TCP/UDP 基础概念

什么是 TCP 连接?

TCP(Transmission Control Protocol,传输控制协议)是面向连接的、可靠的、基于流的传输层协议。TCP 报文段如下所示:

TCP 报文段
TCP 报文段的首部通常为 20 字节(无选项时),最大可扩展至 60 字节。具体结构如下:

字段 长度(字节) 说明
源端口(Source Port) 2 发送方的端口号(如 54321
目的端口(Destination Port) 2 接收方的端口号(如 80 表示 HTTP)
序列号(Sequence Number) 4 本报文段数据的第一个字节的编号(用于数据排序)
确认号(Acknowledgment Number) 4 期望收到的下一个字节的序号(用于确认接收)
数据偏移(Data Offset) 4 bits TCP 首部长度(以 4 字节为单位,最小 5 → 20 字节)
保留(Reserved) 6 bits 未使用,必须置 0
控制标志(Flags) 6 bits 用于连接控制(如 SYN, ACK, FIN
窗口大小(Window Size) 2 接收方的可用缓冲区大小(流量控制)
校验和(Checksum) 2 校验首部 + 数据的完整性(含伪首部,类似 UDP)
紧急指针(Urgent Pointer) 2 仅当 URG=1 时有效,指向紧急数据的末尾
选项(Options) 可变(0-40字节) 可选字段(如 MSS、窗口缩放因子、时间戳等)
填充(Padding) 可变 确保选项字段对齐 4 字节边界
数据(Data) 可变 上层应用数据(如 HTTP 请求、文件内容等)

TCP 连接的特点:

  • 面向连接:通信前,必须通过“三次握手”建立连接,确保双方准备好数据传输。
  • 可靠传输:TCP 采用 确认机制(ACK)、超时重传、流量控制、拥塞控制,确保数据不丢失、不重复、按序到达。
  • 基于流:TCP 以字节流(Byte Stream)形式传输数据,没有明确的消息边界,需要应用层自行处理分包和粘包。
  • 全双工:双方可以同时发送和接收数据。
  • 有序传输:TCP 通过 序列号(Sequence Number) 确保数据按正确顺序到达。

TCP 连接建立过程(简要)

  1. 三次握手:保证双方通信能力,并初始化必要的参数(如序列号)。
  2. 数据传输:通过滑动窗口、超时重传、ACK 确保可靠传输。
  3. 四次挥手:确保数据完整性后关闭连接。

什么是 UDP 连接?

UDP(User Datagram Protocol,用户数据报协议)是无连接的、不可靠的、基于报文的传输层协议。 其数据报格式包含 首部(Header)数据部分(Data)。此外,在计算校验和时还会用到 伪首部(Pseudo Header)。UDP 数据报如下所示:

UDP 数据报由 8字节首部 + 数据部分 组成,具体结构如下:
![UDP数据报格式](TCP 报文段
伪首部 仅用于 校验和计算不会真正传输。它的作用是确保数据报被正确路由到目标 IP 和端口,防止 IP 欺骗或错误转发:

![UDP 数据报的首部和伪首部](TCP 报文段

UDP 的特点:

  • 无连接:发送数据前不建立连接,接收方随时可以处理数据。
  • 不可靠:不保证数据到达,不保证数据顺序,也不提供重传机制。
  • 基于报文:数据以独立的 UDP 报文(Datagram) 发送,每个报文是完整的,没有流的概念。
  • 低开销:UDP 头部仅 8 字节,较 TCP(20 字节)开销小,适用于低延迟场景。
  • 适合实时传输:UDP 适用于 音视频、在线游戏、DNS 查询 等场景,即使部分数据丢失,也不会影响整体体验。

UDP 传输过程:

  1. 发送端直接将数据封装成 UDP 数据报,通过 IP 层发送给目标主机。
  2. 接收端从 UDP 缓冲区获取数据报,并交给应用层处理(但可能丢包、乱序)。

TCP 和 UDP 对比

对比项 TCP(传输控制协议) UDP(用户数据报协议)
是否连接 面向连接,需建立连接(3 次握手) 无连接,直接发送数据
可靠性 可靠传输,保证数据不丢失、不重复、按序到达 不可靠传输,可能丢包、乱序、重复
数据边界 面向字节流,无明确的消息边界,可能粘包 面向数据报,有独立的数据包边界
传输效率 开销较大,需要维护连接和状态 轻量级,无需连接管理,低延迟
流量控制 通过滑动窗口调整传输速率 无流量控制,可能导致接收方过载
拥塞控制 通过 AIMD 算法防止网络拥塞 无拥塞控制,可能造成网络拥塞
应用场景 需要高可靠性,如 HTTP、FTP、数据库 需要低延迟,如视频流、DNS、VoIP

TCP 是用来解决什么问题的?

TCP 主要用于解决以下问题:

  1. 可靠传输:确保数据能够按序、完整地传输,避免数据丢失、重复或乱序。
  2. 流量控制:通过滑动窗口机制,控制发送方的发送速率,避免接收方缓冲区溢出。
  3. 拥塞控制:通过拥塞窗口和算法(如慢启动、拥塞避免),避免网络拥塞。
  4. 连接管理:通过三次握手和四次挥手,确保连接的建立和释放是可靠的。

典型应用场景

  • 文件传输(如 FTP)
  • 网页浏览(如 HTTP/HTTPS)
  • 电子邮件(如 SMTP)
  • 数据库访问

UDP 是用来解决什么问题的?

UDP 主要用于解决以下问题:

  1. 低延迟传输:无需建立连接,直接发送数据,适合对实时性要求高的场景。
  2. 简单高效:头部开销小,传输效率高,适合轻量级通信。
  3. 广播和多播:支持一对多通信,适合广播和多播场景。

典型应用场景

  • 实时音视频传输(如 VoIP、视频会议)
  • 在线游戏
  • DNS 查询
  • 广播和多播应用

TCP 和 UDP 分别对应的常见应用层协议有哪些?

协议类型 常见应用层协议
TCP HTTP/HTTPS(网页浏览)、FTP(文件传输)、SMTP(电子邮件)、SSH(远程登录)、Telnet
UDP DNS(域名解析)、DHCP(动态主机配置)、SNMP(网络管理)、TFTP(简单文件传输)

为什么要 TCP,IP 层实现控制不行吗?

首先需要明白TCP,UDP 和 IP 协议之前的区别:

  • TCP 适合可靠传输,提供流量控制、拥塞控制,保证数据有序。
  • UDP 适合实时传输,低延迟、高吞吐,但可能丢包、乱序。
  • IP 层只是尽力传输,TCP 是在其基础上提供可靠性。

IP 层(互联网协议)只负责无连接、尽力而为(Best-effort)的数据传输,但它本身存在以下问题:

  1. 不可靠:IP 数据报可能丢失、乱序、重复,应用层需要额外处理。
  2. 无流量控制:IP 层不会限制发送速率,可能导致接收方过载。
  3. 无拥塞控制:IP 层不会检测网络拥塞,可能导致全网性能下降。

TCP 之所以存在,是为了弥补 IP 层的不足,提供可靠、稳定的传输。
如果没有 TCP,应用层需要自己处理丢包、重传、乱序等复杂问题,大大增加了开发难度。


TCP 连接建立与断开

TCP 三次握手(Three-Way Handshake)

TCP 是面向连接的协议,在数据传输前,通信双方需要通过 三次握手(Three-Way Handshake) 建立连接,确保双方都具备发送和接收数据的能力。

![用“三次握手”建立 TCP 连接](TCP 报文段

三次握手的过程

假设 客户端(Client) 要与 服务器(Server) 建立 TCP 连接,三次握手的步骤如下:

  1. SYN = 1,seq = x。
  2. SYN = 1,ACK = 1,seq = y,ack = x + 1。
  3. ACK = 1,seq = x + 1,ack = y + 1。

1️⃣ 第一次握手(Client → Server,发送 SYN)

  • 客户端发送一个 SYN(同步) 报文,请求建立连接,并携带一个 初始序列号 ISN(Initial Sequence Number)
  • SYN=1, seq=x
  • 此时,客户端进入 SYN-SENT 状态。

2️⃣ 第二次握手(Server → Client,发送 SYN-ACK)

  • 服务器收到 SYN 请求后,回应一个 SYN-ACK 报文,表示同意连接,并指定自己的初始序列号 ISN(y)。
  • SYN=1, ACK=1, seq=y, ack=x+1
  • 此时,服务器进入 SYN-RECEIVED 状态。

3️⃣ 第三次握手(Client → Server,发送 ACK)

  • 客户端收到 SYN-ACK 后,回复一个 ACK 报文,确认连接建立,并表明自己可以发送数据了。
  • ACK=1, seq=x+1, ack=y+1
  • 客户端进入 ESTABLISHED(已建立连接) 状态。
  • 服务器收到 ACK 后,也进入 ESTABLISHED 状态,连接正式建立。

📌 完成三次握手后,双方可以正式开始数据传输。


TCP 四次挥手(Four-Way Handshake)

TCP 连接关闭时,需要四次挥手(Four-Way Handshake) 来保证数据完全传输,并确保双方都同意断开连接。
![用“四次挥手”释放 TCP 连接](TCP 报文段

四次挥手的过程

假设 客户端(Client) 先发起关闭连接的请求,四次挥手的步骤如下:

  1. FIN = 1,seq = u。
  2. ACK = 1,seq = v,ack = u + 1。
  3. FIN = 1,ACK = 1,seq = w,ack = u + 1。
  4. ACK = 1,seq = u + 1,ack = w + 1。

1️⃣ 第一次挥手(Client → Server,发送 FIN)

  • 客户端发送 FIN(Finish) 报文,表示不再发送数据,但仍可以接收数据。
  • FIN=1, seq=u
  • 客户端进入 FIN-WAIT-1 状态。

2️⃣ 第二次挥手(Server → Client,发送 ACK)

  • 服务器收到 FIN 后,发送一个 ACK 确认。
  • ACK=1, seq=v, ack=u+1
  • 服务器进入 CLOSE-WAIT 状态,客户端进入 FIN-WAIT-2 状态。
  • 服务器仍然可能需要处理未完成的任务,因此连接暂时不会关闭

3️⃣ 第三次挥手(Server → Client,发送 FIN)

  • 服务器处理完数据后,向客户端发送 FIN 报文,表示自己也不再发送数据
  • FIN=1, seq=w, ack=u+1
  • 服务器进入 LAST-ACK 状态。

4️⃣ 第四次挥手(Client → Server,发送 ACK)

  • 客户端收到 FIN 后,回复一个 ACK 报文,表示确认断开
  • ACK=1, seq=u+1, ack=w+1
  • 客户端进入 TIME-WAIT 状态,等待 2MSL(最大报文生存时间) 后,才真正关闭。
  • 服务器收到 ACK 后,立即进入 CLOSED(关闭) 状态,连接完全关闭。

📌 为什么服务器的 ACK 和 FIN 不能合并?

  • 服务器在接收到 FIN 后,可能仍然有未发送的数据,所以需要 先发送 ACK,处理完数据后,再发送 FIN

TCP 初始序列号 ISN

ISN 的定义

  • ISN(Initial Sequence Number) 是 TCP 连接建立时,每个通信方选定的起始序列号,用于数据传输中的字节编号

    如在 TCP 三次握手 过程中:

    • 客户端 发送 SYN 请求,并携带自己的 ISN(x)
    • 服务器 回复 SYN-ACK,并携带自己的 ISN(y)

ISN 的作用

  1. 解决 TCP 可靠传输中的数据编号问题TCP 以字节为单位 进行数据传输,每个字节都需要一个序列号。ISN 作为初始编号,确保每个 TCP 段都有唯一的序列号,方便数据接收端按序重组数据
  2. 防止 TCP 连接中的数据包混淆:TCP 连接断开后,可能仍有旧的 TCP 报文在网络中滞留。TCP 采用动态 ISN 生成,让每次连接的 ISN 随机变化,防止旧连接数据包干扰新连接。

ISN 的取值

  • TCP 初始序列号 ISN(Initial Sequence Number) 并不是固定的,而是动态生成的。

ISN 的生成方式

  • 传统方法:每次连接 ISN 可能固定为 0,但这样容易被攻击者预测,造成数据包劫持
  • 现代方法:ISN 通常使用时间戳加随机数,防止连接劫持:
    ISN = 当前时间戳 + 随机增量
    
  • 现代操作系统(如 Linux、Windows)采用基于时间的 ISN 生成算法,让 ISN 随着时间增加,确保安全性

TCP 三次握手时,发送 SYN 之后就宕机了会怎么样?

假设客户端发送 SYN 之后,宕机了:

  • 服务器收到了 SYN,并回复 SYN-ACK,但客户端已经宕机,无法回复 ACK
  • 服务器会一直等待 ACK,但由于没有响应,会重传 SYN-ACK 若干次(通常是 3-6 次)。
  • 最后,服务器会超时,进入 CLOSED 状态,释放资源。

注意:

  • 每次 SYN-ACK 重传的间隔不是固定的,而是 指数退避(Exponential Backoff) 的策略(1s → 2s → 4s → 8s → 16s → 32s),每次重传的间隔会逐渐增加。

📌 影响:

  • 服务器的资源(如连接队列)会被占用,可能导致 SYN Flood 攻击。

SYN Flood 攻击

SYN Flood(SYN 泛洪攻击) 是一种 DoS(拒绝服务攻击),利用 TCP 三次握手的机制,导致服务器资源耗尽。

攻击原理

  • 攻击者伪造大量 SYN 请求,但不发送 ACK,导致服务器一直等待(SYN-RECEIVED 状态),耗尽服务器的资源(如半连接队列),无法处理新的请求,造成拒绝服务(DoS)。

防御措施

1. 增加半连接队列大小

  • 原理:通过增大服务器的半连接队列(SYN 队列)容量,可以暂时缓解大量 SYN 报文导致的队列溢出问题。
  • 实现
    • 调整操作系统的 TCP 参数,例如 net.ipv4.tcp_max_syn_backlog(Linux 系统)。
    • 增大服务器的内存资源,以支持更大的队列。

2. 减少 SYN+ACK 重试次数

  • 原理:当服务器发送 SYN+ACK 后未收到客户端的 ACK 时,会进行多次重试。减少重试次数可以更快地释放半连接资源。
  • 实现
    • 调整操作系统的 TCP 参数,例如 net.ipv4.tcp_synack_retries(默认是5,Linux 系统)。

3. 启用 SYN Cookie 机制

  • 原理:SYN Cookie 是一种防御 SYN Flood 攻击的技术。服务器在收到 SYN 报文后,不立即分配资源,而是通过加密算法生成一个 Cookie 值作为初始序列号。只有收到合法的 ACK 报文后,服务器才会分配资源
  • 实现
    • 在 Linux 系统中,启用 SYN Cookie:sysctl -w net.ipv4.tcp_syncookies=1
    • 思想是:服务器不再维护 SYN 半连接队列,而是基于客户端的 SYN 报文计算出一个“Cookie”(即特定格式的 ISN,初始序列号),并在 SYN+ACK 报文中返回给客户端。客户端在第三次握手(ACK 报文)中返回这个 Cookie,服务器通过计算验证它的正确性,再正式建立连接。
  • 优点
    • 无需维护半连接队列,节省资源。
    • 有效抵御伪造源 IP 的 SYN Flood 攻击。

半连接状态分配的典型资源有哪些?

  • 内存资源:服务器需在内存中开辟空间记录半连接相关信息,如客户端 IP 地址、端口号、连接请求时间、序列号等。
  • 连接队列资源:服务器设置了专门的半连接队列(SYN 队列)。

三次握手过程中可以携带数据吗?

简短回答理论上可以,但通常不携带,实际应用中只有第三次握手可能携带数据。


三次握手过程分析

在 TCP 三次握手的过程中,只有第三次握手才能发送数据:

  • 普通 TCP

    客户端 -> 服务器: SYN
    服务器 -> 客户端: SYN+ACK
    客户端 -> 服务器: ACK
    客户端 -> 服务器: 数据包
    
    • 需要 额外一次 RTT 之后才能发送数据。
  • 第三次握手携带数据

    客户端 -> 服务器: SYN
    服务器 -> 客户端: SYN+ACK
    客户端 -> 服务器: ACK + 数据包
    
    • 直接在第三次握手的 ACK 报文中携带数据,减少一次 RTT

为什么第一次、第二次握手不能携带数据?

  • TCP 规定 SYN 报文 不能携带数据,因为此时连接还未建立,无法确认对方是否能正确接收数据
  • 服务器收到 SYN 后,需要分配资源,并在 SYN + ACK 里回复自己的初始序列号,因此 不能提前接收数据

为什么第三次握手可以携带数据?

  • 连接已经建立(服务器收到 ACK 之后,连接状态变为 ESTABLISHED)。
  • 服务器已经具备接收能力,理论上可以直接处理数据。
  • 减少一次 RTT(往返时延),提高传输效率。

特殊情况:TCP Fast Open(TFO)

TCP Fast Open(TFO) 是 TCP 的一个优化,它允许 在第一步 SYN 报文中携带数据,但前提是:

  • 客户端和服务器都支持 TFO
  • 客户端之前已经和服务器通信过,并缓存了 TFO Cookie
  • 服务器只有在确认 TFO Cookie 合法后,才会处理数据
  • SYN 携带的数据在服务器接收到 SYN+ACK 之后才能被处理

TFO 的工作流程

  1. 第一次握手(SYN)

    • 客户端发送 SYN 报文,并在报文中携带一个特殊的 TFO Cookie(由服务器在之前的连接中生成)。
    • 客户端可以在 SYN 报文中携带数据(例如 HTTP 请求)。
  2. 第二次握手(SYN+ACK)

    • 服务器验证 TFO Cookie 的合法性。
    • 如果 Cookie 有效,服务器可以在 SYN+ACK 报文中携带响应数据。
  3. 第三次握手(ACK)

    • 客户端发送 ACK 报文,确认服务器的响应。
    • 连接正式建立。

除了四次挥手,还有什么方法断开连接?

  1. RST(Reset)强制断开

    • 直接发送 RST 报文,立即终止连接,不等待对方确认。
    • 适用于异常情况下的连接终止(如端口错误、程序崩溃)。
  2. 超时自动关闭

    • 如果长时间没有数据传输,连接可能被 超时机制(Keep-Alive 机制) 自动关闭。可以命令设置 TCP Keepalive 参数:

      sysctl -w net.ipv4.tcp_keepalive_time=600 # 600 秒后开始检测
      sysctl -w net.ipv4.tcp_keepalive_intvl=75 # 每次检测间隔 75 秒
      sysctl -w net.ipv4.tcp_keepalive_probes=9 # 最多检测 9 次


TCP 挥手的 TIME_WAIT 状态

简短回答:
TIME_WAIT 状态的存在是为了 确保对方正确关闭连接避免旧连接影响新连接。TCP 在主动关闭连接的一方需要等待 2MSL(最大报文生存时间的两倍) 后才能彻底释放连接资源,防止未收到的 FIN 报文或延迟的旧数据干扰新连接。

1. 什么是 TIME_WAIT 状态?

  • TIME_WAIT 是 TCP 连接断开过程中的一个状态,出现在主动关闭方(即先发送 FIN 报文的一方)。
  • 当主动关闭方发送 FIN 报文并接收到对方的 FIN + ACK 后,会进入 TIME_WAIT 状态。
  • TIME_WAIT 状态会持续 2MSL(Maximum Segment Lifetime,最大报文生存时间的两倍),之后连接才会彻底关闭。

2. 为什么需要 TIME_WAIT 状态?

1. 确保对方正确关闭连接(可靠性保证)
  • 在四次挥手中,主动关闭方发送最后一个 ACK 报文后,需要等待一段时间,以确保对方能够收到这个 ACK
  • 如果 ACK 丢失,对方会重传 FIN 报文,主动关闭方可以重新发送 ACK

📌 示例:如果 TIME_WAIT 过早释放

1.  客户端 -> 服务器: FIN
2.  服务器 -> 客户端: ACK
3.  服务器 -> 客户端: FIN (ACK 丢失)
4.  客户端已关闭(但服务器仍在等待 ACK)
5.  服务器重发 FIN,客户端已经不在,服务器永远等待超时

🔹 TIME_WAIT 让客户端有机会重新发送 ACK,防止这种情况发生!


2. 避免旧连接数据影响新连接(延迟数据问题)
  • 在网络中,可能存在延迟的报文(即旧连接的报文)。
  • 如果立即关闭连接,这些延迟报文可能会被误认为是新连接的报文,导致数据混乱。
  • TIME_WAIT 状态确保旧连接的报文在网络中完全消失,避免干扰新连接。

📌 示例:如果 TIME_WAIT 过早释放

1.  旧连接(A)正在传输数据,但关闭过早,没有进入 TIME_WAIT。
2.  旧连接(A)上的某个数据包在网络中延迟到达。
3.  服务器开启新连接(B),端口号与旧连接相同。
4.  旧数据包误入新连接(B),导致数据混乱!

🔹 TIME_WAIT 确保旧连接的残余数据在 2MSL 期间自然消亡,避免影响新连接!


3. TIME_WAIT 的持续时间—— 2MSL

  • MSL(Maximum Segment Lifetime):报文在网络中的最大生存时间,通常为 30 秒到 2 分钟。
  • 2MSL:确保报文在网络中完全消失的时间。

在 Linux 系统中,TIME_WAIT 的默认持续时间是 60 秒(即 2MSL=60 秒)。


4. TIME_WAIT 导致的问题及其解决方案

高并发服务器(如 Nginx 代理、大量短连接应用)中,TIME_WAIT 可能导致 大量端口占用,系统资源耗尽

在高并发场景下,可以通过以下方式优化 TIME_WAIT 状态:

(1)启用 TCP 重用(SO_REUSEADDR 和 SO_REUSEPORT)
  • 允许重用处于 TIME_WAIT 状态的端口
  • 在 Linux 系统中,可以通过以下代码启用:
    int opt = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
    
(2)调整 TCP 参数
  • 减少 TIME_WAIT 状态的持续时间或者 TIME_WAIT 连接的最大数量,超出后直接关闭最早的连接
    sysctl -w net.ipv4.tcp_tw_reuse=1      # 允许重用 TIME_WAIT 状态的连接
    sysctl -w net.ipv4.tcp_tw_recycle=1    # 快速回收 TIME_WAIT 状态的连接(不推荐)
    sysctl -w net.ipv4.tcp_fin_timeout=10  # 减少 FIN_WAIT_2 状态的超时时间
    
(3)使用长连接
  • 减少短连接的频繁创建和关闭,从而减少 TIME_WAIT 状态的数量。