I have been having nightmares while debugging the next few lines of code, there must be something hidden beyond what I am seeing.
Here's a connection between a client and a server
QByteArray Client::request(QByteArray cmd)
{
//DEBUG << "Command: " << cmd.toStdString().c_str();
QTcpSocket *socket = new QTcpSocket(this);
socket->connectToHost(hostAddress, port, QIODevice::ReadWrite);
if (socket->waitForConnected(TIMEOUT))
{
socket->write(cmd);
DEBUG << "Size of bytes written :" << sizeof (cmd);
}
else DEBUG << "Couldn't connect to socket";
if (socket->waitForBytesWritten(TIMEOUT)) DEBUG << "Command sent";
else DEBUG << "Couldn't write to socket";
if (socket->waitForReadyRead(TIMEOUT))
{
QByteArray data = socket->readAll();
socket->close();
return data;
}
else DEBUG << "No reply from Server";
return QByteArray();
}
That's basically it for the client, for the server, here are the snippets that matter.
class ConnectionHandler : public QTcpServer
{
Q_OBJECT
public:
explicit ConnectionHandler(QObject *parent = 0);
void write(QByteArray);
protected:
void ConnectionHandler::incomingConnection(qintptr descriptor)
{
DEBUG << "ConnectionHandler:" << "Incoming Connection :" << descriptor;
ConnectionThread *thread = new ConnectionThread(this, descriptor);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
connect(thread, &ConnectionThread::signalIncomingMessage, this, &ConnectionHandler::slotIncomingMessage);
connect(this, &ConnectionHandler::signalOutgoingMessage, thread, &ConnectionThread::slotOutgoingMessage);
thread->start();
}
public slots:
void slotIncomingMessage(QByteArray);
void slotListen(bool checked){
if (checked)
{
if (!this->listen(QHostAddress::LocalHost, PORT_NUMBER)) {
DEBUG << "ConnectionHandler:" << "Could not start the server!";
} else {
DEBUG << "ConnectionHandler:" << "Listening...";
}
} else {
this->close();
DEBUG << "ConnectionHandler:" << "Connection Closed!";
}
}
signals:
void signalOutgoingMessage(QByteArray);
};
class ConnectionThread : public QThread
{
Q_OBJECT
public:
ConnectionThread(QObject* parent = 0, qintptr descriptor = -1){
if (descriptor != -1)
{
socket = new QTcpSocket();
DEBUG << "ConnectionThread: Connecting to socket number" << descriptor;
if (!socket->setSocketDescriptor(descriptor))
{
DEBUG << "ConnectionThread: Connection failed.";
DEBUG << socket->errorString();
}
else
{
DEBUG << "ConnectionThread: Connected Successfully.";
connect(socket, &QAbstractSocket::disconnected, this, &ConnectionThread::slotSocketDisconnected);
}
}
else {
DEBUG << "ConnectionThread: Please provide a descriptor for the connection thread";
}
}
void run() override{
if (socket->state() != QAbstractSocket::ConnectedState)
{
DEBUG << "ConnectionThread: Socket is not connected!";
DEBUG << "ConnectionThread: Closing Thread!";
emit signalThreadError(socket->errorString());
}
else
{
DEBUG << "ConnectionThread: Socket is Connected.";
connect(socket, &QIODevice::readyRead, this, &ConnectionThread::slotThreadReadyRead, Qt::DirectConnection);
exec();
}
}
signals:
void signalThreadError(QString);
void signalIncomingMessage(QByteArray);
public slots:
void slotThreadReadyRead(){
QByteArray msg = socket->readAll();
if (!msg.isEmpty()) {
emit signalIncomingMessage(msg);
}
DEBUG << "ConnectionThread: Data in:" << msg;
}
void slotSocketDisconnected();
void slotOutgoingMessage(QByteArray msg)
{
if (socket != nullptr) {
socket->write(msg);
if (socket->waitForBytesWritten(TIMEOUT)) {
DEBUG << "ConnectionThread: Outgoing Message: " << msg;
} else {
DEBUG << "ConnectionThread: Outgoing Message Timeout";
}
}
}
private:
QTcpSocket *socket = nullptr;
};
What happens basically, is it sometimes receives the incoming command from the client, and most of the time it doesn't, not only that, it actually receives some commands and ignores some different ones.
Here's a spookier behavior, notice the line //DEBUG << "Command: " << cmd.toStdString().c_str();
in the Client::request()
, when this line is commented out, the server actually receives some commands, when it's not, server is deaf.
here's an output when the line was commented out:
Size of bytes written : 8
Command sent
target num = 4 (a reply was received)
Size of bytes written : 8
Command sent
No reply from Server
Here's another output when the mentioned line isn't commented out:
Command: NUMB
Size of bytes written : 8
Command sent
No reply from Server
target num = 0
Command: TRAN
Size of bytes written : 8
Command sent
No reply from Server
Update Solving the race that happens between socket writing at the client and thread opening did it for me, thread using was unnecessary in my case here so I just dropped it. Answered by @Botje
The problem was caused by a race condition:
If data arrives in the window between 2 and 4, the readyRead signal would not have picked it up. There are several possible solutions, but the simplest is probably to either move the socket to the ConnectionThread after construction, or delay its construction until the ConnectionThread::run method.