You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

注意事项

  1. 线程安全:所有访问node_list的操作都必须加互斥锁,否则会出现数据竞争导致的诡异问题
  2. 数据包格式:你需要自定义广播的数据包格式(比如用分隔符、JSON或者protobuf),确保能正确解析出节点ID和配置参数
  3. 端口与地址:广播时要绑定到正确的端口,并且设置SO_BROADCAST选项(你已经实现广播,应该已经处理了)
  4. 内存管理:程序退出时记得释放node_list的内存,销毁互斥锁

内容的提问来源于stack exchange,提问作者fugasjunior

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:41:24