目录
UDP简介
什么是UDP
UDP全称用户数据报协议(User Datagram Protocol),属于 TCP/IP 协议族的传输层协议。它不保证数据传输的可靠性,但具有传输速度快的特点。
UDP 与 TCP 的核心差异
| UDP | TCP | |
| 连接方式 | 无连接(发送前无需建立连接) | 面向连接(三次握手建立连接) |
| 数据可靠性 | 不可靠(不保证送达、不保证顺序) | 可靠(重传、确认、排序机制) |
| 传输效率 | 高(头部开销小,无额外控制) | 低(头部开销大,含重传 / 确认) |
| 拥塞控制 | 无(可能导致网络拥塞) | 有(根据网络状态调整发送速率) |
| 适用场景 | 实时、低延迟需求 | 可靠、有序需求(如文件传输) |
UDP 核心原理
UDP 数据报结构
UDP 数据报的总长度(头部 + 数据)受限于 IP 数据报的最大长度(IPv4 中默认不超过 65535 字节,因 IP 头部 “总长度” 字段为 16 位)。其结构可拆分为 8 字节固定头部 和 可变长度的数据部分,具体如下:
| 部分 | 长度(字节) | 核心作用 |
|---|---|---|
| UDP 头部 | 固定 8 字节 | 标识源 / 目的端口、校验数据完整性 |
| UDP 数据 | 可变(0~65527) | 承载应用层数据(如 DNS 查询、视频流等) |
UDP 头部结构
UDP 头部无可选字段,所有字段均为固定长度,结构清晰,各字段含义如下:
| 字段名称 | 长度(比特) | 十进制长度(字节) | 核心功能说明 |
|---|---|---|---|
| 源端口号(Source Port) | 16 | 2 | 标识发送 UDP 数据报的应用进程(如客户端端口)。 |
| 目的端口号(Destination Port) | 16 | 2 | 标识接收 UDP 数据报的应用进程(如服务器端口,如 DNS 用 53、DHCP 用 67/68)。 |
| UDP 总长度(UDP Length) | 16 | 2 | 表示整个 UDP 数据报的长度(头部 8 字节 + 数据部分长度)。 |
| 校验和(Checksum) | 16 | 2 | 用于验证 UDP 数据报在传输中是否出现错误(如比特翻转、数据丢失)。 |
UDP 伪头部(Pseudo-Header)
UDP 校验和的计算并非仅基于 UDP 自身,还需结合 IP 头部的部分信息(即 “伪头部”),目的是确保 UDP 数据报被正确交付到目标 IP 和端口(避免 “端口正确但 IP 错误” 的情况)。
伪头部并非 UDP 数据报的实际组成部分,仅在计算校验和时临时构造,结构如下(共 12 字节):
| 字段名称 | 长度(比特) | 来源 | 作用 |
|---|---|---|---|
| 源 IP 地址 | 32 | IP 头部 | 确认发送方 IP 正确 |
| 目的 IP 地址 | 32 | IP 头部 | 确认接收方 IP 正确 |
| 保留字段 | 8 | 固定为 0 | 填充,保证字段对齐 |
| 协议字段 | 8 | IP 头部(IPv4 协议号为 17,标识 UDP) | 确认传输层协议为 UDP |
| UDP 总长度 | 16 | UDP 头部的 “UDP 总长度” 字段 | 校验 UDP 长度的一致性 |
实例:
假设客户端(IP:192.168.1.100,端口:54321)向 DNS 服务器(IP:8.8.8.8,端口:53)发送一条 DNS 查询(数据部分为 16 字节),其 UDP 数据报结构如下:
-
UDP 头部(8 字节):
| 字段 | 十进制 | 十六进制 |
| 源端口号(Source Port) | 54321 | 0xD431 |
| 目的端口号(Destination Port) | 53 | 0x0035 |
| UDP 总长度(UDP Length) | 8+16=24 | 0x0018 |
| 校验和(Checksum) | 基于伪头部 + 头部 + 数据计算得出 |
- UDP 数据(16 字节):
DNS 查询报文(包含查询 ID、标志位、查询域名等,如 “www.baidu.com” 的 DNS 编码)
- 临时伪头部(12 字节):
| 字段 | ||
| 源IP | 192.168.1.100 | 0xC0A80164 |
| 目的IP | 8.8.8.8 | 0x08080808 |
| 保留字段 | 0 | 0x00 |
| 协议 | 17 | 0x11 |
| UDP总长度 | 24 | 0x0018 |
UDP 的传输流程
发送端:
应用层数据封装为 UDP 数据报 → 交给 IP 层封装为 IP 数据包 → 链路层封装与发送。
接收端:
链路层提取IP数据包→ IP 层解包得到 UDP 数据报 → 校验校验和(若失败则丢弃) → 按目的端口交给对应应用。
关键特点:
无 “确认”“重传” 机制,发送后不关心是否送达;无 “顺序控制”,接收端可能乱序。
Python UDP 基础
套接字(Socket)—— UDP 通信的接口
在 UDP 通信中,套接字(Socket)是应用程序与操作系统内核(UDP/IP 协议栈)之间的 “桥梁”—— 它封装了底层协议的复杂细节(如端口绑定、IP 地址处理、数据报封装 / 解封装),为应用程序提供了一套简单的 API,使其无需直接操作 UDP 头部或 IP 层,就能轻松实现 UDP 数据的发送与接收。
UDP 套接字类型:
SOCK_DGRAM(数据报套接字),对应协议族AF_INET(IPv4)/AF_INET6(IPv6)。
Python 核心模块:
socket,无需额外安装,内置支持 UDP 操作。
Python 中 UDP 套接字的核心 API 与场景对应:
| API 函数 | 核心作用 | 适用角色 | 关键参数 / 说明 |
|---|---|---|---|
socket.socket() | 创建 UDP 套接字对象 | 客户端 / 服务器 | 需传入 2 个参数: - 协议族: AF_INET(IPv4)/AF_INET6(IPv6)- 套接字类型: SOCK_DGRAM(固定为 UDP) |
bind() | 绑定本地 IP + 端口 | 服务器必用,客户端可选 | 参数为元组(本地IP, 端口):- 服务器需绑定 “固定端口”(如 (0.0.0.0, 12345),0.0.0.0表示监听所有网卡)- 客户端一般不绑定,系统自动分配临时端口 |
sendto() | 发送 UDP 数据到目标地址 | 客户端 / 服务器 | 参数 1:待发送的二进制数据(需用encode()编码字符串)参数 2:目标地址元组 (目标IP, 目标端口) |
recvfrom() | 接收 UDP 数据,同时获取发送方地址 | 客户端 / 服务器 | 参数:缓冲区大小(如 1024,表示一次最多接收 1024 字节) 返回值: (接收的二进制数据, (发送方IP, 发送方端口)) |
setsockopt() | 配置套接字选项(如开启广播、设置超时) | 按需使用 | 例:sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) 开启广播功能 |
close() | 关闭套接字,释放端口资源 | 客户端 / 服务器 | 通信结束后必须调用,避免端口长时间占用(TIME_WAIT 状态) |
UDP 服务器端基础流程
- 创建套接字:socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- 绑定地址与端口:socket.bind(('IP地址', 端口号))(IP 为空表示监听所有网卡,端口 1024 以下需管理员权限)
- 接收数据:data, addr = socket.recvfrom(缓冲区大小)(返回数据字节流 + 发送方地址)
- 处理数据:对接收的字节流解码(如data.decode('utf-8')),执行业务逻辑
- 发送响应(可选):socket.sendto(响应字节流, addr)(向发送方地址回传数据)
- 关闭套接字(可选):socket.close()(或用with语句自动关闭)
UDP 客户端基础流程
- 创建套接字:同服务器端,无需绑定(系统自动分配临时端口)
- 发送数据:socket.sendto(数据字节流, ('服务器IP', 服务器端口))
- 接收响应(可选):response, addr = socket.recvfrom(缓冲区大小)
- 关闭套接字:同服务器端
Python UDP 实践:完整案例与运行
案例 1:简单文本通信(单客户端 - 服务器)
服务端:
import socket
def udp_server(host='', port=12345, buffer_size=1024):
# 1. 创建UDP套接字
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as server_socket:
# 2. 绑定地址与端口
server_socket.bind((host, port))
print(f"UDP服务器已启动,监听 {host}:{port}...")
while True:
# 3. 接收客户端数据
data, client_addr = server_socket.recvfrom(buffer_size)
client_ip, client_port = client_addr
print(f"收到来自 {client_ip}:{client_port} 的消息:{data.decode('utf-8')}")
# 4. 处理数据并发送响应
if data.decode('utf-8').lower() == 'exit':
response = "已收到退出请求,通信结束".encode('utf-8')
server_socket.sendto(response, client_addr)
print(f"与 {client_ip}:{client_port} 断开连接")
break
response = f"服务器已接收:{data.decode('utf-8')}".encode('utf-8')
server_socket.sendto(response, client_addr)
if __name__ == "__main__":
udp_server()
客户端:
import socket
def udp_client(server_host='localhost', server_port=12345, buffer_size=1024):
# 1. 创建UDP套接字(无需绑定)
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client_socket:
while True:
# 2. 输入并发送数据
message = input("请输入要发送的消息(输入exit退出):")
client_socket.sendto(message.encode('utf-8'), (server_host, server_port))
# 3. 处理退出逻辑
if message.lower() == 'exit':
# 接收服务器退出响应
response, _ = client_socket.recvfrom(buffer_size)
print(f"服务器响应:{response.decode('utf-8')}")
break
# 4. 接收服务器响应
response, server_addr = client_socket.recvfrom(buffer_size)
print(f"服务器响应:{response.decode('utf-8')}")
if __name__ == "__main__":
udp_client()
运行结果:


