Перехватывать сообщения TCP в Qt, когда несколько сообщений упаковываются в одну полезную нагрузку TCP маршрутизатором - PullRequest
0 голосов
/ 07 марта 2019

Контекст

У меня есть приложение, разделенное на клиенте и серверной части, отправляющее сообщения друг другу по TCP. Пока все хорошо.

Отправляемые сообщения могут быть размером от нескольких байтов до нескольких сотен мегабайт. Так бывает, что сообщения разбиты на несколько полезных нагрузок. Чтобы прочитать эти сообщения, я реализовал этот хорошо известный протокол кодирования, который работает с сигналом readyRead() на стороне получателя:

код отправителя:

QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);

stream << (quint32)0;
stream << theData;
stream.device()->seek(0);
stream << (quint32)data.size();

m_tcpSocket->write(data);

код получателя:

...
connect(m_tcpSocket, SIGNAL(readyRead()), this, SLOT(readMessage()));
...

void readMessage() {

    // wait for the msgSize
    while (m_tcpSocket->bytesAvailable() < qint64(sizeof(quint32))) {
        if (!m_tcpSocket->waitForReadyRead()) {
            return;
        }
    }

    // get the message size
    QDataStream stream(m_tcpSocket);
    quint32 msgSize = 0;
    stream >> msgSize;

    //wait for the whole message
    int length = qint64(msgSize - sizeof(quint32));
    while (m_tcpSocket->bytesAvailable() < length) {
        if (!m_tcpSocket->waitForReadyRead()) {
            return;
        }
    }

    // get the message
    QByteArray buffer;
    buffer.resize(length);
    stream.readRawData(buffer.data(), length);

    // do something with the message
    emit messageRead(buffer);

}

Это хорошо работает. Каждый раз, когда сообщение отправляется, по крайней мере одна полезная нагрузка покидает отправителя, и получатель запускается сигналом readyRead(). Если сообщение разбивается на несколько полезных нагрузок, метод readMessage() ожидает поступления следующей полезной нагрузки, пока не будет получено полное сообщение.

Задача

Это приложение должно использоваться в сети, в которой используются большие маршрутизаторы, которые связываются с полезными нагрузками TCP (или, по крайней мере, я думаю, что это является источником проблемы). Иногда маршрутизаторы решают добавить несколько полезных нагрузок в один. Обычно это происходит при отправке небольших сообщений с высокой скоростью.

Я проверил отправленные полезные данные на стороне отправителя и уверен, что по крайней мере одна полезная нагрузка покидает компьютер отправителя каждый раз, когда он отправляет сообщение. Я также проверил на стороне получателя и могу подтвердить, что поступают полезные данные, содержащие несколько сообщений. Единственное, в чем я не уверен, так это то, как и где это происходит по сети. Но в любом случае, я не могу изменить это поведение, и мне придется иметь дело с ним.

Проблема в том, что в этом случае я мог бы получить одну полезную нагрузку, содержащую несколько сообщений. На стороне получателя это вызовет метод readMessage () один раз, и только первое сообщение будет прочитано из сокета.

Временный патч

Я нашел решение этой проблемы, которое пока работает. Я инкапсулировал метод Qt waitForReadyRead() в один из своих собственных, добавив класс-член bool waitingForReadyRead, который я установил в true во время ожидания.

bool myWaitForReadyRead() {
    waitingForReadyRead = true;
    bool returnValue = m_tcpSocket->waitForReadyRead();
    waitingForReadyRead = false;
    return returnValue;
}

Теперь это позволяет мне рекурсивно вызывать мой метод readMessage(), пока есть доступные байты, не беспокоясь о порядке обработанных событий. Если waitingForReadyRead равно true, я просто возвращаюсь из функции.

void readMessage() {
    if (waitingForReadyRead) {
        return;
    }

    // wait for the msgSize
    while (m_tcpSocket->bytesAvailable() < qint64(sizeof(quint32))) {
        if (!myWaitForReadyRead()) {
            return;
        }
    }

    // get the message size
    QDataStream stream(m_tcpSocket);
    quint32 msgSize = 0;
    stream >> msgSize;

    //wait for the whole message
    int length = qint64(msgSize - sizeof(quint32));
    while (m_tcpSocket->bytesAvailable() < length) {
        if (!myWaitForReadyRead()) {
            return;
        }
    }

    // get the message
    QByteArray buffer;
    buffer.resize(length);
    stream.readRawData(buffer.data(), length);

    // do something with the message
    emit messageRead(buffer);

    if (m_tcpSocket->bytesAvailable() > 0) {
        readMessage();
    }
}

Вопрос

Мне не очень нравится этот патч, поскольку рекурсивный вызов метода, как это, не является хорошей практикой.

Есть ли лучший способ спроектировать мою стратегию приемника, чтобы иметь возможность обрабатывать как сообщения, разделенные на несколько полезных нагрузок, так и полезные нагрузки, содержащие несколько сообщений?

...