基于QSerialPort的Qt5 Modbus主站程序接收数据丢失原因排查
排查Qt Modbus主站接收消息丢失问题
看起来你的Modbus主站状态机逻辑有几个关键漏洞,这很可能就是导致20%丢包率的原因。结合你提供的代码和调试输出,我帮你梳理几个核心问题和修复方案:
1. WaitForHead状态的两个致命错误
在serial_read的WaitForHead分支里,有两个直接导致丢包的问题:
case CommMaster::WaitForHead:{ int headIdx=read.indexOf(expectReceiveHead); if(headIdx>=0){//head found int length=read.length()-headIdx; if(length>=expectReceiveLength){ handleReceive((read.data(),expectReceiveLength)); // 语法错误! receive.clear(); comm.mainState=CommMaster::Idle; }else{ receive=read.mid(headIdx); // 漏切换到WaitForComplete状态! } } break; }
- 语法错误:
(read.data(),expectReceiveLength)是逗号表达式,实际只会把expectReceiveLength传给handleReceive,完全没传递正确的接收数据。应该改成handleReceive(read.mid(headIdx, expectReceiveLength).data(), expectReceiveLength)。 - 状态切换遗漏:当找到头部但数据长度不足时,你把剩余数据存入了
receive,但没有切换subState到WaitForComplete。这导致后续触发readyRead时,程序仍然会进入WaitForHead状态,从头查找头部,而不是追加数据补全剩余长度,直接丢失了之前保存的部分数据。
2. 超时处理的缓冲区读取顺序错误
在timer_timeout的超时分支里,你先读取了串口缓冲区的数据,再打印receive,这会导致receive里的部分数据和串口缓冲区的数据被拆分打印,甚至丢失:
QByteArray readAll=serial->readAll(); qDebug()<<"Receive timeout, receive="<<receive<<" serial->readAll()="<<readAll;
正确的做法应该是先把串口缓冲区的数据追加到receive,再统一打印,避免数据拆分:
receive.append(serial->readAll()); qDebug()<<"Receive timeout, receive buffer content:"<<receive; receive.clear(); comm.mainState=CommMaster::Idle; comm.subState=CommMaster::WaitForHead; // 重置子状态,避免下次等待出错
3. 非等待状态下的串口数据积压
当前serial_read只有在WaitForReceive状态下才会处理数据,如果在Idle状态下串口有残留数据(比如从站延迟响应的旧帧),这些数据会留在串口缓冲区里,下次进入WaitForReceive时可能干扰新的接收,甚至直接覆盖新数据。
建议在serial_read开头增加缓冲区清理逻辑:
void MainWindow::serial_read() { QByteArray read = serial->readAll(); if(comm.mainState != CommMaster::WaitForReceive){ qDebug()<<"Discard stale data in non-wait state:"<<read; return; } // 后续状态机逻辑... }
4. WaitForComplete状态的冗余计算
这个问题虽然不致命,但可以让代码更严谨:
int length=receive.length()+read.length(); receive.append(read); if(length>=expectReceiveLength){
可以简化为直接判断追加后的长度:
receive.append(read); if(receive.length()>=expectReceiveLength){
修复后的核心代码片段
修正后的serial_read
void MainWindow::serial_read() { QByteArray read = serial->readAll(); if(comm.mainState != CommMaster::WaitForReceive){ qDebug()<<"Discard stale data in non-wait state:"<<read; return; } switch(comm.subState){ case CommMaster::WaitForHead:{ int headIdx = read.indexOf(expectReceiveHead); if(headIdx >= 0){ QByteArray remaining = read.mid(headIdx); if(remaining.length() >= expectReceiveLength){ handleReceive(remaining.left(expectReceiveLength).data(), expectReceiveLength); receive.clear(); comm.mainState = CommMaster::Idle; comm.subState = CommMaster::WaitForHead; }else{ receive = remaining; comm.subState = CommMaster::WaitForComplete; // 关键:切换子状态 } } break; } case CommMaster::WaitForComplete:{ receive.append(read); if(receive.length() >= expectReceiveLength){ handleReceive(receive.left(expectReceiveLength).data(), expectReceiveLength); receive.clear(); comm.mainState = CommMaster::Idle; comm.subState = CommMaster::WaitForHead; } break; } default: qDebug()<<"Wrong sub state:"<<comm.subState; } }
修正后的超时处理
case CommMaster::WaitForReceive: if(passedTime >= kReceiveTimeout){ receive.append(serial->readAll()); qDebug()<<"Receive timeout, receive buffer content:"<<receive; comm.mainState = CommMaster::Idle; comm.subState = CommMaster::WaitForHead; receive.clear(); } break;
这些修复应该能解决你遇到的接收丢包问题,尤其是状态切换遗漏这个核心错误,直接导致了部分帧无法被正确拼接处理。
内容的提问来源于stack exchange,提问作者Jackoo
相关产品推荐
相关产品推荐