案例 2:UDP 广播通信(一对多)
UDP广播:
- 广播原理:向同一网段所有设备发送
- 广播地址:IPv4 中,网段内最后一个地址为广播地址(如192.168.250.255)
- 套接字设置:需开启广播权限 socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
发送端:
import socket
def udp_broadcast_sender(port=12346, message="UDP广播消息"):
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sender_socket:
# 开启广播权限
sender_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
# 目标广播地址(需根据实际网段修改)
broadcast_addr = ('192.168.250.255', port)
# 发送广播消息
sender_socket.sendto(message.encode('utf-8'), broadcast_addr)
print(f"已向 {broadcast_addr} 发送广播消息:{message}")
if __name__ == "__main__":
udp_broadcast_sender()
接收端:
import socket
def udp_broadcast_receiver(port=12346, buffer_size=1024):
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as receiver_socket:
# 绑定端口(所有网卡监听)
receiver_socket.bind(('', port))
print(f"UDP广播接收端已启动,监听端口 {port}...")
while True:
data, sender_addr = receiver_socket.recvfrom(buffer_size)
print(f"收到来自 {sender_addr} 的广播消息:{data.decode('utf-8')}")
if __name__ == "__main__":
udp_broadcast_receiver()
运行结果:
1118

被折叠的 条评论
为什么被折叠?



