QTcpSocket::readAll() пуст

Я новичок в Qt и немного борюсь. Я пытаюсь отправить строку с клиента на сервер с помощью QTcpSocket.

Сторона клиента:

QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::QttSock->write(block)0);
out << name;

tSock->connectToHost(ipAddress, portNumb.toInt());
tSock->waitForConnected();
tSock->write(block); // block is not empty
tSock->flush();

Сторона сервера:

void Server::readyRead()
{
    QByteArray block;
    QDataStream out(&block, QIODevice::ReadOnly);
    out << tcpSocket->readAll();
    QString name(block); // block is empty
    players.insert(name, tcpSocket);
    std::cout << "name: " << name.toStdString(); // TODO remove
}

На стороне сервера программа вводит readyRead() при получении данных, но блок пустой, хотя на стороне клиента блок не пуст при вызове tSock->write(block)... В чем проблема? Буду признателен за любую помощь. Спасибо!


person Vasil Yoshev    schedule 13.06.2017    source источник


Ответы (3)


Редактировать: ваша ошибка в том, что вы открываете поток данных out в режиме ReadOnly, но пытаетесь записать в него полученный массив байтов:

void Server::readyRead()
{
    QByteArray block;
    QDataStream out(&block, QIODevice::ReadOnly); // !mistake, WriteOnly mode is needed
    out << tcpSocket->readAll(); // this is write operation
    //...
}

Дополнительно: обратите внимание, что существует механизм сериализации данных Qt. Типы, которые полезны в таких случаях:

tSock->write(block); // this is write just a raw data of the block, not the "QByteArray"

Вы можете использовать потоковую операцию для записи необходимых типов данных Qt в сокет напрямую, без преобразования в QByteArray:

// Connect firstly
tSock->connectToHost(ipAddress, portNumb.toInt());
tSock->waitForConnected();

// Then open a data stream for the socket and write to it:
QDataStream out(tSock, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
out << name; // write string directly without a convertion to QByteArray

// Then you may
tSock->flush();

На стороне клиента, а затем используйте аналогичную операцию потока на стороне сервера:

void Server::readyRead()
{
    QString name;
    QDataStream in(tcpSocket, QIODevice::ReadOnly /*or QIODevice::ReadWrite if necessary */);
    in.setVersion(QDataStream::Qt_4_0);    
    in >> name; // read the string
    //...
}

Также возможно читать/записывать объекты других устройств ввода-вывода Qt: QFile, QSerialPort, QProcess, QBuffer и другие.

Изменить 2: не гарантируется, что по сигналу readyRead вы получите полный пакет, который был отправлен. Поэтому см. пример ниже.


Обратите внимание, что в реальном случае (когда у вас несколько разных пакетов в клиент-серверной коммуникации, и неизвестно, какой из нескольких возможных пакетов вы получили) обычно используется более сложный алгоритм, так как при событии readyRead в сообщении могут возникнуть следующие ситуации:

  1. Полный пакет получен
  2. Получил только часть посылки
  3. Получил несколько посылок вместе

Вариант алгоритма (Пример клиента Qt 4 Fortune):

void Client::readFortune() // on readyRead
{
    QDataStream in(tcpSocket);
    in.setVersion(QDataStream::Qt_4_0);

    if (blockSize == 0) {
        if (tcpSocket->bytesAvailable() < (int)sizeof(quint16))
            return;

        in >> blockSize;
    }

    if (tcpSocket->bytesAvailable() < blockSize)
        return;

    QString nextFortune;
    in >> nextFortune;    

    //...
}

Qt 4.0 — слишком старая версия Qt, поэтому см. также Qt 5.9 Fortune Client Пример

person Vladimir Bershov    schedule 13.06.2017
comment
Вообще говоря, повторное создание QDataStream в сокете или является ошибкой, так как вы потеряете состояние потока и также можете потерять данные. - person Kuba hasn't forgotten Monica; 14.06.2017
comment
@KubaOber, но это пример из документации Qt 4, и я уверен, что код вышеуказанной функции readFortune() правильный - person Vladimir Bershov; 14.06.2017
comment
Это правильно в том виде, в котором оно есть, но оно легко станет неверным, как только вы его измените. - person Kuba hasn't forgotten Monica; 14.06.2017

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

tcpsocket->waitForBytesWritten(1000);
person Kamlesh    schedule 14.06.2017

Код readyRead неправильно заполняет block. Вы также предполагаете, что прочитаете достаточно данных, чтобы передать всю строку. Это никогда не гарантируется. Все, что вы знаете, когда readyRead срабатывает, это то, что нужно прочитать хотя бы один байт. В этом помогает система QDataStream транзакций.

Это будет одна правильная реализация:

class Server : public QObject {
  Q_OBJECT
  QPointer<QTcpSocket> m_socket;
  QDataStream m_in{m_socket.data(), &QIODevice::ReadOnly};
  void onReadyRead();
public:
  Server(QTcpSocket * socket, QObject * parent = {}) :
    QObject(parent),
    m_socket(socket)
  {
    connect(socket, &QIODevice::readyRead, this, &Server::onReadyRead);
  }
}

void onReadyRead() {
  while (true) { // must keep reading as long as names are available
    QString name;
    in >> name;
    if (in.commitTransaction()) {
      qDebug << name;
      players.insert(name, &m_socket.data());
    } else
      break;
  }
};
person Kuba hasn't forgotten Monica    schedule 14.06.2017