1

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);
    }
}
Boy
  • 7,010
  • 4
  • 54
  • 68
  • How are you implementing boundaries between the JSON messages? Can you post the code for sending and receiving messages? – bnaecker Dec 10 '17 at 17:09
  • @bnaecker thanks, did it. I rebooted the system (Pi) in the meantime, until now I don't see the issue. But I need a reliable system so I want to fix these issues or work around them. I do not know what you mean with 'boundaries' between the messages. I just send them and when they arrive I parse the data to objects – Boy Dec 10 '17 at 17:40
  • 1
    @Boy: I'll be glad if my answer [here](https://stackoverflow.com/a/20558939/2014561) helps you. :) – Antonio Dias Dec 10 '17 at 21:19
  • @AntonioDias yes, your answer led me into the right direction to (same as accepted answer, see my comment there), thanks! Only thing missing in your post is the slot connected to `dataReceived()`. I upvoted your answer there. – Boy Dec 11 '17 at 20:28

1 Answers1

2

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.

Joker_vD
  • 3,715
  • 1
  • 28
  • 42
  • btw, how do I know when I have the whole message? The in.commitTransaction() seemed sometimes to be false and then I return. That could be it, but at least I had a complete transaction. – Boy Dec 10 '17 at 18:14
  • Thanks! It took a while before I full understood what I did wrong! I added the `while(bytesAvailable())` in the client's ReadyRead, but I didn't do that in the `QTCPServer`! So I missed that the server was actually the one not broadcasting the data to it's clients. Thanks a lot for putting me in the right direction! – Boy Dec 11 '17 at 20:26