一、UDP通信核心特点
UDP 是无连接、面向数据报、不可靠的传输层协议。无需提前建立连接,直接通过数据报收发数据,通信开销小、速度快,适用于直播、语音、游戏等对实时性要求高、可容忍少量丢包的场景。
UDP 通信最大特征:服务端必须绑定端口,客户端无需绑定端口,内核自动分配临时端口。
二、UDP 标准通信总流程
1. 服务端流程(固定顺序)
socket() → 填充 serv_addr → bind() → 循环 recvfrom() 获取客户端地址 → sendto() 应答 → close()
2. 客户端流程(固定顺序)
socket() → 填充 serv_addr → sendto() 发送数据 → recvfrom() 接收应答 → close()

三、分步流程原理详解
(一)服务端流程详解
1. socket() 创建UDP套接字
调用 socket() 创建 SOCK_DGRAM 数据报套接字,指定 UDP 协议,生成通信文件描述符。
2. 填充 serv_addr 地址结构体
配置 IPv4 协议族、监听端口、本机任意网卡(INADDR_ANY),清零结构体防止脏数据导致通信异常。
3. bind() 绑定端口
将套接字与本机固定端口绑定,让客户端可以通过该端口精准发送数据到服务端。UDP服务端必须绑定端口。
4. 循环 recvfrom() 获取客户端地址与数据
recvfrom() 是 UDP 核心函数,不仅接收客户端数据,同时自动获取客户端IP和端口信息,为后续应答提供目标地址。
5. sendto() 应答客户端
利用 recvfrom() 获取到的客户端地址,通过 sendto() 向客户端返回应答数据。
6. close() 关闭套接字
通信结束后释放文件描述符,回收系统资源。
(二)客户端流程详解
1. socket() 创建UDP套接字
与服务端创建方式一致,客户端无需绑定端口,由操作系统自动分配临时端口。
2. 填充 serv_addr 服务端地址
手动填写服务端 IP 地址与监听端口,确定数据发送的目标地址。
3. sendto() 发送数据
UDP 无连接特性,无需握手,直接通过 sendto() 向指定服务端地址发送数据报。
4. recvfrom() 接收服务端应答
阻塞等待服务端返回的应答数据,完成一次完整通信交互。
5. close() 关闭套接字
交互结束,释放套接字资源。
四、完整可运行代码
1. UDP服务端代码(严格对应服务端流程)
cs
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 9999
#define BUF_LEN 1024
int main(void)
{
// 1. socket() 创建UDP套接字
int srv_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (srv_fd == -1)
{
perror("socket");
return -1;
}
// 2. 填充 serv_addr 本地地址结构体
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = INADDR_ANY;
// 3. bind() 绑定端口
if (bind(srv_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
{
perror("bind");
close(srv_fd);
return -1;
}
printf("UDP服务端启动成功,监听端口:%d\n", PORT);
char recv_buf[BUF_LEN] = {0};
struct sockaddr_in cli_addr;
socklen_t cli_addr_len = sizeof(cli_addr);
while (1)
{
memset(recv_buf, 0, BUF_LEN);
// 4. 循环recvfrom,获取客户端地址 + 接收数据
ssize_t recv_len = recvfrom(srv_fd, recv_buf, BUF_LEN, 0,
(struct sockaddr *)&cli_addr, &cli_addr_len);
if (recv_len <= 0)
{
perror("recvfrom");
continue;
}
// 打印客户端信息
char cli_ip[40] = {0};
inet_ntop(AF_INET, &cli_addr.sin_addr, cli_ip, sizeof(cli_ip));
printf("收到客户端 [%s:%d] 消息:%s\n", cli_ip, ntohs(cli_addr.sin_port), recv_buf);
// 5. sendto() 应答客户端
char reply[BUF_LEN];
snprintf(reply, BUF_LEN, "服务端已接收:%s", recv_buf);
sendto(srv_fd, reply, strlen(reply), 0,
(struct sockaddr *)&cli_addr, cli_addr_len);
}
// 6. close() 关闭套接字
close(srv_fd);
return 0;
}
2. UDP客户端代码(严格对应客户端流程)
cs
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 9999
#define BUF_LEN 1024
int main(void)
{
// 1. socket() 创建UDP套接字
int cli_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (cli_fd == -1)
{
perror("socket");
return -1;
}
// 2. 填充 serv_addr 服务端地址结构体
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVER_PORT);
inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr);
socklen_t serv_addr_len = sizeof(serv_addr);
char send_buf[BUF_LEN] = {0};
char recv_buf[BUF_LEN] = {0};
while (1)
{
printf("请输入消息(quit退出):");
fgets(send_buf, BUF_LEN, stdin);
send_buf[strcspn(send_buf, "\n")] = '\0';
if (strcmp(send_buf, "quit") == 0)
break;
// 3. sendto() 向服务端发送数据
sendto(cli_fd, send_buf, strlen(send_buf), 0,
(struct sockaddr *)&serv_addr, serv_addr_len);
// 4. recvfrom() 接收服务端应答
memset(recv_buf, 0, BUF_LEN);
recvfrom(cli_fd, recv_buf, BUF_LEN, 0, NULL, NULL);
printf("服务端应答:%s\n\n", recv_buf);
}
// 5. close() 关闭套接字
close(cli_fd);
return 0;
}
五、编译与运行方法
1. 编译命令
cs
gcc udp_server.c -o server
gcc udp_client.c -o client
2. 运行步骤
第一步:终端1运行服务端
cs
./server
第二步:终端2运行客户端,输入内容交互
cs
./client
六、核心流程总结
-
服务端核心:必须 bind 绑定端口,依靠 recvfrom 获取客户端地址,才能回复数据。
-
客户端核心:无需 bind,直接指定服务端地址,sendto 发数据、recvfrom 收应答。
-
UDP 本质:无连接通信,每次收发数据都需要明确对方地址。