QTcpSocket / QTcpServer, two way communication
-
@KroMignon , by server I mean the application using QTcpServer that is in effect the master because it is the application that starts the other processes, listens for incoming messages and issues commands to the other processes.
The clients use QTcpSocket to connect to the server application. If the server goes away then the clients will self terminate. I don't know about the KeepAlive feature so I will read up, but basically the heartbeat is an alternative to pinging the server to see if it is active and responding. Can I self terminate if the server doesn't respond using the KeepAlive?
-
@KroMignon , by server I mean the application using QTcpServer that is in effect the master because it is the application that starts the other processes, listens for incoming messages and issues commands to the other processes.
The clients use QTcpSocket to connect to the server application. If the server goes away then the clients will self terminate. I don't know about the KeepAlive feature so I will read up, but basically the heartbeat is an alternative to pinging the server to see if it is active and responding. Can I self terminate if the server doesn't respond using the KeepAlive?
@SPlatten said in QTcpSocket / QTcpServer, two way communication:
Can I self terminate if the server doesn't respond using the KeepAlive?
TCP KeepAlive feature, send "non data" packet between two endpoints to check if counterpart is still present/reachable. If not, the connection is closed (cf. https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html).
To enable KeepAlive:
socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
But, there are always some backdrawn. With Qt, you can only switch on the KeepAlive feature for the socket (must be done when socket is connected, not before!).
According to your OS, you also have to setup the KeepAlive parameters:- IDLE interval before starting KeepAlive
- how many KeepAlive retries before deconnection
- interval between each KeepAlive
For example for Linux (maybe Unix?) system, the TCP settings can be found on
/proc/sys/net/ipv4
:- tcp_keepalive_time: interval between the last data packet sent and the first keepalive probe in seconds.
- tcp_keepalive_intvl: interval between subsequent keepalive probes in seconds.
- tcp_keepalive_probes: number of probes that are sent and unacknowledged before the client considers the connection broken and notifies the application layer
-
Whats the live cycle of a connection with QTcpSocket?
My client connects to the application which is listening, here is the client connection code:
setSocketOption(QAbstractSocket::LowDelayOption, 1); //Get the I/P address QList<QHostAddress> lstAddresses = QNetworkInterface::allAddresses(); QString strIP; for( int i=0; i<lstAddresses.size(); i++ ) { if ( lstAddresses[i] != QHostAddress::LocalHost && lstAddresses[i].toIPv4Address() ) { strIP = lstAddresses[i].toString(); break; } } if ( strIP.isEmpty() == true ) { strIP = QHostAddress(QHostAddress::LocalHost).toString(); } //Connect to the Application qdbg() << "Connecting to: " << strIP << ":" << muint16XMLMPAMport; connectToHost(strIP, muint16XMLMPAMport);
The port is 8124. This does connect to the server and sends a message with:
void clsSocketClient::sendJSON(const QJsonObject& crobjJSON) { if ( isOpen() != true ) { return; } //Associate this TCP socket with the output data stream QByteArray arybytMsg; QDataStream dsOut(&arybytMsg, QIODevice::WriteOnly); dsOut.setVersion(clsJSON::mscintQtVersion); //Send message to data stream dsOut << QJsonDocument(crobjJSON).toJson(QJsonDocument::Compact); //Write message #if defined(DEBUG_SOCKETS) qint64 int64Written = #endif write(arybytMsg); #if defined(DEBUG_SOCKETS) QJsonDocument objDoc(crobjJSON); qdbg() << "clsSocketClient::sendJSON" << "[" << int64Written << "]:" << QString(objDoc.toJson(QJsonDocument::Compact)); #endif }
This works and I can see that int64Written is > 0 in the Application Output. The server application receives this message and sends and Ack:
bool blnDecodeHeartbeat(const QJsonObject& crobjJSON) { QJsonObject::const_iterator citrFound = crobjJSON.find(clsJSON::mscszModule); if ( citrFound == crobjJSON.end() ) { return false; } QString strModuleName = citrFound.value().toString(); clsModule* pModule = clsModule::pGetModule(strModuleName); if ( pModule == nullptr ) { return false; } pModule->updateHearbeat(); //Send acknowledge back to module QJsonObject objJSON; objJSON.insert(clsJSON::mscszModule, strModuleName); objJSON.insert(clsJSON::mscszMsgType, clsJSON::mscszAck); //Cast the socket to the required type emit pModule->sendJSON(objJSON); return true; }
This decode is called the instance the message arrives from the client and the sending of the Ack message is always successful. The server later tries to send a message to the client which is not the result of receiving a message:
void clsScriptHelper::notify(const QJsonObject& crobjModule ,QJsonObject objCmds) { QJsonObject::const_iterator citrFound = crobjModule.find(clsJSON::mscszModule); QString strModuleName; clsModule* pModule; if ( citrFound == crobjModule.end() ) { return; } strModuleName = citrFound.value().toString(); //Make sure the module is set in the message objCmds.insert(clsJSON::mscszModule, strModuleName); //Set command type objCmds.insert(clsJSON::mscszMsgType, clsJSON::mscszCmdNotify); //Look up the socket for the module pModule = clsModule::pGetModule(strModuleName); if ( pModule == nullptr || pModule->blnTxAllowed() != true ) { clsModule::sendLater(strModuleName, objCmds); return; } //Convert object into byte array for transmission emit pModule->sendJSON(objCmds); }
The code to send Later:
void clsModule::sendLater(const QString& crstrModule, const QJsonObject& crobjJSON) { mpSendLater::iterator itrList = clsModule::msmpSendLaterLst.find(crstrModule); QJsonObject* pobjJSON; if ( itrList != clsModule::msmpSendLaterLst.end() ) { pobjJSON = itrList->second; } else { pobjJSON = new QJsonObject(); if ( pobjJSON != nullptr ) { clsModule::msmpSendLaterLst.insert(std::make_pair(crstrModule, pobjJSON)); } } if ( pobjJSON != nullptr ) { *pobjJSON = crobjJSON; #if defined(DEBUG_SOCKETS) QString strExpoded(clsJSON::strExplodeJSON(*pobjJSON, 0)); qdbg() << "clsModule::sendLater: " << strExpoded.toLatin1().data(); #endif } }
The purpose of this function is that if the client is not connected or not ready then it inserts the message into a map that is supposed to be transmitted when the client next connects.
When the client is connected the waiting messages are sent using the clsSocketClient::sendJSON as posted above, and this fails 100% of the time. Why?
Its as if the client isn't ready to receive a message. Typical output in Application Output:
D00000000000000000028T000000000915:clsJSON::commonDecode [50]: {"module":"mdFileIO","msgType":"init","port":8124} W00000000000000000029T000000001334:QProcess::setProgram: Process is already running D00000000000000000030T000000001334:Process: mdFileIO started, PID: 1046 D00000000000000000031T000000002816:clsJSON::commonDecode [49]: {"PID":"1046","module":"mdFileIO","msgType":"hb"} D00000000000000000032T000000002816:clsSocketClient::sendJSON[41]:{"module":"mdFileIO","msgType":"ack"} D00000000000000000033T000000002816:clsSocketClient::onBytesWritten:41 D00000000000000000034T000000004718:clsJSON::commonDecode [49]: {"PID":"1046","module":"mdFileIO","msgType":"hb"} D00000000000000000035T000000004718:clsSocketClient::sendJSON[41]:{"module":"mdFileIO","msgType":"ack"} D00000000000000000036T000000004718:clsSocketClient::onBytesWritten:41