以太网原始套接字生产者消费者模型数据包丢失问题咨询
接收器丢包问题分析与修复方案
核心问题拆解
你的怀疑部分成立,但代码里还有几个更致命的问题,直接导致丢包或数据损坏:
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
相关产品推荐
相关产品推荐

