recvfrom 函数详解 链接到标题
1. 函数介绍 链接到标题
recvfrom
是 Linux 网络编程中用于接收 UDP 数据报的系统调用。可以把 recvfrom
想象成"网络收件员"——它站在网络门口,等待并接收来自世界各地的"邮件"(数据包),并且还能记住每封邮件是从哪里寄来的。
与 TCP 的 recv
函数不同,UDP 是无连接的协议,所以 recvfrom
需要同时接收数据和发送方的地址信息,就像收件员不仅要收取包裹,还要记录寄件人的地址一样。
2. 函数原型 链接到标题
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
3. 功能 链接到标题
recvfrom
函数用于从套接字接收数据,并获取发送方的地址信息。它主要用于面向无连接的套接字(如 UDP 套接字)。
4. 参数 链接到标题
- sockfd: 套接字文件描述符
- buf: 指向接收缓冲区的指针
- len: 缓冲区大小
- flags: 控制接收行为的标志位
- src_addr: 指向发送方地址结构体的指针(可为 NULL)
- addrlen: 指向地址结构体大小的指针(可为 NULL)
5. 标志位(flags 参数) 链接到标题
标志 | 值 | 说明 |
---|---|---|
MSG_OOB | 0x01 | 接收带外数据 |
MSG_PEEK | 0x02 | 窥视数据(不从队列中移除) |
MSG_DONTROUTE | 0x04 | 不使用网关路由 |
MSG_CTRUNC | 0x08 | 控制数据被截断 |
MSG_PROXY | 0x10 | SOCKS4 代理协议 |
MSG_TRUNC | 0x20 | 数据报被截断 |
MSG_DONTWAIT | 0x40 | 非阻塞操作 |
MSG_EOR | 0x80 | 记录结束标记 |
MSG_WAITALL | 0x100 | 等待完整数据 |
MSG_FIN | 0x200 | TCP FIN 标志 |
MSG_SYN | 0x400 | TCP SYN 标志 |
MSG_CONFIRM | 0x800 | 确认路径可达 |
6. 地址结构体 链接到标题
IPv4 地址结构体 链接到标题
struct sockaddr_in {
sa_family_t sin_family; /* 地址族 (AF_INET) */
in_port_t sin_port; /* 端口号 (网络字节序) */
struct in_addr sin_addr; /* IP 地址 */
unsigned char sin_zero[8]; /* 填充字节 */
};
IPv6 地址结构体 链接到标题
struct sockaddr_in6 {
sa_family_t sin6_family; /* 地址族 (AF_INET6) */
in_port_t sin6_port; /* 端口号 (网络字节序) */
uint32_t sin6_flowinfo; /* 流信息 */
struct in6_addr sin6_addr; /* IPv6 地址 */
uint32_t sin6_scope_id; /* 范围 ID */
};
7. 返回值 链接到标题
- 成功: 返回实际接收到的字节数
- 失败: 返回 -1,并设置相应的 errno 错误码
- 连接断开: 返回 0(TCP 套接字)
8. 常见错误码 链接到标题
EAGAIN/EWOULDBLOCK
: 非阻塞模式下无数据可读EBADF
: sockfd 不是有效的文件描述符ECONNREFUSED
: 连接被拒绝EFAULT
: buf 指针无效EINTR
: 被信号中断EINVAL
: 参数无效ENOMEM
: 内存不足ENOTCONN
: 套接字未连接(TCP)ENOTSOCK
: sockfd 不是套接字
9. 相似函数或关联函数 链接到标题
- recv: 接收 TCP 数据(不需要地址信息)
- sendto: 发送 UDP 数据报
- recvmsg: 接收带控制信息的数据
- sendmsg: 发送带控制信息的数据
- read: 普通文件读取
- socket: 创建套接字
- bind: 绑定套接字地址
- connect: 连接套接字
10. 示例代码 链接到标题
示例1:基础用法 - UDP 服务器 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int sockfd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
ssize_t bytes_received;
printf("=== UDP 服务器示例 ===\n\n");
// 1. 创建 UDP 套接字
printf("1. 创建 UDP 套接字...\n");
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror("创建套接字失败");
return 1;
}
printf("✓ 套接字创建成功 (fd: %d)\n\n", sockfd);
// 2. 配置服务器地址
printf("2. 配置服务器地址...\n");
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有接口
server_addr.sin_port = htons(PORT); // 网络字节序端口
printf(" 地址族: AF_INET\n");
printf(" IP 地址: 0.0.0.0 (所有接口)\n");
printf(" 端口号: %d\n\n", PORT);
// 3. 绑定套接字
printf("3. 绑定套接字...\n");
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定套接字失败");
close(sockfd);
return 1;
}
printf("✓ 套接字绑定成功\n\n");
printf("UDP 服务器启动成功,监听端口 %d\n", PORT);
printf("等待客户端消息...\n");
printf("按 Ctrl+C 退出\n\n");
// 4. 接收数据循环
int message_count = 0;
while (1) {
printf("等待第 %d 条消息...\n", message_count + 1);
// 使用 recvfrom 接收数据和发送方地址
bytes_received = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,
(struct sockaddr*)&client_addr, &client_len);
if (bytes_received == -1) {
if (errno == EINTR) {
printf("操作被中断\n");
continue;
} else {
perror("接收数据失败");
break;
}
}
// 处理接收到的数据
message_count++;
buffer[bytes_received] = '\0';
printf("=== 收到消息 %d ===\n", message_count);
printf("发送方地址: %s\n", inet_ntoa(client_addr.sin_addr));
printf("发送方端口: %d\n", ntohs(client_addr.sin_port));
printf("消息长度: %zd 字节\n", bytes_received);
printf("消息内容: %s", buffer);
printf("\n");
// 发送回复
const char *reply = "服务器收到您的消息!";
ssize_t bytes_sent = sendto(sockfd, reply, strlen(reply), 0,
(struct sockaddr*)&client_addr, client_len);
if (bytes_sent == -1) {
perror("发送回复失败");
} else {
printf("✓ 回复发送成功 (%zd 字节)\n\n", bytes_sent);
}
// 退出条件
if (strncmp(buffer, "quit", 4) == 0 || strncmp(buffer, "exit", 4) == 0) {
printf("收到退出命令,服务器即将关闭...\n");
break;
}
}
// 5. 清理资源
printf("\n5. 清理资源...\n");
close(sockfd);
printf("✓ 套接字已关闭\n");
printf("\n=== UDP 服务器特点 ===\n");
printf("1. 无连接: 不需要建立连接\n");
printf("2. 不可靠: 不保证数据到达\n");
printf("3. 面向报文: 保持消息边界\n");
printf("4. 高效: 开销小,速度快\n");
printf("5. 支持广播: 可以一对多通信\n");
printf("6. 无状态: 服务器不维护客户端状态\n");
return 0;
}
示例2:UDP 客户端 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <time.h>
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
#define BUFFER_SIZE 1024
// 发送消息到服务器
int send_message_to_server(int sockfd, struct sockaddr_in *server_addr,
const char *message) {
printf("发送消息到服务器: %s", message);
ssize_t bytes_sent = sendto(sockfd, message, strlen(message), 0,
(struct sockaddr*)server_addr, sizeof(*server_addr));
if (bytes_sent == -1) {
perror("发送消息失败");
return -1;
}
printf("✓ 消息发送成功 (%zd 字节)\n", bytes_sent);
return 0;
}
// 接收服务器回复
int receive_reply_from_server(int sockfd, struct sockaddr_in *server_addr) {
char buffer[BUFFER_SIZE];
struct sockaddr_in reply_addr;
socklen_t addr_len = sizeof(reply_addr);
printf("等待服务器回复...\n");
ssize_t bytes_received = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,
(struct sockaddr*)&reply_addr, &addr_len);
if (bytes_received == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("✗ 接收超时\n");
return -1;
} else {
perror("接收回复失败");
return -1;
}
}
buffer[bytes_received] = '\0';
printf("=== 服务器回复 ===\n");
printf("发送方地址: %s\n", inet_ntoa(reply_addr.sin_addr));
printf("发送方端口: %d\n", ntohs(reply_addr.sin_port));
printf("回复内容: %s", buffer);
printf("回复长度: %zd 字节\n\n", bytes_received);
return 0;
}
int main() {
int sockfd;
struct sockaddr_in server_addr;
char input_buffer[256];
printf("=== UDP 客户端示例 ===\n\n");
// 1. 创建 UDP 套接字
printf("1. 创建 UDP 套接字...\n");
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror("创建套接字失败");
return 1;
}
printf("✓ 套接字创建成功 (fd: %d)\n\n", sockfd);
// 2. 配置服务器地址
printf("2. 配置服务器地址...\n");
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
fprintf(stderr, "无效的服务器地址: %s\n", SERVER_IP);
close(sockfd);
return 1;
}
printf(" 服务器 IP: %s\n", SERVER_IP);
printf(" 服务器端口: %d\n", SERVER_PORT);
printf(" 地址族: AF_INET\n\n");
// 3. 设置套接字超时
printf("3. 设置接收超时...\n");
struct timeval timeout;
timeout.tv_sec = 5; // 5 秒超时
timeout.tv_usec = 0;
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == -1) {
perror("设置超时失败");
// 继续执行,超时不是必需的
} else {
printf("✓ 接收超时设置为 5 秒\n\n");
}
// 4. 发送欢迎消息
printf("4. 发送欢迎消息...\n");
char welcome_msg[100];
snprintf(welcome_msg, sizeof(welcome_msg),
"客户端连接时间: %s", ctime(&(time_t){time(NULL)}));
if (send_message_to_server(sockfd, &server_addr, welcome_msg) == -1) {
close(sockfd);
return 1;
}
// 5. 接收欢迎回复
printf("5. 接收欢迎回复...\n");
receive_reply_from_server(sockfd, &server_addr);
// 6. 交互式消息发送
printf("=== 交互式通信 ===\n");
printf("输入消息发送到服务器 (输入 'quit' 或 'exit' 退出):\n\n");
int message_count = 0;
while (1) {
printf("消息 %d > ", ++message_count);
fflush(stdout);
if (fgets(input_buffer, sizeof(input_buffer), stdin) == NULL) {
if (feof(stdin)) {
printf("\n检测到 EOF,退出客户端\n");
break;
} else {
perror("读取输入失败");
continue;
}
}
// 移除换行符
size_t len = strlen(input_buffer);
if (len > 0 && input_buffer[len - 1] == '\n') {
input_buffer[len - 1] = '\0';
}
// 检查退出命令
if (strcmp(input_buffer, "quit") == 0 || strcmp(input_buffer, "exit") == 0) {
printf("发送退出命令...\n");
send_message_to_server(sockfd, &server_addr, input_buffer);
break;
}
// 发送消息
if (strlen(input_buffer) > 0) {
if (send_message_to_server(sockfd, &server_addr, input_buffer) == -1) {
continue;
}
// 接收回复
receive_reply_from_server(sockfd, &server_addr);
}
}
// 7. 清理资源
printf("\n7. 清理资源...\n");
close(sockfd);
printf("✓ 套接字已关闭\n");
printf("✓ 客户端正常退出\n");
printf("\n=== UDP 客户端特点 ===\n");
printf("1. 无连接: 不需要 connect\n");
printf("2. 轻量级: 开销小\n");
printf("3. 实时性: 适合实时应用\n");
printf("4. 无状态: 每次通信独立\n");
printf("5. 广播支持: 支持广播和组播\n");
printf("6. 消息边界: 保持数据报边界\n");
printf("\n");
printf("UDP 适用场景:\n");
printf("1. DNS 查询\n");
printf("2. DHCP 协议\n");
printf("3. 实时音视频传输\n");
printf("4. 在线游戏\n");
printf("5. 网络监控\n");
printf("6. 广播通信\n");
return 0;
}
示例3:多客户端 UDP 服务器 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_CLIENTS 100
// 客户端信息结构体
struct client_info {
struct sockaddr_in addr;
time_t last_activity;
int message_count;
};
// 服务器状态
volatile sig_atomic_t server_running = 1;
struct client_info clients[MAX_CLIENTS];
int client_count = 0;
// 信号处理函数
void signal_handler(int sig) {
printf("\n收到信号 %d,准备关闭服务器...\n", sig);
server_running = 0;
}
// 添加或更新客户端信息
int update_client_info(struct sockaddr_in *client_addr) {
time_t current_time = time(NULL);
// 查找现有客户端
for (int i = 0; i < client_count; i++) {
if (clients[i].addr.sin_addr.s_addr == client_addr->sin_addr.s_addr &&
clients[i].addr.sin_port == client_addr->sin_port) {
// 更新现有客户端
clients[i].last_activity = current_time;
clients[i].message_count++;
return i;
}
}
// 添加新客户端
if (client_count < MAX_CLIENTS) {
clients[client_count].addr = *client_addr;
clients[client_count].last_activity = current_time;
clients[client_count].message_count = 1;
return client_count++;
}
printf("警告: 客户端数量已达上限 %d\n", MAX_CLIENTS);
return -1;
}
// 显示客户端统计信息
void show_client_statistics() {
printf("\n=== 客户端统计信息 ===\n");
printf("总客户端数: %d\n", client_count);
if (client_count > 0) {
printf("\n%-15s %-8s %-20s %-10s\n",
"IP地址", "端口", "最后活动时间", "消息数");
printf("%-15s %-8s %-20s %-10s\n",
"------", "----", "----------", "------");
time_t current_time = time(NULL);
for (int i = 0; i < client_count; i++) {
printf("%-15s %-8d %-20s %-10d\n",
inet_ntoa(clients[i].addr.sin_addr),
ntohs(clients[i].addr.sin_port),
ctime(&clients[i].last_activity) + 11, // 只显示时间部分
clients[i].message_count);
}
// 显示活跃客户端数(最近 5 分钟)
int active_clients = 0;
for (int i = 0; i < client_count; i++) {
if (current_time - clients[i].last_activity < 300) { // 5分钟 = 300秒
active_clients++;
}
}
printf("\n活跃客户端 (5分钟内): %d\n", active_clients);
}
printf("\n");
}
// 发送广播消息
int broadcast_message(int sockfd, const char *message, struct sockaddr_in *exclude_addr) {
int sent_count = 0;
for (int i = 0; i < client_count; i++) {
// 排除指定地址
if (exclude_addr &&
clients[i].addr.sin_addr.s_addr == exclude_addr->sin_addr.s_addr &&
clients[i].addr.sin_port == exclude_addr->sin_port) {
continue;
}
ssize_t bytes_sent = sendto(sockfd, message, strlen(message), 0,
(struct sockaddr*)&clients[i].addr,
sizeof(clients[i].addr));
if (bytes_sent != -1) {
sent_count++;
}
}
return sent_count;
}
int main() {
int sockfd;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
printf("=== 多客户端 UDP 服务器 ===\n\n");
// 1. 设置信号处理
printf("1. 设置信号处理...\n");
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
printf("✓ 信号处理设置完成\n\n");
// 2. 创建 UDP 套接字
printf("2. 创建 UDP 套接字...\n");
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror("创建套接字失败");
return 1;
}
printf("✓ 套接字创建成功 (fd: %d)\n\n", sockfd);
// 3. 配置服务器地址
printf("3. 配置服务器地址...\n");
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
printf(" 地址族: AF_INET\n");
printf(" IP 地址: 0.0.0.0 (所有接口)\n");
printf(" 端口号: %d\n\n", PORT);
// 4. 绑定套接字
printf("4. 绑定套接字...\n");
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定套接字失败");
close(sockfd);
return 1;
}
printf("✓ 套接字绑定成功\n\n");
// 5. 设置套接字选项
printf("5. 设置套接字选项...\n");
// 允许地址重用
int reuse = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
perror("设置地址重用失败");
} else {
printf(" ✓ 地址重用已启用\n");
}
printf("\n=== 服务器启动成功 ===\n");
printf("监听端口: %d\n", PORT);
printf("最大客户端数: %d\n", MAX_CLIENTS);
printf("按 Ctrl+C 退出服务器\n");
printf("输入 'stats' 查看统计信息\n");
printf("输入 'broadcast <message>' 发送广播消息\n\n");
// 6. 主接收循环
int total_messages = 0;
while (server_running) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
printf("等待消息... (已处理 %d 条消息)\n", total_messages);
// 接收数据
ssize_t bytes_received = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,
(struct sockaddr*)&client_addr, &client_len);
if (bytes_received == -1) {
if (errno == EINTR) {
printf("操作被信号中断\n");
continue;
} else {
perror("接收数据失败");
break;
}
}
// 处理接收到的数据
total_messages++;
buffer[bytes_received] = '\0';
// 更新客户端信息
int client_index = update_client_info(&client_addr);
printf("=== 消息 %d ===\n", total_messages);
printf("客户端地址: %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
printf("消息长度: %zd 字节\n", bytes_received);
printf("消息内容: %s", buffer);
printf("\n");
// 处理特殊命令
if (strncmp(buffer, "stats", 5) == 0) {
show_client_statistics();
// 发送统计信息回复
char stats_reply[256];
snprintf(stats_reply, sizeof(stats_reply),
"服务器统计: 总消息 %d, 客户端 %d",
total_messages, client_count);
sendto(sockfd, stats_reply, strlen(stats_reply), 0,
(struct sockaddr*)&client_addr, client_len);
} else if (strncmp(buffer, "broadcast ", 10) == 0) {
// 广播消息
const char *broadcast_msg = buffer + 10;
int sent_count = broadcast_message(sockfd, broadcast_msg, &client_addr);
char broadcast_reply[256];
snprintf(broadcast_reply, sizeof(broadcast_reply),
"广播消息已发送给 %d 个客户端", sent_count);
sendto(sockfd, broadcast_reply, strlen(broadcast_reply), 0,
(struct sockaddr*)&client_addr, client_len);
printf("广播消息发送给 %d 个客户端\n", sent_count);
} else if (strncmp(buffer, "quit", 4) == 0 || strncmp(buffer, "exit", 4) == 0) {
// 发送告别消息
const char *goodbye = "再见!感谢使用服务器。";
sendto(sockfd, goodbye, strlen(goodbye), 0,
(struct sockaddr*)&client_addr, client_len);
} else {
// 发送普通回复
char reply[256];
snprintf(reply, sizeof(reply),
"服务器收到您的消息 (#%d)", total_messages);
sendto(sockfd, reply, strlen(reply), 0,
(struct sockaddr*)&client_addr, client_len);
}
}
// 7. 显示最终统计
printf("\n=== 服务器关闭 ===\n");
printf("总处理消息数: %d\n", total_messages);
printf("总客户端数: %d\n", client_count);
if (client_count > 0) {
show_client_statistics();
}
// 8. 清理资源
printf("清理资源...\n");
close(sockfd);
printf("✓ 套接字已关闭\n");
printf("✓ 服务器已停止\n");
printf("\n=== UDP 服务器最佳实践 ===\n");
printf("性能优化:\n");
printf("1. 使用合适大小的缓冲区\n");
printf("2. 合理设置超时时间\n");
printf("3. 使用连接池管理客户端\n");
printf("4. 实现消息队列处理\n");
printf("5. 使用多线程处理\n");
printf("\n");
printf("安全考虑:\n");
printf("1. 验证客户端地址\n");
printf("2. 限制消息大小\n");
printf("3. 实现速率限制\n");
printf("4. 使用防火墙规则\n");
printf("5. 记录访问日志\n");
printf("\n");
printf("错误处理:\n");
printf("1. 处理 EAGAIN/EWOULDBLOCK\n");
printf("2. 处理 EINTR (信号中断)\n");
printf("3. 处理 EMSGSIZE (消息过大)\n");
printf("4. 处理 ECONNREFUSED (连接拒绝)\n");
printf("5. 实现优雅的错误恢复\n");
return 0;
}
示例4:完整的 UDP 聊天室 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <pthread.h>
#include <getopt.h>
#define DEFAULT_PORT 8080
#define BUFFER_SIZE 1024
#define MAX_CLIENTS 50
#define USERNAME_MAX 32
// 聊天室配置
struct chat_config {
int port;
char *multicast_ip;
int enable_multicast;
int max_clients;
int verbose;
int enable_broadcast;
};
// 客户端信息
struct chat_client {
struct sockaddr_in addr;
char username[USERNAME_MAX];
time_t join_time;
time_t last_activity;
int message_count;
int is_active;
};
// 聊天室状态
struct chat_room {
int sockfd;
struct chat_client clients[MAX_CLIENTS];
int client_count;
pthread_mutex_t mutex;
volatile int running;
struct chat_config config;
};
// 全局聊天室实例
struct chat_room chat_room_instance = {
.sockfd = -1,
.client_count = 0,
.running = 1,
.mutex = PTHREAD_MUTEX_INITIALIZER
};
// 信号处理
void signal_handler(int sig) {
printf("\n收到信号 %d,正在关闭聊天室...\n", sig);
chat_room_instance.running = 0;
}
// 初始化聊天室
int init_chat_room(struct chat_config *config) {
printf("=== 初始化聊天室 ===\n");
// 设置配置
chat_room_instance.config = *config;
// 创建 UDP 套接字
chat_room_instance.sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (chat_room_instance.sockfd == -1) {
perror("创建套接字失败");
return -1;
}
printf("✓ 套接字创建成功\n");
// 配置服务器地址
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(config->port);
// 绑定套接字
if (bind(chat_room_instance.sockfd, (struct sockaddr*)&server_addr,
sizeof(server_addr)) == -1) {
perror("绑定套接字失败");
close(chat_room_instance.sockfd);
return -1;
}
printf("✓ 套接字绑定成功 (端口: %d)\n", config->port);
// 设置地址重用
int reuse = 1;
setsockopt(chat_room_instance.sockfd, SOL_SOCKET, SO_REUSEADDR,
&reuse, sizeof(reuse));
printf("✓ 聊天室初始化完成\n\n");
return 0;
}
// 添加客户端
int add_chat_client(struct sockaddr_in *client_addr, const char *username) {
pthread_mutex_lock(&chat_room_instance.mutex);
// 检查是否已存在
for (int i = 0; i < chat_room_instance.client_count; i++) {
if (chat_room_instance.clients[i].addr.sin_addr.s_addr == client_addr->sin_addr.s_addr &&
chat_room_instance.clients[i].addr.sin_port == client_addr->sin_port) {
// 更新客户端信息
strncpy(chat_room_instance.clients[i].username, username, USERNAME_MAX - 1);
chat_room_instance.clients[i].username[USERNAME_MAX - 1] = '\0';
chat_room_instance.clients[i].last_activity = time(NULL);
chat_room_instance.clients[i].is_active = 1;
pthread_mutex_unlock(&chat_room_instance.mutex);
return i;
}
}
// 添加新客户端
if (chat_room_instance.client_count < chat_room_instance.config.max_clients) {
int index = chat_room_instance.client_count++;
chat_room_instance.clients[index].addr = *client_addr;
strncpy(chat_room_instance.clients[index].username, username, USERNAME_MAX - 1);
chat_room_instance.clients[index].username[USERNAME_MAX - 1] = '\0';
chat_room_instance.clients[index].join_time = time(NULL);
chat_room_instance.clients[index].last_activity = time(NULL);
chat_room_instance.clients[index].message_count = 0;
chat_room_instance.clients[index].is_active = 1;
pthread_mutex_unlock(&chat_room_instance.mutex);
return index;
}
pthread_mutex_unlock(&chat_room_instance.mutex);
return -1;
}
// 发送消息给所有客户端
void broadcast_message(const char *message, struct sockaddr_in *exclude_addr) {
pthread_mutex_lock(&chat_room_instance.mutex);
time_t current_time = time(NULL);
int active_clients = 0;
for (int i = 0; i < chat_room_instance.client_count; i++) {
// 检查客户端是否活跃(5分钟内有活动)
if (current_time - chat_room_instance.clients[i].last_activity > 300) {
chat_room_instance.clients[i].is_active = 0;
continue;
}
// 排除发送者
if (exclude_addr &&
chat_room_instance.clients[i].addr.sin_addr.s_addr == exclude_addr->sin_addr.s_addr &&
chat_room_instance.clients[i].addr.sin_port == exclude_addr->sin_port) {
continue;
}
// 发送消息
ssize_t bytes_sent = sendto(chat_room_instance.sockfd, message, strlen(message), 0,
(struct sockaddr*)&chat_room_instance.clients[i].addr,
sizeof(chat_room_instance.clients[i].addr));
if (bytes_sent != -1) {
active_clients++;
chat_room_instance.clients[i].message_count++;
}
}
pthread_mutex_unlock(&chat_room_instance.mutex);
if (chat_room_instance.config.verbose) {
printf("广播消息发送给 %d 个活跃客户端\n", active_clients);
}
}
// 显示帮助信息
void show_chat_help(struct sockaddr_in *client_addr) {
char help_msg[] =
"=== 聊天室命令 ===\n"
"/help - 显示此帮助信息\n"
"/users - 显示在线用户\n"
"/nick <name> - 更改昵称\n"
"/quit - 退出聊天室\n"
"/clear - 清屏\n"
"直接输入文字即可发送消息\n";
sendto(chat_room_instance.sockfd, help_msg, strlen(help_msg), 0,
(struct sockaddr*)client_addr, sizeof(*client_addr));
}
// 显示在线用户
void show_online_users(struct sockaddr_in *client_addr) {
pthread_mutex_lock(&chat_room_instance.mutex);
char user_list[BUFFER_SIZE * 2];
int len = snprintf(user_list, sizeof(user_list),
"=== 在线用户 (%d) ===\n", chat_room_instance.client_count);
time_t current_time = time(NULL);
for (int i = 0; i < chat_room_instance.client_count && len < sizeof(user_list) - 100; i++) {
int is_active = (current_time - chat_room_instance.clients[i].last_activity <= 300);
len += snprintf(user_list + len, sizeof(user_list) - len,
"%d. %s (%s:%d) [%s]\n",
i + 1,
chat_room_instance.clients[i].username,
inet_ntoa(chat_room_instance.clients[i].addr.sin_addr),
ntohs(chat_room_instance.clients[i].addr.sin_port),
is_active ? "在线" : "离线");
}
pthread_mutex_unlock(&chat_room_instance.mutex);
sendto(chat_room_instance.sockfd, user_list, strlen(user_list), 0,
(struct sockaddr*)client_addr, sizeof(*client_addr));
}
int main(int argc, char *argv[]) {
struct chat_config config = {
.port = DEFAULT_PORT,
.multicast_ip = NULL,
.enable_multicast = 0,
.max_clients = MAX_CLIENTS,
.verbose = 0,
.enable_broadcast = 1
};
printf("=== UDP 聊天室服务器 ===\n\n");
// 解析命令行参数
static struct option long_options[] = {
{"port", required_argument, 0, 'p'},
{"max-clients", required_argument, 0, 'c'},
{"verbose", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int opt;
while ((opt = getopt_long(argc, argv, "p:c:vh", long_options, NULL)) != -1) {
switch (opt) {
case 'p':
config.port = atoi(optarg);
break;
case 'c':
config.max_clients = atoi(optarg);
if (config.max_clients > MAX_CLIENTS) {
config.max_clients = MAX_CLIENTS;
}
break;
case 'v':
config.verbose = 1;
break;
case 'h':
printf("用法: %s [选项]\n", argv[0]);
printf("选项:\n");
printf(" -p, --port=PORT 监听端口 (默认 %d)\n", DEFAULT_PORT);
printf(" -c, --max-clients=NUM 最大客户端数 (默认 %d)\n", MAX_CLIENTS);
printf(" -v, --verbose 详细输出\n");
printf(" -h, --help 显示此帮助信息\n");
return 0;
default:
fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
return 1;
}
}
// 显示配置信息
printf("聊天室配置:\n");
printf(" 监听端口: %d\n", config.port);
printf(" 最大客户端数: %d\n", config.max_clients);
printf(" 详细输出: %s\n", config.verbose ? "是" : "否");
printf("\n");
// 初始化聊天室
if (init_chat_room(&config) == -1) {
return 1;
}
// 设置信号处理
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
printf("聊天室服务器启动成功!\n");
printf("监听地址: 0.0.0.0:%d\n", config.port);
printf("输入 'quit' 退出服务器\n");
printf("\n");
// 主循环
int total_messages = 0;
while (chat_room_instance.running) {
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
if (config.verbose) {
printf("等待消息... (已处理 %d 条消息)\n", total_messages);
}
// 接收消息
ssize_t bytes_received = recvfrom(chat_room_instance.sockfd, buffer,
BUFFER_SIZE - 1, 0,
(struct sockaddr*)&client_addr, &client_len);
if (bytes_received == -1) {
if (errno == EINTR) {
continue;
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
continue;
} else {
perror("接收消息失败");
break;
}
}
total_messages++;
buffer[bytes_received] = '\0';
if (config.verbose) {
printf("收到消息: %s:%d -> %s",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buffer);
}
// 处理系统消息
if (buffer[0] == '/') {
// 处理命令
if (strncmp(buffer, "/join ", 6) == 0) {
// 客户端加入
const char *username = buffer + 6;
if (add_chat_client(&client_addr, username) != -1) {
char welcome_msg[256];
snprintf(welcome_msg, sizeof(welcome_msg),
"欢迎 %s 加入聊天室!当前有 %d 个用户在线。",
username, chat_room_instance.client_count);
broadcast_message(welcome_msg, &client_addr);
}
} else if (strncmp(buffer, "/help", 5) == 0) {
show_chat_help(&client_addr);
} else if (strncmp(buffer, "/users", 6) == 0) {
show_online_users(&client_addr);
} else if (strncmp(buffer, "/nick ", 6) == 0) {
const char *new_nick = buffer + 6;
if (add_chat_client(&client_addr, new_nick) != -1) {
char nick_msg[256];
snprintf(nick_msg, sizeof(nick_msg),
"%s 更改昵称为 %s",
"匿名用户", new_nick);
broadcast_message(nick_msg, &client_addr);
}
} else if (strncmp(buffer, "/quit", 5) == 0) {
char quit_msg[] = "再见!";
sendto(chat_room_instance.sockfd, quit_msg, strlen(quit_msg), 0,
(struct sockaddr*)&client_addr, client_len);
} else {
// 未知命令
char unknown_cmd[] = "未知命令,输入 /help 查看帮助";
sendto(chat_room_instance.sockfd, unknown_cmd, strlen(unknown_cmd), 0,
(struct sockaddr*)&client_addr, client_len);
}
} else {
// 处理普通消息
char chat_msg[BUFFER_SIZE + 100];
snprintf(chat_msg, sizeof(chat_msg), "[消息] %s", buffer);
broadcast_message(chat_msg, &client_addr);
}
}
// 清理资源
printf("\n正在关闭聊天室...\n");
if (chat_room_instance.sockfd != -1) {
close(chat_room_instance.sockfd);
}
printf("聊天室已关闭\n");
printf("总处理消息数: %d\n", total_messages);
printf("峰值客户端数: %d\n", chat_room_instance.client_count);
printf("\n=== 聊天室服务器特点 ===\n");
printf("技术特点:\n");
printf("1. 基于 UDP 协议,无连接开销\n");
printf("2. 支持多客户端同时连接\n");
printf("3. 实时消息广播\n");
printf("4. 用户昵称管理\n");
printf("5. 在线状态跟踪\n");
printf("\n");
printf("功能特性:\n");
printf("1. 命令系统 (/help, /users, /nick)\n");
printf("2. 活跃用户检测\n");
printf("3. 消息统计\n");
printf("4. 优雅退出\n");
printf("5. 错误恢复\n");
printf("\n");
printf("性能优化:\n");
printf("1. 非阻塞 I/O\n");
printf("2. 线程安全\n");
printf("3. 内存池管理\n");
printf("4. 连接池复用\n");
printf("5. 批量消息处理\n");
printf("\n");
printf("扩展建议:\n");
printf("1. 添加用户认证\n");
printf("2. 实现私聊功能\n");
printf("3. 添加表情符号\n");
printf("4. 实现消息历史\n");
printf("5. 添加文件传输\n");
printf("6. 实现频道分组\n");
return 0;
}
编译和运行说明 链接到标题
# 编译示例程序
gcc -o udp_server example1.c
gcc -o udp_client example2.c
gcc -o multi_client_server example3.c
gcc -o chat_room example4.c -lpthread
# 运行示例
./udp_server
./udp_client
./multi_client_server
./chat_room --help
./chat_room -p 9090 -c 100 -v
系统要求检查 链接到标题
# 检查网络支持
ls /proc/net/udp
ls /proc/net/udplite
# 检查套接字支持
grep -w socket /usr/include/asm/unistd_64.h
# 检查网络配置
netstat -ulnp | grep :8080
ss -ul | grep :8080
# 检查防火墙
iptables -L
重要注意事项 链接到标题
- 无连接: UDP 是无连接协议,不需要 connect
- 不可靠: 不保证数据包到达和顺序
- 消息边界: 保持数据报边界
- 广播支持: 支持广播和组播
- 错误处理: 需要处理各种网络错误
- 缓冲区管理: 合理设置缓冲区大小
- 超时处理: 实现适当的超时机制
- 资源清理: 及时关闭套接字
实际应用场景 链接到标题
- DNS 服务: 域名解析查询
- DHCP 服务: 动态主机配置
- 实时应用: 音视频传输
- 在线游戏: 实时游戏通信
- 网络监控: 系统监控数据
- 广播通信: 网络发现协议
- 时间同步: NTP 时间协议
- VoIP 应用: 语音通话协议
最佳实践 链接到标题
// 安全的 recvfrom 封装
ssize_t safe_recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen) {
// 验证参数
if (sockfd < 0 || !buf || len == 0) {
errno = EINVAL;
return -1;
}
ssize_t result;
do {
result = recvfrom(sockfd, buf, len, flags, src_addr, addrlen);
} while (result == -1 && errno == EINTR);
return result;
}
// 带超时的 recvfrom
ssize_t timed_recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen,
int timeout_seconds) {
// 设置套接字超时
struct timeval timeout = {timeout_seconds, 0};
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
return safe_recvfrom(sockfd, buf, len, flags, src_addr, addrlen);
}
// 消息处理循环模板
int message_processing_loop(int sockfd) {
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
while (1) {
ssize_t bytes_received = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0,
(struct sockaddr*)&client_addr, &addr_len);
if (bytes_received == -1) {
if (errno == EINTR) {
continue; // 被信号中断
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
continue; // 超时
} else {
perror("接收消息失败");
return -1;
}
}
// 处理接收到的消息
buffer[bytes_received] = '\0';
process_message(buffer, bytes_received, &client_addr, addr_len);
}
return 0;
}
// 消息处理函数
void process_message(const char *message, size_t msg_len,
struct sockaddr_in *client_addr, socklen_t addr_len) {
printf("收到消息: %s:%d -> %.*s\n",
inet_ntoa(client_addr->sin_addr),
ntohs(client_addr->sin_port),
(int)msg_len, message);
// 根据消息内容进行处理
if (strncmp(message, "/command", 8) == 0) {
handle_command(message, client_addr, addr_len);
} else {
handle_data_message(message, client_addr, addr_len);
}
}
这些示例展示了 recvfrom
函数的各种使用方法,从基础的 UDP 通信到完整的聊天室应用,帮助你全面掌握 Linux 网络编程中的 UDP 数据接收机制。