linux学习笔记(32)网络编程——UDP
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 socketint 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 1024int main() {printf("=== UDP客户端启动 ===\n");// 1. 创建UDP socketint 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同时使用
一个进程可以创建很多个套接字,也可以使用两个以上的端口