13TCP/IP详解(卷1:协议)

UDP

用户数据报协议

UDP数据报无连接

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第13章:UDP(User Datagram Protocol)

用户数据报协议

一、导读

1.1 本章学习目标

第13章讲解UDP(用户数据报协议)——TCP/IP协议族中与TCP并列的另一个传输层协议。UDP提供简单的、无连接的数据报服务,不建立连接、不保证可靠交付、不进行流量控制和拥塞控制。UDP的设计哲学是最小化:只提供最基本的功能(端口复用和差错检测),将复杂性留给应用层。

本章的核心学习目标包括:

  • 掌握UDP报文段的格式:极其简单的8字节头部
  • 理解UDP与TCP的核心区别:无连接vs面向连接、不可靠vs可靠
  • 了解UDP的适用场景:DNS、DHCP、视频流、在线游戏、VoIP
  • 理解UDP校验和的计算方法:覆盖伪头部、UDP头部和数据
  • 掌握UDP套接字编程:与TCP套接字的关键区别
  • 了解基于UDP的可靠传输协议:QUIC、KCP等

1.2 为什么本章如此重要

虽然TCP是Internet上使用最广泛的传输层协议,但UDP在很多关键场景中不可替代:

  • DNS:域名查询使用UDP,因为查询报文很小,UDP的开销更低
  • DHCP:主机在获取IP地址之前无法使用TCP
  • 实时应用:视频通话、在线游戏、VoIP对延迟敏感,可以容忍丢包但不能容忍重传延迟
  • 简单请求-响应:NTP、SNMP等协议使用UDP,因为交互简单
  • 现代协议基础:Google的QUIC协议在UDP之上实现了可靠传输和加密,是HTTP/3的基础

理解UDP的优缺点,有助于你在网络编程中做出正确的协议选择。

1.3 核心问题引导

在阅读本章时,请带着以下问题思考:

UDP头部只有8字节,它提供了哪些功能?省略了哪些功能?

UDP的校验和在IPv4中是可选的,为什么?在IPv6中为什么变成强制的?

什么场景下UDP比TCP更合适?判断标准是什么?

如果应用层需要可靠传输,为什么不直接用TCP,而是在UDP上自己实现?

QUIC协议为什么选择在UDP之上构建,而不是直接修改TCP?

UDP有没有流量控制?如果发送方发送过快,会发生什么?


二、核心概念详解

2.1 UDP的特点

特性UDPTCP
连接无连接面向连接
可靠性不可靠可靠
有序性不保证顺序保证顺序
流量控制无有
拥塞控制无有
头部开销8字节20-60字节
传输方式数据报字节流
连接数一对多/一对一一对一

UDP的设计哲学:尽可能少做事。它只做两件事:

通过端口号区分不同的应用进程

通过校验和检测数据错误

其他所有功能(可靠传输、流量控制、拥塞控制、连接管理)都由应用层自行实现(如果需要的话)。

2.2 UDP报文段格式

UDP头部极其简单,只有4个字段,共8字节:

0      7 8     15 16    23 24    31
+--------+--------+--------+--------+
|     Source Port      |   Dest Port    |
+--------+--------+--------+--------+
|     Length           |   Checksum     |
+--------+--------+--------+--------+
|          Data (variable)             |
+-------------------------------------+
字段长度说明
Source Port16位源端口号(可选,可以为0)
Destination Port16位目的端口号
Length16位UDP报文段总长度(头部+数据),最小为8
Checksum16位校验和(IPv4可选,IPv6强制)

2.2.1 UDP校验和

UDP校验和覆盖三个部分:

伪头部(12字节):源IP、目的IP、零、协议号(17)、UDP长度

UDP头部(8字节)

UDP数据

伪头部的目的是让UDP校验和能够验证数据报确实到达了正确的目的主机(通过检查IP地址)。

伪头部:
+--------+--------+--------+--------+
|           Source IP Address         |
+--------+--------+--------+--------+
|        Destination IP Address       |
+--------+--------+--------+--------+
|  Zero  |Protocol|    UDP Length     |
+--------+--------+--------+--------+

