How to handle multiple socket connections the "right" way?
-
Consider the following code snippet:
for(auto i = go_command_target_hosts.cbegin(); i != go_command_target_hosts.cend(); ++i) { QString hostname = *i; TcpConnection* connection = new TcpConnection(); connection->connectToHost(hostname, LISTEN_PORT); if(connection->waitForConnected()) { connection->sendCommand(go_command); } connection->deleteLater(); }
According to the QAbstractSocket Documentation:
Note: This function may fail randomly on Windows. Consider using the event loop and the connected() signal if your software will run on Windows.
The problem is, I have multiple connections to make, and send a command to. How would I manage sending the command to multiple remote hosts and deal with possible connections failures using the suggested signal/slot approach? There's no provision for attaching a payload to the connected() signal, nor any obvious way of identifying which connections succeeded/failed. Also, there could be different reasons for making the connections with different actions to be performed once the connection is made. Sending a "GO" command is only one of those possible actions.
I'm assuming there's a better way to do this?
NOTE: TcpConnection is a subclassed QTcpSocket
-
The right way is to make it work asynchronously and do not use the
wait*
methods. If you absolutely need synchronous processing, replaceconnection->connectToHost(hostname, LISTEN_PORT); if(connection->waitForConnected()){
with:
bool connected=false; QEventLoop blocker; QObject::connect(connection,&TcpConnection::connected,[&connected]()->void{connected=true;}); QObject::connect(connection,&TcpConnection::connected,blocker,&QEventLoop::quit); QTimer::singleShot(30000,blocker,&QEventLoop::quit); //timeout connection->connectToHost(hostname, LISTEN_PORT); blocker.exec(); if(connected){
-
Consider the following code snippet:
for(auto i = go_command_target_hosts.cbegin(); i != go_command_target_hosts.cend(); ++i) { QString hostname = *i; TcpConnection* connection = new TcpConnection(); connection->connectToHost(hostname, LISTEN_PORT); if(connection->waitForConnected()) { connection->sendCommand(go_command); } connection->deleteLater(); }
According to the QAbstractSocket Documentation:
Note: This function may fail randomly on Windows. Consider using the event loop and the connected() signal if your software will run on Windows.
The problem is, I have multiple connections to make, and send a command to. How would I manage sending the command to multiple remote hosts and deal with possible connections failures using the suggested signal/slot approach? There's no provision for attaching a payload to the connected() signal, nor any obvious way of identifying which connections succeeded/failed. Also, there could be different reasons for making the connections with different actions to be performed once the connection is made. Sending a "GO" command is only one of those possible actions.
I'm assuming there's a better way to do this?
NOTE: TcpConnection is a subclassed QTcpSocket
I don't have the time to go into the details just now, but don't subclass the socket class. Make your own "session" object and "tie" it to the socket via signal-slots. Take a peek here for an example (it's a server but it goes pretty much the same way on the client side, you can ignore the threading).
-
The right way is to make it work asynchronously and do not use the
wait*
methods. If you absolutely need synchronous processing, replaceconnection->connectToHost(hostname, LISTEN_PORT); if(connection->waitForConnected()){
with:
bool connected=false; QEventLoop blocker; QObject::connect(connection,&TcpConnection::connected,[&connected]()->void{connected=true;}); QObject::connect(connection,&TcpConnection::connected,blocker,&QEventLoop::quit); QTimer::singleShot(30000,blocker,&QEventLoop::quit); //timeout connection->connectToHost(hostname, LISTEN_PORT); blocker.exec(); if(connected){
@VRonin You are correct.. TcpConnection is a subclassed QTcpSocket. I don't need synchronous processing, though in this case it simplifies things. What I am more looking for is a strategy for doing what I want the correct way. I use the signal/slot approach all the time, but its usually for less exotic purposes.
In this case, I need to connect to several hosts, handling success/failure of the connection, as well as sending a command to the connections. The problem with the latter is that the reason for the connection can change. In one case I want to send a "GO" command, and in another, I want to send a "checkinstall" command, and in yet another, I want to send a "killall" command. When I receive the connected() signal, I don't have a payload attached and its now disassociated from the reason I was trying to make the connection. I could have a class attribute which has a "reason code", I suppose, and preserve any data that would need to be sent. In the former case, If the connection succeeds or fails, I need to know more about which connection object is generating the event, ideally, the IP address I attempted to connect to.
Considering all of the external bookkeeping that is needed to get this to do what I want, it seems to smack of a bad design on my part and that there must be a cleaner way to do it.
-
I don't have the time to go into the details just now, but don't subclass the socket class. Make your own "session" object and "tie" it to the socket via signal-slots. Take a peek here for an example (it's a server but it goes pretty much the same way on the client side, you can ignore the threading).
@kshegunov It was not my decision to subclass the socket, but in this case, its not the root cause of my issue anyway. I will take a look at your suggestion
-
The asynchronous way would be something like:
replaceconnection->connectToHost(hostname, LISTEN_PORT); if(connection->waitForConnected()) { connection->sendCommand(go_command); } connection->deleteLater();
with
QObject::connect(connection,&TcpConnection::connected,[connection,&go_command]()->void{ connection->sendCommand(go_command); }); QObject::connect(connection,&TcpConnection::connected,connection,&TcpConnection::deleteLater); connection->connectToHost(hostname, LISTEN_PORT);
Btw, if I understand correctly, are you creating a new connection every time you have to send something to the server?
-
The asynchronous way would be something like:
replaceconnection->connectToHost(hostname, LISTEN_PORT); if(connection->waitForConnected()) { connection->sendCommand(go_command); } connection->deleteLater();
with
QObject::connect(connection,&TcpConnection::connected,[connection,&go_command]()->void{ connection->sendCommand(go_command); }); QObject::connect(connection,&TcpConnection::connected,connection,&TcpConnection::deleteLater); connection->connectToHost(hostname, LISTEN_PORT);
Btw, if I understand correctly, are you creating a new connection every time you have to send something to the server?
@VRonin yes, I am creating the connection and closing it each time. Its a bit complicated to explain. Our application consists of several components that can be distributed to several different hosts. Each of the hosting platforms has a daemon process running which is used to manage the application components its responsible for. In a nutshell, here is the background on the most common commands:
GO - sent by a remote host. It tells this one of the daemon process to communicate with all the other daemon processes it knows about to tell them to bootstrap the application components each daemon is responsible for. Once that is done, communication ends and there is no further need to communicate among the daemons
checkinstall - after the system is newly installed a remote host tells one of the daemon processes to validate the install across the entire system. It then sends the command to all other daemons it knows of and gathers information to determine if the installation was successful. Once done, no further communication is needed among the daemons
killall - if one of the application components in the system stop responding, a remote host tells one of the daemon processes to kill the system. Again, the daemon process tell the others to do the same with its controlled application components. Once done, no further communications are necessary.
The only user interface to the remote components is an iPad-based GUI. The other components are all hardwired into the aircraft cockpit with no user interface available.
I was not aware of the format of the signal/slot convention you posted.