UDP 数据传输:从原理到 Python 实践

目录

UDP简介

什么是UDP

UDP 与 TCP 的核心差异

UDP 核心原理

UDP 数据报结构

UDP 头部结构

UDP 伪头部(Pseudo-Header)

UDP 的传输流程

Python UDP 基础

套接字(Socket)—— UDP 通信的接口

UDP 服务器端基础流程

UDP 客户端基础流程

Python UDP 实践:完整案例与运行

案例 1:简单文本通信(单客户端 - 服务器)

案例 2:UDP 广播通信(一对多)


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 数据报的应用进程(如客户端端口)。
若不需要回复,可设为 0(表示 “无意义端口”)。

目的端口号(Destination Port)

16

2

标识接收 UDP 数据报的应用进程(如服务器端口,如 DNS 用 53、DHCP 用 67/68)。
这是 UDP 实现 “进程间通信” 的核心字段,需与源端口配合使用。

UDP 总长度(UDP Length)

16

2

表示整个 UDP 数据报的长度(头部 8 字节 + 数据部分长度)。
最小值为 8(仅头部,无数据),最大值为 65535(因 16 位字段最大取值为 2¹⁶-1)。

校验和(Checksum)

16

2

用于验证 UDP 数据报在传输中是否出现错误(如比特翻转、数据丢失)。
- 计算范围:UDP 伪头部 + UDP 头部 + UDP 数据
- 可选(IPv4 中可设为 0,IPv6 中必须启用),若校验失败,数据报会被直接丢弃。

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)543210xD431
目的端口号(Destination Port)530x0035
UDP 总长度(UDP Length)8+16=240x0018
校验和(Checksum)基于伪头部 + 头部 + 数据计算得出
  • UDP 数据(16 字节)

DNS 查询报文(包含查询 ID、标志位、查询域名等,如 “www.baidu.com” 的 DNS 编码)

  • 临时伪头部(12 字节):
字段
源IP192.168.1.1000xC0A80164
目的IP8.8.8.80x08080808
保留字段00x00
协议170x11
UDP总长度240x0018

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 服务器端基础流程

  1. 创建套接字:socket.socket(socket.AF_INET, socket.SOCK_DGRAM)​
  2. 绑定地址与端口:socket.bind(('IP地址', 端口号))(IP 为空表示监听所有网卡,端口 1024 以下需管理员权限)​
  3. 接收数据:data, addr = socket.recvfrom(缓冲区大小)(返回数据字节流 + 发送方地址)​
  4. 处理数据:对接收的字节流解码(如data.decode('utf-8')),执行业务逻辑​
  5. 发送响应(可选):socket.sendto(响应字节流, addr)(向发送方地址回传数据)​
  6. 关闭套接字(可选):socket.close()(或用with语句自动关闭)

UDP 客户端基础流程

  1. 创建套接字:同服务器端,无需绑定(系统自动分配临时端口)​
  2. 发送数据:socket.sendto(数据字节流, ('服务器IP', 服务器端口))​
  3. 接收响应(可选):response, addr = socket.recvfrom(缓冲区大小)​
  4. 关闭套接字:同服务器端

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广播:

  1. 广播原理:向同一网段所有设备发送​
  2. 广播地址:IPv4 中,网段内最后一个地址为广播地址(如192.168.250.255)​
  3. 套接字设置:需开启广播权限 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()

运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值