QModbusTCPClient 服务器断开引起的程序崩溃
最近使用QModbusTCPClient 与一套设备通信,有一个QTimer频繁的通过读取设备寄存器。程序运行良好,但是有个问题:正常进行中设备断电了,整个程序都会崩溃。解决过程如下:
1.失败方案一
在QModbusTCPClient的errorOccurred()信号中判断错误后及时关闭QTimer,避免出错之后还要频繁访问。
此方案失败,问题不在这里。
2.失败方案二
m_reply= m_modbus->sendReadRequest(m_unit[1], m_outputID);
if(m_reply && !m_reply->isFinished())
{
connect(m_reply, &QModbusReply::finished, [this]() {
if(m_reply->error() == QModbusDevice::NoError)
{
m_outputs = m_reply->result().values();
}
m_modbus->disconnect(SIGNAL(timeoutChanged(int)), 0, 0);
delete m_reply;
m_reply = nullptr;
});
}
一个典型的应用如上。对 QModbusTCPClient发送读写请求后,会得到一个QModbusReply指针,根据QModbusReply的finished信号判断请求结果。这个过程是异步的,所以上面的及时停止QTimer并不能真的“及时”停止。
在finished的响应槽函数中判断一下error状态,再进行后面的操作,仍然失败。
3.方案三(有点眉目)
m_reply= m_modbus->sendReadRequest(m_unit[1], m_outputID);
if(m_reply && !m_reply->isFinished())
{
connect(m_reply, &QModbusReply::finished, [this]() {
});
}
直接把这个函数体变成空的,什么也不做,发现程序不崩溃了。问题范围成功缩小。于是对函数体中的逐行打印,看看到底哪一步崩溃的。
m_reply= m_modbus->sendReadRequest(m_unit[1], m_outputID);
if(m_reply && !m_reply->isFinished())
{
connect(m_reply, &QModbusReply::finished, [this]() {
qDebug()<<1;
if(m_reply->error() == QModbusDevice::NoError)
{
qDebug()<<2;
m_outputs = m_reply->result().values();
}
qDebug()<<3;
m_modbus->disconnect(SIGNAL(timeoutChanged(int)), 0, 0);
qDebug()<<4;
delete m_reply;
qDebug()<<5;
m_reply = nullptr;
});
}
结果发现只要对m_reply进行访问,就会崩溃。
4.方案四(部分解决)
在reply的finished信号响应函数中为啥不能访问reply呢,打印一下reply看看啥情况。
m_reply= m_modbus->sendReadRequest(m_unit[1], m_outputID);
if(m_reply && !m_reply->isFinished())
{
connect(m_reply, &QModbusReply::finished, [this]() {
qDebug()<<m_reply;
});
}
一旦QModbus设备断电,reply竟然是空值!!!!
QModbusReply(0x232d1d5de40)
QModbusReply(0x232d1d5f3b0)
QModbusReply(0x232d1d604e0)
QModbusReply(0x232d1d60f00)
QModbusReply(0x232d1d630e0)
"TCP socket error (The remote host closed the connection)."
QModbusDevice::UnconnectedState
QObject(0x0)
QObject(0x0)
QObject(0x0)
QObject(0x0)
QObject(0x0)
远程服务器关闭之后,reply的响应函数中访问reply竟然是空值!!!
所以在响应函数中还要判断reply是都为空值,才能继续:
m_reply= m_modbus->sendReadRequest(m_unit[1], m_outputID);
if(m_reply && !m_reply->isFinished())
{
connect(m_reply, &QModbusReply::finished, [this]() {
if (!m_reply[0]) {
return ;
}
if(m_reply->error() == QModbusDevice::NoError)
{
m_outputs = m_reply->result().values();
}
m_modbus->disconnect(SIGNAL(timeoutChanged(int)), 0, 0);
delete m_reply;
m_reply = nullptr;
});
}
这样处理后程序终于正常了,但是又出现了另一个问题。
5.方案五(完整解决)
上述方案中使用deleter m_reply竟然也有问题。当本来通信超时的时候(比如传入的错误的通信地址),响应会比较慢。此时服务器断开连接,reply竟然不是nullptr,此时程序在delete reply这句崩了。怀疑此时的reply还在异步处理别的事情。改成reply->deleterLater()之后就没问题了。
还有一个隐藏的问题,m_modbus->disconnect(SIGNAL(timeoutChanged(int)), 0, 0) 这句是为了解决内存增加问题,如果服务器中断导致reply==nullptr,这句话就被跳过了。可以把这句放在函数体最前面,并没有导致问题。
完整解决后如下:
m_reply = m_modbus->sendReadRequest(read, m_485ID);
if(m_reply && !m_reply->isFinished())
{
connect(m_reply, &QModbusReply::finished, [this]() {
m_modbus->disconnect(SIGNAL(timeoutChanged(int)), 0, 0);
//如果远程服务器关闭,这个reply是0
if (!m_reply) {
return ;
}
if(m_reply->error() == QModbusDevice::NoError)
{
QVector<quint16>values = m_reply->result().values();
if(m_values != values) {
m_values = values;
}
}
//如果超时错误,下面不能直接delete,否则服务中断仍然崩溃
m_reply->deleteLater();
m_reply = nullptr;
});
}
else {
m_modbus->disconnect(SIGNAL(timeoutChanged(int)), 0, 0);
m_reply->deleteLater();
m_reply = nullptr;
}
目前算是彻底解决崩溃问题,后面继续测试。