从QLocalSocket读取超过2048个字节

时间:2013-02-12 11:46:30

标签: c++ sockets qt5

我在从QLocalSocket读取超过2048个字节时遇到问题。 这是我的服务器端代码:

clientConnection->flush();  // <-- clientConnection is a QLocalSocket

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

out.setVersion(QDataStream::Qt_5_0);
out << (quint16)message.size() << message;  // <--- message is a QString

qint64 c = clientConnection->write(block);
clientConnection->waitForBytesWritten();
if(c == -1)
    qDebug() << "ERROR:" << clientConnection->errorString();

clientConnection->flush();

这就是我在客户端读取数据的方式:

QDataStream in(sock);  // <--- sock is a QLocalSocket
in.setVersion(QDataStream::Qt_5_0);

while(sock->bytesAvailable() < (int)sizeof(quint16)){
    sock->waitForReadyRead();
}
in >> bytes_to_read; // <--- quint16

while(sock->bytesAvailable() < (int)bytes_to_read){
    sock->waitForReadyRead();
}

in >> received_message;

客户端代码连接到readyRead信号,并在第一次调用插槽后断开连接。

为什么我只能读取2048个字节?

== EDIT ==

peppe回复后,我更新了我的代码。以下是它现在的样子:

服务器端代码:

clientConnection->flush();

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

out.setVersion(QDataStream::Qt_5_0);

out << (quint16)0;
out << message;
out.device()->seek(0);
out << (quint16)(block.size() - sizeof(quint16));
qDebug() << "Bytes client should read" << (quint16)(block.size() - sizeof(quint16));

qint64 c = clientConnection->write(block);
clientConnection->waitForBytesWritten();

客户端代码:

QDataStream in(sock);
in.setVersion(QDataStream::Qt_5_0);

while(sock->bytesAvailable() < sizeof(quint16)){
    sock->waitForReadyRead();
}

quint16 btr;
in >> btr;

qDebug() << "Need to read" << btr << "and we have" << sock->bytesAvailable() << "in sock";
while(sock->bytesAvailable() < btr){
    sock->waitForReadyRead();
}

in >> received_message;

qDebug() << received_message;

我仍然无法阅读更多数据。

1 个答案:

答案 0 :(得分:1)

out.setVersion(QDataStream::Qt_5_0);
out << (quint16)message.size() << message;  // <--- message is a QString

这是错误的。序列化长度的“message”将是message.size()* 2 + 4个字节,因为QString将自己的长度作为quint32预先设置,并且每个QString字符实际上是UTF-16代码单元,因此它需要2个字节。期望在读取器中只读取message.size()字节将导致QDataStream短读,这是未定义的行为。

请在这些行之后检查“块”的大小 - 它将是2 + 4 + 2 * message.size()字节。所以你需要修复数学。你可以放心地假设它不会改变,因为Qt数据类型is known and documented的序列化格式。

但是,我确实认识到了预设长度的“设计模式”。它可能来自Qt附带的财富网络示例。 The notable difference有一个例子没有以UTF-16代码单位前置字符串的长度(这是没有意义的,因为它不是如何序列化的) - 它预先设置了序列化 QString。看看它的作用:

    out << (quint16)0;
    out << fortunes.at(qrand() % fortunes.size());
    out.device()->seek(0);
    out << (quint16)(block.size() - sizeof(quint16));

首先,它通过编写0在输出中保留一些空间。然后它序列化一个QString。然后它回溯并用序列化QString的长度覆盖0 - 此时,正好是block.size()减去前面的整数,说明长度(我们知道序列化长度一个quint16 sizeof(quint16)

为了重复自己,实际上有两个理由说明为什么示例方式编码,并且它们在某种程度上是相关的:

  1. QDataStream无法从短读取中恢复:当您使用operator>>反序列化对象时,成功解码对象所需的所有数据必须可用。因此,在确保收到所有数据之前,您无法使用它。这将我们带到:
  2. TCP没有内置机制来分离“记录”中的数据。你不能只发送一些字节后跟一个“记录标记”,它告诉接收者他已经收到了与记录有关的所有数据。 TCP提供的是原始字节流。最终,您可以(半)关闭连接以向另一个对等方发信号通知传输结束。
  3. 1 + 2意味着您必须使用其他一些机制来了解(在接收方)您是否已经拥有所需的所有数据,或者您必须等待更多。例如,您可以引入带内标记,如\r\n(如IRC或 - 在某种程度上 - HTTP)。

    财富示例中的解决方案是在“实际”数据(带有命运消息的序列化QString)前面加上该数据的长度(以字节为单位);然后它发送长度(作为16位整数),然后是数据本身。

    接收器首先读取长度;然后它读取了那么多字节,然后它知道它可以解码财富。 如果没有足够的可用数据(两者都是长度 - 即你收到的不到2个字节 - 以及有效负载本身),客户端什么都不做,等待更多。

    请注意:

    • 设计不是新的:这是所有大多数协议的功能。在“标准”TCP / IP堆栈中,TCP,IP,以太网等都在其“标题”中有一个字段,用于指定有效负载(或整个“记录”)的长度;
    • “length”的传输使用以特定字节顺序发送的 16位无符号整数:它不是memcpy()d进入缓冲区,而是使用QDataStream来存储它和读回来。虽然看起来似乎微不足道,但实际上完成了您正在使用的协议的定义
    • 如果QDataStream能够从短读取中恢复(通过抛出异常并将数据保留在设备中),则不需要发送有效负载的长度,因为QDataStream 已经发送字符串的长度(作为32位无符号bigendian整数),然后是UTF-16字符。