Search code examples
qtqtcpsocketqtcpserver

QTcpSocket data arrives late


I am using QTCPSockets to talk to a program I've written in Qt for Raspberry Pi. The same software runs on my Mac (or Windows, whatever). The Pi is running a QTCPServer.

I send JSON data to it and most of the time this goes ok.

But sometimes, the Pi is not responding, the data does not seem to arrive. But then, when I send some more data, that data is not being handled, but the previous Json message is! And this stays like this. All the messages are now off by 1. Sending a new messages, triggers the previous one.

It feels a bit connected to this bugreport: https://bugreports.qt.io/browse/QTBUG-58262 But I'm not sure if it is the same.

I've tried waitForBytesWritten and flush and it seemed to work at first, but later I saw the issue again.

I expect that the TCP buffer on the Pi is not being flushed, but I do now know how to make sure that all data is handled right away.

As asked, here is some sourcecode: This is the client software:

Client::Client() : tcpSocket(new QTcpSocket(this)), in(tcpSocket)
{
    connect(tcpSocket, &QIODevice::readyRead, this, &Client::readData);
    connect(tcpSocket, &QTcpSocket::connected, this, &Client::connected);
    connect(tcpSocket, &QTcpSocket::stateChanged, this, &Client::onConnectionStateChanged);
    void (QAbstractSocket:: *sig)(QAbstractSocket::SocketError) = &QAbstractSocket::error;
    connect(tcpSocket, sig, this, &Client::error);
}

void Client::connectTo(QString ip, int port) {
    this->ip = ip;
    this->port = port;
    tcpSocket->connectToHost(ip, port);
}

void Client::reconnect() {
    connectTo(ip, port);
}

void Client::disconnect()
{
    tcpSocket->disconnectFromHost();
}

void Client::connected()
{
    qDebug() << TAG << "connected!";
}

void Client::error(QAbstractSocket::SocketError error)
{
    qDebug() << TAG << error;
}

void Client::sendData(const QString& data)
{
    bool connected = (tcpSocket->state() == QTcpSocket::ConnectedState);
    if (!connected) {
        qDebug() << TAG << "NOT CONNECTED!";
        return;
    }

    QByteArray block;
    QDataStream out(&block, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_5_7);

    out << data;
    tcpSocket->write(block);
    tcpSocket->flush();
}

void Client::sendData(const QByteArray& data) {
    bool connected = (tcpSocket->state() == QTcpSocket::ConnectedState);
    if (!connected) {
        qDebug() << TAG << " is NOT connected!";
        return;
    }
    tcpSocket->write(data);
    tcpSocket->flush();
}

void Client::readData()
{
    in.startTransaction();

    QString data;
    in >> data;

    if (!in.commitTransaction())
    {
        return;
    }

    emit dataReceived(data);
}

void Client::onConnectionStateChanged(QAbstractSocket::SocketState state)
{
    switch (state) {
    case QAbstractSocket::UnconnectedState:
        connectionState = "Not connected";
        break;
    case QAbstractSocket::ConnectingState:
        connectionState = "connecting";
        break;
    case QAbstractSocket::ConnectedState:
        connectionState = "connected";
        break;
    default:
        connectionState = QString::number(state);
    }

    qDebug() << TAG << " connecting state: " << state;

    emit connectionStateChanged(connectionState);

    if (state == QAbstractSocket::UnconnectedState) {
        QTimer::singleShot(1000, this, &Client::reconnect);
    }
}

and here the server part:

Server::Server()
{
    tcpServer = new QTcpServer(this);

    connect(tcpServer, &QTcpServer::newConnection, this, &Server::handleConnection);

    tcpServer->listen(QHostAddress::Any, 59723);

    QString ipAddress;
    QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
    // use the first non-localhost IPv4 address
    for (int i = 0; i < ipAddressesList.size(); ++i) {
        if (ipAddressesList.at(i) != QHostAddress::LocalHost &&
            ipAddressesList.at(i).toIPv4Address()) {
            ipAddress = ipAddressesList.at(i).toString();
            break;
        }
    }

    // if we did not find one, use IPv4 localhost
    if (ipAddress.isEmpty())
        ipAddress = QHostAddress(QHostAddress::LocalHost).toString();

    qDebug() << TAG <<  "ip " << ipAddress << " serverport: " << tcpServer->serverPort();
}

void Server::clientDisconnected()
{
    QTcpSocket *client = qobject_cast<QTcpSocket *>(QObject::sender());

    int idx = clients.indexOf(client);
    if (idx != -1) {
        clients.removeAt(idx);
    }

    qDebug() << TAG << "client disconnected: " << client;

    client->deleteLater();
}

void Server::handleConnection()
{
    qDebug() << TAG << "incoming!";

    QTcpSocket* clientConnection = tcpServer->nextPendingConnection();
    connect(clientConnection, &QAbstractSocket::disconnected, this, &Server::clientDisconnected);
    connect(clientConnection, &QIODevice::readyRead, this, &Server::readData);
    clients.append(clientConnection);
    broadcastUpdate(Assets().toJson());
}

void Server::readData()
{
    QTcpSocket *client = qobject_cast<QTcpSocket *>(QObject::sender());

    QDataStream in(client);
    in.startTransaction();

    QString data;
    in >> data;

    if (!in.commitTransaction())
    {
        return;
    }

...
    // here I do something with the data. I removed that code as it is
    // not necessary for this issue
...

    broadcastUpdate(data);
}

void Server::broadcastUpdate(const QString& data)
{
    QByteArray block;
    QDataStream out(&block, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_5_7);

    out << data;

    foreach(QTcpSocket* client, clients) {
        bool connected = (client->state() == QTcpSocket::ConnectedState);
        if (!connected) {
            qDebug() << TAG << client << " is NOT connected!";
            continue;
        }
        client->write(block);
    }
}

Solution

  • I think the problem is with your void Client::readData(): you have to write it in such a way that you read all available data from the socket inside it (usually it's written with while (socket->bytesAvailable() > 0) { ... } loop).

    It is because of the way the readyRead() signal is emitted: the remote peer may send any non-zero number of packets to you, and your socket will emit any non-zero number of readyRead() signals. In your case, it seems that the server sends two messages but they only cause one readyRead() signal to be emitted on the client.