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

以太网原始套接字生产者消费者模型数据包丢失问题咨询

接收器丢包问题分析与修复方案

核心问题拆解

你的怀疑部分成立,但代码里还有几个更致命的问题,直接导致丢包或数据损坏:

1. 语法错误搞乱了接收逻辑

代码里的do语句没配while()条件,这是语法错误——编译器会报错,就算勉强运行,recvfrom只会执行一次,遇到信号中断这类临时失败的情况没法重试,直接就报错退出,平白丢包。

2. 没加锁就判断缓冲区状态,导致冲突

你在没加锁的时候调用buffer->isFull(),多个生产者线程同时跑的时候会出问题:

  • 线程A看缓冲区没满,准备收数据;
  • 线程B同时也看缓冲区没满,收完数据直接把缓冲区填满了;
  • 线程A收完数据再去塞,缓冲区已经满了,这次的数据直接丢了。

3. 缓冲区满就停收,内核直接丢包

当你的环形缓冲区满了,代码直接跳过recvfrom,这时候内核的套接字缓冲区会一直攒新数据包,攒满之后:

  • UDP的话,新包直接被内核扔了,没地方存;
  • TCP的话,会通知发送方停发,时间久了就超时丢包。

4. 重复用一块缓冲区,数据被覆盖

你只分配了一块rcvData内存,每次收包都覆盖它:

  • 把指针塞到环形缓冲区后,下一次收包会把这块内存里的旧数据冲掉;
  • 消费者拿到指针的时候,数据已经不是原来的了,相当于白收了。

5. 没存数据长度,消费者没法正确处理

recvfrom返回的rcvDataSize是实际收了多少字节,但你没把这个长度和指针一起存到缓冲区里,消费者不知道该读多少,要么读少了要么读多了,肯定出问题。


修复方案

1. 先把语法错误改了,处理接收中断

给do补上while条件,处理信号中断导致的接收失败:

do {
    rcvDataSize = recvfrom(sockfd, rcvData, RCV_BUFF_SIZE, 0, (struct sockaddr *)&saddr, (socklen_t *)&saddr_len);
    // 被信号中断的话重试接收
} while (rcvDataSize < 0 && errno == EINTR);

// 再处理真的错误
if (rcvDataSize <= sizeof(struct ethhdr)) {
    std::cerr << "RECEIVER: Error in reading recvfrom() function..." << endl;
    free(rcvData);
    return -1;
}

2. 加锁后再操作缓冲区,用条件变量等空闲

别在锁外判断缓冲区状态,改用条件变量等缓冲区有空位,而且不管缓冲区满不满,先把内核的数据收下来,避免内核丢包:

// 先定义条件变量(要和bufferMutex对应,全局或类成员)
std::condition_variable bufferNotFull;
std::condition_variable bufferNotEmpty;

while (1) {
    // 先收数据,不管缓冲区满不满,先把内核的包拿出来
    do {
        rcvDataSize = recvfrom(sockfd, rcvData, RCV_BUFF_SIZE, 0, (struct sockaddr *)&saddr, (socklen_t *)&saddr_len);
    } while (rcvDataSize < 0 && errno == EINTR);

    if (rcvDataSize <= sizeof(struct ethhdr)) {
        std::cerr << "RECEIVER: Error in reading recvfrom() function..." << endl;
        free(rcvData);
        return -1;
    }

    // 给当前包单独分配内存,避免被下一次接收覆盖
    unsigned char *packet = (unsigned char *)malloc(rcvDataSize);
    memcpy(packet, rcvData, rcvDataSize);

    // 加锁操作环形缓冲区
    std::unique_lock<std::mutex> lock(bufferMutex);
    // 缓冲区满的话,等消费者腾出空间
    while (buffer->isFull()) {
        bufferNotFull.wait(lock);
    }
    // 把包和长度一起存进去(要改RingBuffer的push接口,支持存长度)
    buffer->push(packet, rcvDataSize);
    // 通知消费者有新包了
    bufferNotEmpty.notify_one();
    lock.unlock();
}

3. 改环形缓冲区的接口

让RingBuffer能存数据包的指针和长度,或者直接存数据拷贝:

  • 存指针的话,消费者处理完要记得释放内存;
  • 存拷贝的话,缓冲区内部自己管理内存,别漏了释放。

4. 别忘释放内存

原代码里没释放rcvData,退出的时候要加free(rcvData),不然会内存泄漏。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 23:54:58