最近使用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是0if (!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;}
目前算是徹底解決崩潰問題,后面繼續測試。