文章目录
-
- UDP协议详解:无连接、不可靠但高效的传输协议
- 一、传输层的作用
-
- [1.1 网络分层回顾](#1.1 网络分层回顾)
- [1.2 传输层的核心概念:端口号](#1.2 传输层的核心概念:端口号)
- [1.3 五元组:唯一标识一条通信](#1.3 五元组:唯一标识一条通信)
- [1.4 端口号的范围划分](#1.4 端口号的范围划分)
- [1.5 端口号的两个问题](#1.5 端口号的两个问题)
- 二、UDP协议概述
- 三、UDP缓冲区机制
-
- [3.1 UDP没有真正的发送缓冲区](#3.1 UDP没有真正的发送缓冲区)
- [3.2 UDP有接收缓冲区](#3.2 UDP有接收缓冲区)
- [3.3 UDP的全双工特性](#3.3 UDP的全双工特性)
- 四、UDP的使用注意事项
-
- [4.1 UDP报文大小限制](#4.1 UDP报文大小限制)
- [4.2 UDP的校验和](#4.2 UDP的校验和)
- [4.3 UDP的应用层协议设计](#4.3 UDP的应用层协议设计)
- 五、基于UDP的应用层协议
-
- [5.1 为什么选择UDP](#5.1 为什么选择UDP)
- [5.2 常见的UDP应用](#5.2 常见的UDP应用)
-
- [DNS(Domain Name System)](#DNS(Domain Name System))
- [DHCP(Dynamic Host Configuration Protocol)](#DHCP(Dynamic Host Configuration Protocol))
- [NTP(Network Time Protocol)](#NTP(Network Time Protocol))
- 视频直播、在线游戏
- [TFTP(Trivial File Transfer Protocol)](#TFTP(Trivial File Transfer Protocol))
- [5.3 自定义UDP应用层协议](#5.3 自定义UDP应用层协议)
- [六、UDP vs TCP快速对比](#六、UDP vs TCP快速对比)
-
- [6.1 对比表](#6.1 对比表)
- [6.2 选择标准](#6.2 选择标准)
- 七、本篇总结
-
- [7.1 核心要点](#7.1 核心要点)
- [7.2 容易混淆的点](#7.2 容易混淆的点)
UDP协议详解:无连接、不可靠但高效的传输协议
💬 开篇:前面我们学习了HTTP、HTTPS等应用层协议,这些协议都运行在传输层之上。传输层负责把数据从"本机某个进程"送到"对端某个进程"(靠端口号复用/分用)。传输层有两个主要协议:TCP和UDP。TCP复杂但可靠,UDP简单但高效。这一篇先从UDP讲起,理解UDP的设计哲学------放弃可靠性换取高效率。掌握UDP,你就理解了为什么有些应用选择UDP而不是TCP,比如视频直播、在线游戏、DNS查询。
👍 点赞、收藏与分享:这篇会把UDP的原理讲透,包括报文格式、特点、缓冲区机制、应用场景。如果对你有帮助,请点赞收藏!
🚀 循序渐进:从传输层的概念讲起,到端口号,到UDP报文格式,到UDP的特点,到缓冲区机制,到应用场景,一步步理解UDP为什么这样设计。
一、传输层的作用
1.1 网络分层回顾
还记得我们之前讲过的网络分层吗?
bash
应用层:HTTP、HTTPS、FTP、SSH、DNS、SMTP...
传输层:TCP、UDP
网络层:IP
数据链路层:以太网、WiFi
物理层:光纤、电缆
传输层的位置:介于应用层和网络层之间。
传输层的职责:
- 应用层的数据如何传输到对方的应用层
- 不关心数据怎么在网络上跑(那是网络层的事)
- 只关心数据能否可靠地到达对方
1.2 传输层的核心概念:端口号
问题:一台主机上可能运行多个应用程序,比如:
- 浏览器(HTTP)
- 邮件客户端(SMTP)
- SSH客户端
- 游戏
如何区分这些应用程序的数据?
答案:端口号(Port)。
端口号的作用:标识一个主机上进行通信的不同应用程序。
类比:
bash
IP地址 = 城市地址(找到哪个城市)
端口号 = 房间号(找到城市里的哪个房间)
1.3 五元组:唯一标识一条通信
在TCP/IP协议中,一条通信由五个要素唯一标识:
bash
(源IP, 源端口, 目的IP, 目的端口, 协议号)
例子:
bash
(192.168.1.100, 54321, 8.8.8.8, 53, UDP)
这表示:
- 从192.168.1.100的54321端口
- 到8.8.8.8的53端口
- 使用UDP协议
查看本机的通信五元组:
bash
netstat -n
输出示例:
bash
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 192.168.1.100:54321 8.8.8.8:443 ESTABLISHED
udp 0 0 192.168.1.100:53214 8.8.8.8:53
1.4 端口号的范围划分
端口号范围:0 - 65535(16位无符号整数)
分类:
| 范围 | 名称 | 用途 |
|---|---|---|
| 0-1023 | 知名端口(Well-Known Port) | 系统保留,常用服务 |
| 1024-65535 | 动态端口(Dynamic Port) | 客户端程序、临时端口 |
常见知名端口:
| 服务 | 端口 | 协议 |
|---|---|---|
| SSH | 22 | TCP |
| Telnet | 23 | TCP |
| FTP | 21 | TCP |
| HTTP | 80 | TCP |
| HTTPS | 443 | TCP |
| DNS | 53 | UDP/TCP |
| SMTP | 25 | TCP |
| POP3 | 110 | TCP |
| MySQL | 3306 | TCP |
| Redis | 6379 | TCP |
查看系统的知名端口:
bash
cat /etc/services
重要:自己写程序时,要避开这些知名端口。
1.5 端口号的两个问题
问题1:一个进程能bind多个端口吗?
答案:可以。
例子:
cpp
// 一个服务器程序同时监听多个端口
TcpServer server1("0.0.0.0", 8080); // 监听8080
TcpServer server2("0.0.0.0", 8081); // 监听8081
TcpServer server3("0.0.0.0", 8082); // 监听8082
实际应用:
- Nginx同时监听80和443端口
- MySQL主从复制,主库监听3306,从库也监听3306
问题2:一个端口能被多个进程bind吗?
答案:不能(默认情况下)。
例子:
bash
# 进程A监听8080
./server 8080
# 进程B尝试监听8080
./server 8080
# 错误:Address already in use
例外:某些情况下可以通过 socket 选项实现端口重用。
SO_REUSEADDR 常用于"端口快速重启复用"(避免 TIME_WAIT 等导致的占用)
Linux 下如果要"多个进程同时监听同一端口并做负载分担",通常用 SO_REUSEPORT
二、UDP协议概述
2.1 UDP是什么
UDP(User Datagram Protocol):用户数据报协议。
设计哲学:简单、快速、高效。
代价:不可靠。
类比:
bash
TCP = 挂号信(可靠,但慢)
UDP = 明信片(快速,但可能丢失)
2.2 UDP报文格式
UDP报文结构:
bash
┌─────────────────────────────────────────┐
│ UDP首部(8字节) │
├─────────────────────────────────────────┤
│ 源端口号(16位) │ 目的端口号(16位) │
├─────────────────────────────────────────┤
│ 长度(16位) │ 校验和(16位) │
├─────────────────────────────────────────┤
│ UDP数据(可变长度) │
└─────────────────────────────────────────┘
字段说明:
- 源端口号(16位):发送方的端口
- 目的端口号(16位):接收方的端口
- 长度(16位):整个UDP报文的长度(首部+数据),最小8字节,最大65535字节
- 校验和(16位):用于差错检测(UDP不是 CRC,而是 16 位一补和),计算时还会包含 IP 的"伪首部",用于尽早发现报文在传输中被破坏或投递错误。
- 数据:应用层的数据
重要:UDP首部只有8字节,非常简洁。
2.3 UDP的特点
特点1:无连接
含义:不需要建立连接就可以直接发送数据。
对比TCP:
bash
TCP:
1. connect(建立连接)
2. send/recv(传输数据)
3. close(关闭连接)
UDP:
1. sendto(直接发送)
优点:
- 速度快(省去握手过程)
- 开销小(不需要维护连接状态)
缺点:
- 无法确认对方是否存在
- 无法确认对方是否收到
特点2:不可靠
含义:不保证数据一定能到达对方。
可能发生的情况:
- 数据丢失(网络拥塞、路由器故障)
- 数据损坏(传输过程中出错)
- 数据乱序(不同路径到达)
UDP的态度:
bash
"我尽力了,但不保证。"
对比TCP:
bash
TCP:
- 数据丢失 → 重传
- 数据损坏 → 丢弃并重传
- 数据乱序 → 重新排序
UDP:
- 数据丢失 → 不管
- 数据损坏 → 丢弃
- 数据乱序 → 不管
应用层的责任:
bash
如果应用层需要可靠性,就自己实现。
特点3:面向数据报
含义:UDP保留应用层数据的边界。
具体表现:
- 应用层发送多少数据,UDP就发送多少
- 不会拆分数据
- 不会合并数据
例子:
cpp
// 发送端
char data[100] = "hello";
sendto(sock, data, 100, 0, ...); // 发送100字节
// 接收端
char buf[100];
recvfrom(sock, buf, 100, 0, ...); // 接收100字节
// 必须一次接收100字节,不能分多次接收
UDP 保留报文边界:一次 recvfrom 只能读到一个完整的 UDP 报文。
如果接收缓冲区 buf 比报文小,报文会被截断,多出来的部分直接丢弃(不会像 TCP 那样下次还能继续读)。
对比TCP:
bash
TCP是面向字节流的,可以分多次读写。
UDP是面向数据报的,必须一次读写完整的报文。
重要:这是UDP和TCP最大的区别之一。
三、UDP缓冲区机制
3.1 UDP没有真正的发送缓冲区
TCP的发送缓冲区:
bash
应用层 → 发送缓冲区 → 网络层 → 网络
TCP会把数据先放在缓冲区,然后逐步发送。
UDP的发送过程:
bash
应用层 → 直接交给网络层 → 网络
UDP调用sendto后,数据直接交给内核的网络层,不经过缓冲区。
含义:
sendto返回成功 ≠ 数据一定能到达对方sendto返回成功只表示数据交给了网络层
3.2 UDP有接收缓冲区
接收缓冲区的作用:
- 存储从网络接收到的UDP报文
- 应用层通过
recvfrom从缓冲区读取
接收缓冲区的特点:
- 不保证顺序:
bash
发送:报文1 → 报文2 → 报文3
接收缓冲区可能是:报文2 → 报文1 → 报文3
- 容量有限:
bash
如果缓冲区满了,新到达的报文会被丢弃。
- 报文边界清晰:
bash
每个报文都是独立的,不会混在一起。
3.3 UDP的全双工特性
全双工(Full Duplex):一个连接既可以读,也可以写。
UDP socket的特点:
cpp
// 同一个socket既可以发送,也可以接收
sendto(sock, ...); // 发送
recvfrom(sock, ...); // 接收
对比TCP:
bash
TCP也是全双工的,但需要先建立连接。
UDP不需要连接,天生就是全双工的。
四、UDP的使用注意事项
4.1 UDP报文大小限制
UDP报文的最大长度:65535字节(16位长度字段)
实际限制:
bash
理论最大值:65535字节
UDP首部:8字节
IP首部:20字节
实际数据:65535 - 8 - 20 = 65507字节
但还有更严格的限制:
MTU(Maximum Transmission Unit):最大传输单元。
bash
以太网的MTU:1500字节
WiFi的MTU:1500字节
实际可传输的UDP数据:
bash
1500 - 20(IP首部) - 8(UDP首部) = 1472字节
超过MTU的处理:
bash
如果UDP报文超过MTU,IP层会进行分片。
分片后,如果任何一片丢失,整个报文就无法重组。
建议:
bash
UDP报文大小不要超过1472字节。
如果需要传输更大的数据,在应用层手动分包。
4.2 UDP的校验和
校验和的作用:检测报文是否损坏。
如果校验和出错:
bash
UDP直接丢弃该报文,不通知应用层。
应用层的感受:
bash
"咦,这个报文怎么没了?"
4.3 UDP的应用层协议设计
由于UDP的不可靠性,应用层需要自己处理:
- 数据完整性:
bash
添加校验码或哈希值
- 数据顺序:
bash
添加序列号
- 数据可靠性:
bash
如果需要可靠性,自己实现重传机制
- 数据边界:
bash
由于UDP是面向数据报的,这个不用担心
五、基于UDP的应用层协议
5.1 为什么选择UDP
UDP的优势:
- 速度快(无连接开销)
- 延迟低(直接发送)
- 开销小(首部只有8字节)
适用场景:
- 实时性要求高
- 对可靠性要求不高
- 需要广播/多播
5.2 常见的UDP应用
DNS(Domain Name System)
功能:域名解析。
为什么用UDP:
- 查询通常很小(一个域名)
- 响应也很小(一个IP地址)
- 实时性要求高
- 如果丢失,客户端会重新查询
例子:
bash
# DNS查询
nslookup www.google.com
# 使用UDP的53端口
DHCP(Dynamic Host Configuration Protocol)
功能:动态分配IP地址。
为什么用UDP:
- 客户端还没有IP地址,无法用TCP
- 需要广播(UDP支持广播)
NTP(Network Time Protocol)
功能:网络时间同步。
为什么用UDP:
- 实时性要求高
- 数据量小
视频直播、在线游戏
为什么用UDP:
- 实时性要求极高
- 丢失几帧不影响用户体验
- 不需要完全可靠
例子:
bash
直播:丢失一帧视频,用户看不出来
游戏:丢失一个位置更新,下一帧会纠正
TFTP(Trivial File Transfer Protocol)
功能:简单文件传输。
为什么用UDP:
- 简单(不需要TCP的复杂机制)
- 用于无盘设备启动
5.3 自定义UDP应用层协议
当你写UDP程序时:
cpp
// 定义自己的协议
struct Request {
uint32_t id; // 请求ID
uint32_t seq; // 序列号
char data[1024]; // 数据
};
struct Response {
uint32_t id; // 响应ID
uint32_t seq; // 序列号
uint32_t status; // 状态码
char data[1024]; // 数据
};
// 发送请求
Request req;
req.id = 1;
req.seq = 1;
strcpy(req.data, "hello");
sendto(sock, &req, sizeof(req), 0, ...);
// 接收响应
Response resp;
recvfrom(sock, &resp, sizeof(resp), 0, ...);
六、UDP vs TCP快速对比
6.1 对比表
| 特性 | UDP | TCP |
|---|---|---|
| 连接 | 无连接 | 面向连接 |
| 可靠性 | 不可靠 | 可靠 |
| 顺序 | 不保证 | 保证顺序 |
| 速度 | 快 | 慢 |
| 首部大小 | 8字节 | 20-60字节 |
| 数据格式 | 数据报 | 字节流 |
| 流量控制 | 无 | 有 |
| 拥塞控制 | 无 | 有 |
| 广播/多播 | 支持 | 不支持 |
6.2 选择标准
选择UDP:
- 实时性要求高(视频、游戏、VoIP)
- 对可靠性要求不高
- 需要广播/多播
- 数据量小
选择TCP:
- 需要可靠传输(文件传输、邮件)
- 需要有序到达
- 需要流量控制
- 需要连接管理
七、本篇总结
7.1 核心要点
传输层的作用:
- 负责数据从发送端传输到接收端
- 通过端口号区分不同的应用程序
- 五元组唯一标识一条通信
端口号:
- 0-1023:知名端口(系统保留)
- 1024-65535:动态端口(客户端使用)
UDP的特点:
- 无连接:直接发送,不需要握手
- 不可靠:不保证到达、顺序、完整性
- 面向数据报:保留应用层数据边界
UDP缓冲区:
- 无真正的发送缓冲区(直接交给网络层)
- 有接收缓冲区(存储接收的报文)
- 接收缓冲区满了会丢弃新报文
UDP报文大小:
- 理论最大:65535字节
- 实际限制:1472字节(考虑MTU)
- 超过MTU会被分片,任何一片丢失都会导致整个报文无法重组
UDP应用:
- DNS、DHCP、NTP、视频直播、在线游戏
- 实时性要求高的场景
7.2 容易混淆的点
-
UDP没有发送缓冲区:
- sendto返回成功 ≠ 数据一定能到达
- 只表示数据交给了网络层
-
UDP的不可靠性:
- 不是UDP的bug,而是设计选择
- 为了换取速度和低延迟
-
UDP的面向数据报:
- 和TCP的面向字节流不同
- 应用层发多少,UDP就发多少
-
UDP报文大小限制:
- 不是65535字节就能发
- 实际受MTU限制,通常1472字节
-
UDP的全双工:
- 同一个socket既可以发也可以收
- 不需要建立连接
💬 总结:UDP是一个简单、快速、高效的协议。它放弃了可靠性,换取了速度和低延迟。理解UDP,就理解了为什么有些应用选择UDP而不是TCP。在实时性要求高的场景(视频直播、在线游戏、VoIP),UDP是最佳选择。但如果需要可靠传输(文件下载、邮件、网页),就必须用TCP。两者各有优缺点,没有绝对的好坏,只有是否适合。
👍 点赞、收藏与分享:如果这篇帮你理解了UDP的原理和特点,请点赞收藏!下一篇我们讲TCP,看看TCP如何通过复杂的机制来保证可靠性。