如果校验和计算结果为0,在IPv4中表示"未计算校验和"(因为0的补码仍然是0,无法区分"校验和为0"和"未计算")。因此,如果计算结果确实为0,使用全1(0xFFFF)代替。

2.3 UDP与TCP的对比

2.3.1 何时选择UDP

选择UDP的典型场景:

场景原因示例
简单请求-响应不需要连接建立的开销DNS、SNMP、NTP
实时应用不能容忍重传延迟VoIP、视频会议、在线游戏
广播/多播TCP不支持一对多DHCP、服务发现
隧道协议需要封装其他协议VPN、隧道
自定义可靠协议需要自定义的可靠传输策略QUIC、KCP

2.3.2 UDP的"不可靠"意味着什么

  • 不保证交付:数据报可能丢失(网络拥塞、缓冲区满)
  • 不保证顺序:数据报可能乱序到达
  • 不保证不重复:数据报可能重复到达
  • 没有流量控制:发送方可能淹没接收方
  • 没有拥塞控制:可能导致网络拥塞加剧

应用层如果需要这些保证,必须自己实现。

2.4 UDP套接字编程

UDP套接字与TCP套接字的关键区别:

c
// TCP客户端
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
connect(sockfd, ...);      // 建立连接
write(sockfd, data, len);  // 发送数据
read(sockfd, buf, len);    // 接收数据
close(sockfd);             // 关闭连接

// UDP客户端
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 不需要connect
sendto(sockfd, data, len, 0, (struct sockaddr *)&dest, sizeof(dest));
recvfrom(sockfd, buf, len, 0, (struct sockaddr *)&src, &srclen);
close(sockfd);

关键区别:

  • UDP使用 SOCK_DGRAM(数据报套接字),TCP使用 SOCK_STREAM
  • UDP不需要 connect(但可以使用 connect 来简化后续操作)
  • UDP使用 sendto/recvfrom(需要指定/获取地址),TCP使用 write/read
  • UDP是消息边界的,每次 sendto 对应一次 recvfrom

2.4.1 UDP的connect

虽然UDP是无连接的,但可以调用 connect 来指定对端地址。之后可以使用 write/read 代替 sendto/recvfrom,并且内核只接收来自该对端的数据报。

c
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
connect(sockfd, (struct sockaddr *)&dest, sizeof(dest));
// 之后可以使用write/read
write(sockfd, data, len);
read(sockfd, buf, len);

2.5 UDP的常见应用

2.5.1 DNS

DNS查询通常使用UDP(端口53),因为:

  • 查询和响应报文通常很小(<512字节)
  • UDP的开销比TCP小(不需要三次握手)
  • 如果响应超过512字节,DNS会回退到TCP

2.5.2 DHCP

DHCP使用UDP(客户端68,服务器67),因为:

  • 客户端在获取IP地址之前无法使用TCP
  • DHCP报文需要广播,UDP支持广播

2.5.3 实时多媒体

VoIP、视频会议、在线游戏使用UDP,因为:

  • 实时性比可靠性更重要
  • 旧的数据重传没有意义(已经过时)
  • 应用层可以处理丢包(如插值、前向纠错)

2.5.4 NFS

NFS(Network File System)早期版本使用UDP,因为:

  • 简单、低延迟
  • 应用层实现可靠传输(针对文件操作)
  • 现代NFS(v4+)使用TCP

2.6 基于UDP的可靠传输

很多现代协议选择在UDP之上构建可靠传输,而不是直接使用TCP:

2.6.1 QUIC(HTTP/3)

Google设计的QUIC协议在UDP之上实现了:

  • 可靠传输(类似TCP)
  • 多路复用(无队头阻塞)
  • 加密(内置TLS 1.3)
  • 快速连接建立(0-RTT或1-RTT)
  • 连接迁移(使用Connection ID而非四元组)

QUIC被标准化为HTTP/3的传输协议,是Web的未来方向。

2.6.2 KCP

KCP是一个基于UDP的自定义可靠传输协议,特点是:

  • 比TCP更快的重传策略
  • 可选择SACK(选择性确认)
  • 非退让流控(适合游戏等场景)

