UDP 协议通信原理详解(Linux-附完整客户端 / 服务器代码)
UDP(用户数据报协议)是传输层核心协议之一,以无连接、轻量、实时性好为核心特点,广泛应用于实时音视频、DNS 查询、物联网设备通信等场景。本文基于学习笔记,详细解析 UDP 的通信原理、协议格式,并附上可直接运行的 Socket 编程代码,帮助快速掌握 UDP 开发核心。
一、UDP 协议核心概述
UDP 是无连接的传输层协议,核心特点如下:
- 无连接:通信前无需建立连接(对比 TCP 的三次握手),直接发送数据;
- 不可靠:不保证数据的有序、无丢失、无重复(可能出现丢包、乱序、重复);
- 无流量控制:发送方不会根据接收方状态调整发送速率;
- 实时性强:无连接开销,数据传输延迟低,适合对实时性要求高的场景;
- 轻量:协议首部仅 8 字节,传输开销小。
二、UDP 协议格式详解
UDP 报文由首部(8 字节)和数据部分组成,首部字段如下(共 16 位 / 字段):
| 字段 | 长度(位) | 功能说明 |
|---|---|---|
| 源端口 | 16 | 发送方应用程序的端口号(可选,若为 0 则表示无需回复) |
| 目的端口 | 16 | 接收方应用程序的端口号(必选,用于定位接收进程) |
| 长度 | 16 | UDP 报文总长度(首部 + 数据,单位:字节;最小值为 8,即仅含首部) |
| 校验和 | 16 | 可选字段,用于校验首部 + 数据的完整性(若为 0 则表示未启用校验) |
三、UDP 通信流程(服务器 / 客户端)
UDP 无连接的特性,使其编程流程比 TCP 更简洁,核心是 “绑定地址(服务器)→收发数据”。
| 服务器 | 客户机 | ||
| 创建套接字 | socket | socket | 创建套接字 |
| 准备地址结构 | sockaddr_in | sockaddr_in | 准备地址结构 |
| 绑定地址 | bind | ---- | ---- |
| 接收请求 | recvfrom | sendto | 发送请求 |
| 发送响应 | sendto | recvfrom | 接收响应 |
| 关闭套接字 | close | close | 关闭套接字 |
1. 服务器端流程
服务器负责绑定端口、接收客户端数据并响应,步骤为:
- 创建 Socket:调用
socket()创建通信端点,指定 IPv4(AF_INET)和 UDP(SOCK_DGRAM); - 准备地址结构:定义
sockaddr_in结构体,设置协议族、监听端口、绑定 IP(INADDR_ANY表示监听所有网卡); - 绑定地址:调用
bind()将 Socket 与本机 IP + 端口绑定; - 循环收发数据:通过
recvfrom()接收客户端数据(同时获取客户端地址),处理后用sendto()将响应发回客户端; - 关闭 Socket:调用
close()关闭套接字。
2. 客户端流程
客户端无需绑定端口(系统自动分配临时端口),直接向服务器发送数据,步骤为:
- 创建 Socket:同服务器,调用
socket(); - 准备服务器地址:定义
sockaddr_in结构体,设置服务器的 IP 和端口; - 收发数据:通过
sendto()向服务器发送数据,用recvfrom()接收服务器响应; - 关闭 Socket:调用
close()关闭套接字。
四、UDP 核心函数解析
UDP 编程依赖以下核心函数(基于 Linux 环境):
| 函数原型 | 功能说明 |
|---|---|
int socket(int domain, int type, int protocol) | 创建 UDP Socket;domain=AF_INET(IPv4),type=SOCK_DGRAM(UDP),protocol=0 |
|
| 绑定 Socket 到 IP + 端口;addr为sockaddr_in结构(需转换为通用sockaddr) |
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) | 接收数据,同时获取发送方(客户端)的地址;flags=0为默认阻塞模式 |
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) | 向指定地址(服务器 / 客户端)发送数据;flags=0为默认模式 |
int close(int fd) | 关闭 Socket |
五、完整客户端 / 服务器代码实现
以下是可直接编译运行的 UDP 客户端与服务器代码(Linux 环境),实现 “客户端发送字符串→服务器转大写后回显” 的功能。
1. UDP 服务器代码(udp_server.c)
#include
#include
#include
#include
#include
#include
#include
#define PORT 8888 // 监听端口
#define BUF_SIZE 1024
int main() {
int sockfd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
char buf[BUF_SIZE];
ssize_t n;
// 1. 创建UDP Socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket创建失败");
exit(1);
}
// 2. 准备服务器地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET; // IPv4协议
server_addr.sin_port = htons(PORT); // 端口转为网络字节序
server_addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡
// 3. 绑定地址
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind失败");
close(sockfd);
exit(1);
}
printf("UDP服务器启动,监听端口%d...
", PORT);
// 4. 循环收发数据
while (1) {
// 接收客户端数据(同时获取客户端地址)
n = recvfrom(sockfd, buf, BUF_SIZE-1, 0, (struct sockaddr*)&client_addr, &client_addr_len);
if (n < 0) {
perror("recvfrom失败");
continue;
}
buf[n] = ' ';
printf("收到客户端[%s:%d]数据:%s
",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buf);
// 处理数据:转为大写
for (int i = 0; i < n; i++) {
if (buf[i] >= 'a' && buf[i] <= 'z') {
buf[i] -= 32;
}
}
// 发送响应给客户端
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&client_addr, client_addr_len);
}
// 5. 关闭Socket(实际不会执行,需手动终止)
close(sockfd);
return 0;
}
2. UDP 客户端代码(udp_client.c)
#include
#include
#include
#include
#include
#include
#define SERVER_IP "127.0.0.1" // 服务器IP
#define SERVER_PORT 8888 // 服务器端口
#define BUF_SIZE 1024
int main() {
int sockfd;
struct sockaddr_in server_addr;
socklen_t server_addr_len = sizeof(server_addr);
char buf[BUF_SIZE];
ssize_t n;
// 1. 创建UDP Socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket创建失败");
exit(1);
}
// 2. 准备服务器地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
// 将字符串IP转为网络字节序
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
perror("IP地址无效");
close(sockfd);
exit(1);
}
printf("已连接到UDP服务器[%s:%d],输入数据发送(输入q退出):
", SERVER_IP, SERVER_PORT);
// 3. 收发数据
while (1) {
// 从键盘输入数据
fgets(buf, BUF_SIZE-1, stdin);
buf[strcspn(buf, "
")] = ' '; // 去掉换行符
// 输入q则退出
if (strcmp(buf, "q") == 0) {
break;
}
// 发送数据到服务器
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&server_addr, server_addr_len);
// 接收服务器回显数据
n = recvfrom(sockfd, buf, BUF_SIZE-1, 0, (struct sockaddr*)&server_addr, &server_addr_len);
if (n < 0) {
perror("recvfrom失败");
continue;
}
buf[n] = ' ';
printf("服务器回显:%s
", buf);
}
// 4. 关闭Socket
close(sockfd);
return 0;
}
编译与运行
- 编译服务器:
gcc udp_server.c -o udp_server - 编译客户端:
gcc udp_client.c -o udp_client - 启动服务器:
./udp_server - 启动客户端:
./udp_client(可启动多个客户端测试)
六、总结
UDP 以 “无连接、轻量、实时” 为核心优势,适合对延迟敏感但可容忍少量丢包的场景;其 Socket 编程流程简洁,服务器仅需 “创建→绑定→收发”,客户端无需绑定端口直接通信。本文的代码实现了基础的回显功能,可扩展为更复杂的应用(如实时数据采集、音视频传输等)。
作者:趙小贞
声明:本文基于个人学习经验总结,如有错误欢迎指正!
版权:转载请注明出处,禁止商业用途。
AI声明:本文代码注释借助AI详细补全,整体框架和内容优化借助CSDN文章AI助手润色!
整体内容原创,用于复习总结以及分享经验,欢迎大家指点!










