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

C raw UDP socket环回地址下recvfrom()无法接收发送数据问题

Raw UDP Socket 通信故障修复方案

你的代码存在raw socket使用逻辑错误、参数未初始化、协议字段填充错误三类核心问题,以下是具体修改点位和通用编码规范问题。

必须修改的核心点位

客户端(发送端)修复

你当前的核心问题是缓冲区结构和socket行为不匹配:你要求内核自动生成IP头,但却在缓冲区开头预留了IP头的空位,导致内核将你填充的0值区域识别为IP头,直接丢弃非法数据包。

  • 移除缓冲区开头预留的IP头空间:不设置IP_HDRINCL选项时,传给sendto()的缓冲区从第一个字节开始就应该是UDP头,不需要为内核生成的IP头预留位置。因此UDP头指针直接指向缓冲区起始地址,载荷指针紧跟UDP头之后。
  • 修正UDP头长度字段:udphdr->len为UDP头+UDP载荷的总长度,不是整个缓冲区的长度,你当前载荷为4字节,因此应赋值为htons(sizeof(struct udphdr) + 4),必须转网络字节序。
  • 移除硬编码的校验和:将check字段设为0即可,内核会自动计算并填充合法的UDP校验和,硬编码4会导致校验和非法,数据包被网络栈丢弃。

服务端(接收端)修复

你当前的收包逻辑存在参数未初始化、返回值未校验的致命错误,导致recvfrom()调用直接失败:

  • 初始化recvfrom()的长度参数:len是传入传出参数,调用前必须赋值为sizeof(struct sockaddr_in),未初始化时为栈上随机值,会导致recvfrom()直接返回错误。
  • 校验recvfrom()返回值:你设置了非阻塞socket,没有数据包到达时recvfrom()会立刻返回-1并置errno为EAGAIN,必须判断返回值大于0时再解析缓冲区内容,否则会读到全0的无效数据。如果不需要非阻塞逻辑,可以直接移除fcntl设置非阻塞的代码,用阻塞模式接收更简单。
  • 建议绑定环回地址:调用bind()将socket绑定到127.0.0.1,避免收到其他接口的无关流量,也能解决部分内核版本下环回流量不递交给未绑定raw socket的问题。

修复后参考代码

服务端代码

int main() {
    // 创建raw socket,需要root权限运行
    int sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
    if (sock == -1) {
        perror("socket create failed");
        return 1;
    }

    // 绑定环回地址,仅接收lo口UDP流量
    struct sockaddr_in bind_addr = {0};
    bind_addr.sin_family = AF_INET;
    bind_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (bind(sock, (struct sockaddr*)&bind_addr, sizeof(bind_addr)) == -1) {
        perror("bind failed");
        return 1;
    }

    // 若需要阻塞模式,删除以下fcntl相关代码即可
    int fcntl_ret = fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK);
    if (fcntl_ret == -1) {
        perror("fcntl set nonblock failed");
        return 1;
    }

    char packet[128] = {0};
    // raw socket收包时,缓冲区前sizeof(struct iphdr)为内核填充的IP头,之后是UDP头和载荷
    struct udphdr *header = (struct udphdr*)(packet + sizeof(struct iphdr));
    char *body = packet + sizeof(struct iphdr) + sizeof(struct udphdr);
    struct sockaddr_in peer_addr;
    socklen_t addr_len = sizeof(peer_addr); // 必须初始化长度
    ssize_t recv_bytes;

    while (1) {
        memset(packet, 0, sizeof(packet));
        recv_bytes = recvfrom(sock, packet, sizeof(packet), 0, (struct sockaddr*)&peer_addr, &addr_len);
        if (recv_bytes > 0) {
            printf("Received %zd bytes\nChecksum: %u\nBody: %s\n", 
                   recv_bytes, ntohs(header->check), body);
        }
        sleep(1);
    }

    close(sock);
    return 0;
}

客户端代码

int main() {
    // 创建raw socket,需要root权限运行
    int sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
    if (sock == -1) {
        perror("socket create failed");
        return -1;
    }

    struct sockaddr_in dest_addr = {0};
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    // 注意:自行构造UDP头时,sockaddr_in中的sin_port不生效,端口以UDP头中填充的为准

    char packet[128] = {0};
    // 不设置IP_HDRINCL时,缓冲区直接从UDP头开始,不需要预留IP头空间
    struct udphdr *header = (struct udphdr*)packet;
    char *body = packet + sizeof(struct udphdr);
    memcpy(body, "test", 4);

    // 填充UDP头
    header->source = htons(25252);
    header->dest = htons(80);
    header->len = htons(sizeof(struct udphdr) + 4); // UDP头+载荷总长度,转网络字节序
    header->check = 0; // 置0由内核自动计算校验和

    ssize_t send_bytes;
    while (1) {
        send_bytes = sendto(sock, packet, ntohs(header->len), 0, 
                            (struct sockaddr*)&dest_addr, sizeof(dest_addr));
        if (send_bytes == -1) {
            perror("send failed");
        } else {
            printf("Sent %zd bytes\n", send_bytes);
        }
        sleep(1);
    }

    close(sock);
    return 0;
}

通用编码问题

  • 缺少错误详情打印:所有系统调用失败时仅打印固定提示,没有通过perror()输出errno对应的具体错误原因,极大增加调试难度。
  • 未校验系统调用返回值:原代码中sendto()/recvfrom()返回失败时仍会执行后续打印、解析逻辑,导致无效输出和内存访问。
  • 协议字段字节序错误:原代码中UDP长度字段直接赋值主机序数值,没有通过htons()转为网络字节序,导致协议解析错误。
  • 资源未释放:socket使用完后没有调用close()释放,虽然进程退出时内核会回收,但属于不规范写法。
  • 硬编码长度值:原代码中多处直接写128作为缓冲区长度,没有用sizeof()计算,后续修改缓冲区大小时容易出现长度不匹配的问题。
  • 缺少权限提示:raw socket创建需要root权限,原代码没有相关提示,普通用户运行时会直接创建socket失败但无法快速定位原因。

注意:两个程序都必须以root权限运行,否则raw socket创建、收发都会失败。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 01:48:16