2.6.3 为什么不在TCP上实现这些功能

TCP是操作系统内核的一部分,修改和升级非常困难。在UDP上构建新协议,可以:

  • 快速迭代和部署
  • 不受操作系统限制
  • 灵活定制传输策略

三、实战案例

3.1 案例一:使用tcpdump观察UDP报文

bash
sudo tcpdump -i eth0 -nn 'udp port 53'

输出示例(DNS查询):

12:34:56.789 IP 192.168.1.100.50000 > 8.8.8.8.53:
  12345 [1au] A? www.example.com. (34)

12:34:56.815 IP 8.8.8.8.53 > 192.168.1.100.50000:
  12345 1/0/0 A 93.184.216.34 (50)

分析:

  • UDP头部只有源端口、目的端口、长度、校验和
  • 与TCP相比,没有序号、确认号、窗口大小等字段
  • 每个UDP报文段是独立的,没有连接概念

3.2 案例二:编写UDP回声服务器

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 65535

int main() {
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(9999);
    bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    
    char buffer[BUF_SIZE];
    struct sockaddr_in cli_addr;
    socklen_t cli_len = sizeof(cli_addr);
    
    while (1) {
        int n = recvfrom(sockfd, buffer, BUF_SIZE, 0,
                         (struct sockaddr *)&cli_addr, &cli_len);
        if (n > 0) {
            buffer[n] = '\0';
            printf("Received %d bytes from %s:%d: %s\n",
                   n, inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buffer);
            sendto(sockfd, buffer, n, 0, (struct sockaddr *)&cli_addr, cli_len);
        }
    }
    close(sockfd);
    return 0;
}

3.3 案例三:测试UDP的不可靠性

bash
# 发送大量UDP数据报,观察是否有丢失
# 发送端
for i in $(seq 1 1000); do
    echo "packet $i" | nc -u -w0 192.168.1.200 9999
done

# 接收端统计收到的报文数
# 如果少于1000,说明有丢包

四、常见误区与难点

4.1 误区一:"UDP不可靠所以不如TCP"

UDP和TCP各有适用场景。UDP的"不可靠"在某些场景中反而是优势——没有重传延迟、没有拥塞控制的限速、没有连接建立的开销。

4.2 误区二:"UDP没有校验和"

UDP有校验和(IPv4可选,IPv6强制),只是不提供可靠传输保证。校验和用于检测数据传输过程中的错误,与可靠传输是两个不同的概念。

4.3 难点:理解为什么QUIC选择UDP而不是TCP

TCP是操作系统内核的一部分,升级困难(需要更新所有操作系统)。UDP是用户空间协议的基础,可以在应用层实现任何自定义逻辑。这是QUIC能够快速部署的关键原因。


五、本章小结

第13章讲解了UDP协议:

UDP头部只有8字节:源端口、目的端口、长度、校验和。

UDP是无连接的、不可靠的:不建立连接、不保证交付、不保证顺序、没有流量控制和拥塞控制。

UDP适用于简单请求-响应、实时应用、广播/多播、隧道协议等场景。

UDP校验和覆盖伪头部、UDP头部和数据,IPv4可选,IPv6强制。

现代协议(QUIC、KCP)在UDP之上构建可靠传输,避免修改TCP内核实现的限制。


六、思考与练习

思考题

如果让你设计一个基于UDP的可靠传输协议,你会如何实现可靠传输?与TCP相比,你会做哪些不同的设计?

UDP的源端口号是可选的(可以为0)。如果源端口为0,接收方如何回复?

为什么DNS主要使用UDP而不是TCP?如果DNS完全迁移到TCP(或QUIC),会有什么影响?

QUIC的多路复用解决了TCP的什么问题?它是如何实现的?

练习题

编写一个UDP客户端和服务器,使用tcpdump观察报文交互过程。

测试UDP在不同丢包率下的传输性能,分析UDP的局限性。

研究QUIC协议的规范(RFC 9000),分析它如何在UDP之上实现可靠传输和加密。

13/13UDP
0%