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

重要注意事项 見出しへのリンク

  1. 无连接: UDP 是无连接协议,不需要 connect
  2. 不可靠: 不保证数据包到达和顺序
  3. 消息边界: 保持数据报边界
  4. 广播支持: 支持广播和组播
  5. 错误处理: 需要处理各种网络错误
  6. 缓冲区管理: 合理设置缓冲区大小
  7. 超时处理: 实现适当的超时机制
  8. 资源清理: 及时关闭套接字

实际应用场景 見出しへのリンク

  1. DNS 服务: 域名解析查询
  2. DHCP 服务: 动态主机配置
  3. 实时应用: 音视频传输
  4. 在线游戏: 实时游戏通信
  5. 网络监控: 系统监控数据
  6. 广播通信: 网络发现协议
  7. 时间同步: NTP 时间协议
  8. 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 数据接收机制。