UDP是什么?
UDP = 像寄明信片
- 写了就寄,不管对方收没收到
- 不保证顺序,可能先寄的后到
- 简单快速,没有复杂流程
1. UDP vs TCP 直观对比
TCP(像打电话):
// 必须建立连接
connect() → 三次握手 → 可靠传输 → 四次挥手
// 保证:顺序、不丢失、不重复
UDP(像寄明信片):
// 直接发送,无连接
sendto() → 网络 → 可能到达,可能丢失
// 不保证:顺序、不丢失、不重复
2. 最简单的UDP服务器
#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 8080
#define BUFFER_SIZE 1024
int main() {
printf("=== UDP服务器启动 ===\n");
// 1. 创建UDP socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket创建失败");
exit(1);
}
printf("1. UDP socket创建成功\n");
// 2. 设置服务器地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
server_addr.sin_port = htons(PORT); // 端口号
// 3. 绑定地址(UDP也需要bind!)
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("绑定失败");
close(sockfd);
exit(1);
}
printf("2. 绑定端口 %d 成功\n", PORT);
printf("3. 等待客户端数据...\n\n");
// 主循环
while (1) {
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
// 4. 接收数据(阻塞等待)
int bytes_received = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0,
(struct sockaddr*)&client_addr, &client_len);
if (bytes_received > 0) {
buffer[bytes_received] = '\0'; // 添加字符串结束符
// 获取客户端信息
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
int client_port = ntohs(client_addr.sin_port);
printf("📨 收到来自 %s:%d 的消息:\n", client_ip, client_port);
printf(" 内容: %s\n", buffer);
printf(" 长度: %d 字节\n\n", bytes_received);
// 5. 回复客户端
char response[BUFFER_SIZE];
snprintf(response, sizeof(response),
"服务器已收到你的消息: %s", buffer);
sendto(sockfd, response, strlen(response), 0,
(struct sockaddr*)&client_addr, client_len);
printf("📤 已发送回复\n\n");
}
}
close(sockfd);
return 0;
}
3. 最简单的UDP客户端
#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 PORT 8080
#define BUFFER_SIZE 1024
int main() {
printf("=== UDP客户端启动 ===\n");
// 1. 创建UDP socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket创建失败");
exit(1);
}
printf("1. UDP socket创建成功\n");
// 2. 设置服务器地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);
printf("2. 目标服务器: %s:%d\n", SERVER_IP, PORT);
while (1) {
char buffer[BUFFER_SIZE];
// 3. 获取用户输入
printf("\n💬 请输入要发送的消息 (输入quit退出): ");
fgets(buffer, sizeof(buffer), stdin);
buffer[strcspn(buffer, "\n")] = '\0'; // 去掉换行符
if (strcmp(buffer, "quit") == 0) {
break;
}
// 4. 发送数据到服务器
int bytes_sent = sendto(sockfd, buffer, strlen(buffer), 0,
(struct sockaddr*)&server_addr, sizeof(server_addr));
printf("📤 发送: %s (%d字节)\n", buffer, bytes_sent);
// 5. 接收服务器回复
char response[BUFFER_SIZE];
struct sockaddr_in from_addr;
socklen_t from_len = sizeof(from_addr);
int bytes_received = recvfrom(sockfd, response, sizeof(response) - 1, 0,
(struct sockaddr*)&from_addr, &from_len);
if (bytes_received > 0) {
response[bytes_received] = '\0';
printf("📥 收到回复: %s\n", response);
} else {
printf("❌ 未收到回复 (可能丢失)\n");
}
}
close(sockfd);
printf("👋 客户端退出\n");
return 0;
}
4. 编译和测试
编译命令:
编译UDP服务器
gcc -o udp_server udp_server.c
编译UDP客户端
gcc -o udp_client udp_client.c
测试步骤:
- 终端1 - 启动UDP服务器:
./udp_server - 终端2 - 启动UDP客户端:
./udp_client
输入:Hello UDP! - 终端3 - 再启动一个UDP客户端:
./udp_client
输入:This is client 2
预期输出:
服务器输出:
=== UDP服务器启动 ===
1. UDP socket创建成功
2. 绑定端口 8080 成功
3. 等待客户端数据...
📨 收到来自 127.0.0.1:54321 的消息:
内容: Hello UDP!
长度: 10 字节
📤 已发送回复
📨 收到来自 127.0.0.1:54322 的消息:
内容: This is client 2
长度: 16 字节
📤 已发送回复
客户端输出:
=== UDP客户端启动 ===
1. UDP socket创建成功
2. 目标服务器: 127.0.0.1:8080
💬 请输入要发送的消息 (输入quit退出): Hello UDP!
📤 发送: Hello UDP! (10字节)
📥 收到回复: 服务器已收到你的消息: Hello UDP!
💬 请输入要发送的消息 (输入quit退出): quit
👋 客户端退出
5. UDP的核心特点
无连接:
// 不需要connect(),直接发送
sendto(sockfd, data, len, 0, &server_addr, addr_len);
// 每个数据包都是独立的
// 可以同时给多个服务器发送数据
不可靠:
// 数据可能:
// - 丢失(网络拥堵)
// - 重复(网络重传)
// - 乱序(走不同路径)
// - 损坏(比特错误)
简单高效:
// 头部只有8字节
struct udp_header {
uint16_t source_port;
uint16_t dest_port;
uint16_t length;
uint16_t checksum;
};
// 相比TCP的20字节头部,开销小很多
6. UDP的关键函数
sendto()****- 发送数据
int sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
recvfrom()****- 接收数据
int recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
// src_addr会填充发送者的地址信息
7. UDP的使用场景
适合UDP的场景:
// 1. 实时音视频通话
// - 丢失几帧没关系,但要低延迟
// 2. DNS查询
// - 简单请求响应,丢了重试就行
// 3. 游戏数据包
// - 位置更新,丢了用新的覆盖
// 4. 广播/组播
// - 一对多通信
不适合UDP的场景:
// 1. 文件传输
// - 不能丢失任何数据
// 2. 网页浏览
// - 需要保证所有数据正确到达
// 3. 电子邮件
// - 必须可靠传输
8. UDP的优缺点总结
优点:
- ✅ 速度快:没有连接建立、确认、重传
- ✅ 开销小:头部只有8字节
- ✅ 无连接:可以同时与多个对端通信
- ✅ 适合广播:天然支持一对多
缺点:
- ❌ 不可靠:可能丢失、重复、乱序
- ❌ 无拥塞控制:可能加剧网络拥堵
- ❌ 需要应用层处理:自己实现可靠性
🎯 终极对比:
|-----|-----------|------------|
| 特性 | TCP (打电话) | UDP (寄明信片) |
| 连接 | 需要建立连接 | 无连接 |
| 可靠性 | 保证不丢失 | 可能丢失 |
| 顺序 | 保证顺序 | 不保证顺序 |
| 速度 | 慢 | 快 |
| 头部 | 20字节 | 8字节 |
| 控制 | 有流量和拥塞控制 | 无控制 |
同一个端口,可以被tcp和udp同时使用
一个进程可以创建很多个套接字,也可以使用两个以上的端口