C语言UDP接收未知节点数据包及存储地址的实现求教
嘿,这个场景我之前做过类似的UDP节点发现与通信需求,给你捋捋具体的实现思路和关键代码片段,应该能帮到你:
核心要点:利用UDP的无连接特性获取发送方地址
你已经实现了广播发送,接收端的关键就是用recvfrom()函数——它不仅能拿到数据包内容,还能同时获取发送方的完整网络地址(struct sockaddr_in),这正是你后续直接发送的核心依据。
节点存储方案:动态结构体数组+线程安全保护
因为节点数量未知,静态数组肯定不够用,推荐用动态扩容的结构体数组(或者链表,数组遍历更方便)来存储每个节点的配置和地址信息。同时因为多个线程(广播、接收、发送、清理)都会访问这个列表,必须加互斥锁保证线程安全。
第一步:定义节点结构体
先把每个节点需要存储的信息封装起来,比如唯一ID、配置参数、网络地址、最后活跃时间(用于清理下线节点):
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> #include <time.h> // 节点配置与地址结构体 typedef struct { char node_id[32]; // 节点唯一标识(比如设备ID) int config_param; // 示例配置参数(根据你的需求自定义) struct sockaddr_in addr; // 节点的UDP地址(IP+端口) time_t last_active; // 最后活跃时间(用于超时清理) } NodeInfo; // 全局节点列表+互斥锁+节点计数 NodeInfo *node_list = NULL; int node_count = 0; pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER;
第二步:接收线程的实现
单独开一个线程监听广播端口,每次用recvfrom()拿到数据和发送方地址,然后检查这个节点是否已经在列表里:
- 如果存在:更新它的配置和最后活跃时间
- 如果不存在:扩容数组,新增节点
void *recv_broadcast_thread(void *arg) { int sockfd = *(int *)arg; struct sockaddr_in sender_addr; socklen_t addr_len = sizeof(sender_addr); char recv_buf[1024]; ssize_t recv_len; while (1) { // 接收广播数据包,同时获取发送方地址 recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf)-1, 0, (struct sockaddr *)&sender_addr, &addr_len); if (recv_len < 0) { perror("recvfrom failed"); continue; } recv_buf[recv_len] = '\0'; // 解析数据包里的节点配置(这里假设数据包格式为"node_id,config_value") NodeInfo new_node; memcpy(&new_node.addr, &sender_addr, sizeof(sender_addr)); char *delim = strchr(recv_buf, ','); if (delim != NULL) { *delim = '\0'; strncpy(new_node.node_id, recv_buf, sizeof(new_node.node_id)-1); new_node.config_param = atoi(delim + 1); } new_node.last_active = time(NULL); // 线程安全地更新节点列表 pthread_mutex_lock(&list_mutex); // 检查节点是否已存在(通过IP+端口判断唯一性) int exists = 0; for (int i = 0; i < node_count; i++) { if (node_list[i].addr.sin_addr.s_addr == sender_addr.sin_addr.s_addr && node_list[i].addr.sin_port == sender_addr.sin_port) { // 更新现有节点的配置和活跃时间 memcpy(&node_list[i], &new_node, sizeof(NodeInfo)); exists = 1; break; } } if (!exists) { // 动态扩容节点列表 NodeInfo *temp = realloc(node_list, (node_count + 1) * sizeof(NodeInfo)); if (temp == NULL) { perror("realloc failed"); pthread_mutex_unlock(&list_mutex); continue; } node_list = temp; node_list[node_count] = new_node; node_count++; printf("New node added: %s (%s:%d)\n", new_node.node_id, inet_ntoa(new_node.addr.sin_addr), ntohs(new_node.addr.sin_port)); } pthread_mutex_unlock(&list_mutex); } return NULL; }
第三步:向指定节点直接发送数据
当需要给某个节点发消息时,从列表里根据node_id找到对应的struct sockaddr_in,然后用sendto()直接发送即可:
int send_to_target_node(const char *target_id, const char *data, int data_len) { pthread_mutex_lock(&list_mutex); // 查找目标节点 int target_idx = -1; for (int i = 0; i < node_count; i++) { if (strcmp(node_list[i].node_id, target_id) == 0) { target_idx = i; break; } } if (target_idx == -1) { pthread_mutex_unlock(&list_mutex); fprintf(stderr, "Node %s not found in list\n", target_id); return -1; } // 复用广播socket或者新建一个(这里示例新建) int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket create failed"); pthread_mutex_unlock(&list_mutex); return -1; } // 发送数据到目标节点 ssize_t send_len = sendto(sockfd, data, data_len, 0, (struct sockaddr *)&node_list[target_idx].addr, sizeof(node_list[target_idx].addr)); close(sockfd); pthread_mutex_unlock(&list_mutex); if (send_len < 0) { perror("sendto failed"); return -1; } printf("Sent data to %s successfully\n", target_id); return 0; }
重要补充:节点超时清理
网络中难免有节点下线,所以需要定期清理超过一定时间没有活跃的节点,避免内存泄漏:
void *cleanup_inactive_thread(void *arg) { while (1) { sleep(5); // 每5秒检查一次 pthread_mutex_lock(&list_mutex); int new_count = 0; // 筛选出活跃节点(这里设超时时间为10秒) for (int i = 0; i < node_count; i++) { if (time(NULL) - node_list[i].last_active <= 10) { node_list[new_count++] = node_list[i]; } else { printf("Node %s timed out, removed from list\n", node_list[i].node_id); } } // 重新分配内存(如果没有活跃节点,释放内存) NodeInfo *temp = realloc(node_list, new_count * sizeof(NodeInfo)); if (temp != NULL || new_count == 0) { node_list = temp; node_count = new_count; } pthread_mutex_unlock(&list_mutex); } return NULL; }
注意事项
- 线程安全:所有访问
node_list的操作都必须加互斥锁,否则会出现数据竞争导致的诡异问题 - 数据包格式:你需要自定义广播的数据包格式(比如用分隔符、JSON或者protobuf),确保能正确解析出节点ID和配置参数
- 端口与地址:广播时要绑定到正确的端口,并且设置
SO_BROADCAST选项(你已经实现广播,应该已经处理了) - 内存管理:程序退出时记得释放
node_list的内存,销毁互斥锁
内容的提问来源于stack exchange,提问作者fugasjunior
相关产品推荐
相关产品推荐

