使用 QDataStream 从 QTcpSocket 读取

Read from QTcpSocket using QDataStream

本文关键字:读取 QTcpSocket QDataStream 使用      更新时间:2023-10-16

我需要通过QTcpSocket发送二进制数据。我正在考虑使用 QDataStream ,但我遇到了一个问题 - 如果在我尝试读取时没有数据到达,它会静默失败。

例如,如果我有以下代码:

QString str;
stream >> str;

如果套接字中当前没有数据,它将以静默方式失败。有没有办法告诉它阻止?

问题有点严重。套接字可以以块的形式接收数据,因此即使您等待waitForReadyRead它也可能会失败,因为没有足够的数据来立即读取某些对象。
要解决此问题,您必须首先发送一定大小的数据,然后再发送实际数据。发送代码:

QByteArray block;
QDataStream sendStream(&block, QIODevice::ReadWrite);
sendStream << quint16(0) << str;
sendStream.device()->seek(0);
sendStream << (quint16)(block.size() - sizeof(quint16));
tcpSocket->write(block);

在接收器上,您必须等待,直到可用数据的大小满足要求。接收器代码看起来或多或少是这样的:

void SomeClass::slotReadClient() { // slot connected to readyRead signal of QTcpSocket
    QTcpSocket *tcpSocket = (QTcpSocket*)sender();
    QDataStream clientReadStream(tcpSocket);
    while(true) {
        if (!next_block_size) {
            if (tcpSocket->bytesAvailable() < sizeof(quint16)) { // are size data available
                break;
            }
            clientReadStream >> next_block_size;
        }
        if (tcpSocket->bytesAvailable() < next_block_size) {
            break;
        }
        QString str;
        clientReadStream >> str;
        next_block_size = 0;
    }
}


小更新,基于文档,可以在不添加额外大小信息的情况下读取QString,因为传递给QDataStream的QString包含大小信息。尺寸可以像这样验证:
void SomeClass::slotReadClient() { // slot connected to readyRead signal of QTcpSocket
    QTcpSocket *tcpSocket = (QTcpSocket*)sender();
    while(true) {
        if (tcpSocket->bytesAvailable() < 4) {
           break;
        }
        char buffer[4]
        quint32 peekedSize;
        tcpSocket->peek(buffer, 4);
        peekedSize = qFromBigEndian<quint32>(buffer); // default endian in QDataStream
        if (peekedSize==0xffffffffu) // null string
           peekedSize = 0;
        peekedSize += 4;
        if (tcpSocket->bytesAvailable() < peekedSize) {
           break;
        }
        // here all required for QString  data are available
        QString str;
        QDataStream(tcpSocket) >> str;
        emit stringHasBeenRead(str);
     }
}

我根据@Marek的想法重新设计了代码并创建了 2 个类 - BlockReader 和 BlockWriter:

示例用法:

// Write block to the socket.
BlockWriter(socket).stream() << QDir("C:/Windows").entryList() << QString("Hello World!");
....
// Now read the block from the socket.
QStringList infoList;
QString s;
BlockReader(socket).stream() >> infoList >> s;
qDebug() << infoList << s;

块阅读器:

class BlockReader
{
public:
    BlockReader(QIODevice *io)
    {
        buffer.open(QIODevice::ReadWrite);
        _stream.setVersion(QDataStream::Qt_4_8);
        _stream.setDevice(&buffer);
        quint64 blockSize;
        // Read the size.
        readMax(io, sizeof(blockSize));
        buffer.seek(0);
        _stream >> blockSize;
        // Read the rest of the data.
        readMax(io, blockSize);
        buffer.seek(sizeof(blockSize));
    }
    QDataStream& stream()
    {
        return _stream;
    }
private:
    // Blocking reads data from socket until buffer size becomes exactly n. No
    // additional data is read from the socket.
    void readMax(QIODevice *io, int n)
    {
        while (buffer.size() < n) {
            if (!io->bytesAvailable()) {
                io->waitForReadyRead(30000);
            }
            buffer.write(io->read(n - buffer.size()));
        }
    }
    QBuffer buffer;
    QDataStream _stream;
};

块写入器:

class BlockWriter
{
public:
    BlockWriter(QIODevice *io)
    {
        buffer.open(QIODevice::WriteOnly);
        this->io = io;
        _stream.setVersion(QDataStream::Qt_4_8);
        _stream.setDevice(&buffer);
        // Placeholder for the size. We will get the value
        // at the end.
        _stream << quint64(0);
    }
    ~BlockWriter()
    {
        // Write the real size.
        _stream.device()->seek(0);
        _stream << (quint64) buffer.size();
        // Flush to the device.
        io->write(buffer.buffer());
    }
    QDataStream &stream()
    {
        return _stream;
    }
private:
    QBuffer buffer;
    QDataStream _stream;
    QIODevice *io;
};

你可以调用 QTCPSocket::waitForReadyRead 函数,该函数将阻塞直到数据可用,或者连接到 readyRead() 信号,当你的插槽被调用时,然后从流中读取。

QDataStream从5.7开始获得了读取事务的概念:https://doc.qt.io/qt-5/qdatastream.html#using-read-transactions

因此,QDataStream 知道何时发生截断(由于没有足够的可用字节)。

解决上述问题的一种方法可能是:

QString str;
do
{
    stream.startTransaction();
    stream >> str;
} while(!stream.commitTransaction());

但是,这只是说明概念。只有在绝对确定必要的数据很快就会到达的情况下,才应使用这种相当幼稚的实现,否则这可能会导致无限循环。更强大的检查可能还应该考虑QDataStream后面的底层QIODevice的状态,等等。