从 QLocalSocket 读取超过 2048 个字节

Posted

技术标签:

【中文标题】从 QLocalSocket 读取超过 2048 个字节【英文标题】:Reading more than 2048 bytes from QLocalSocket 【发布时间】:2013-02-12 11:46:29 【问题描述】:

我时出现问题。 这是我的服务器端代码:

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 个字节?

==编辑==

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;

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

【问题讨论】:

您必须检查 bytesAvailable 是否等于或大于您希望收到的任何内容。通常,如果没有足够的数据可以读取,您可以从 readyRead 槽返回,当更多数据到来时会再次调用它。 @Archie bytesAvailable() 返回 4KB(我期望 2KB),所以这应该没问题。至于第二个电话,是做不到的。我需要在一次调用中完成所有事情(根据 Qt 文档,这是可能的,我只需要等待数据即可)。 【参考方案1】:
out.setVersion(QDataStream::Qt_5_0);
out << (quint16)message.size() << message;  // <--- message is a QString

这是错误的。 “消息”的序列化长度将是 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() 减去说明长度的前置整数(我们知道 a 的序列化长度quint16sizeof(quint16))

重复我自己,实际上有两个原因说明为什么那个例子被编码为那样,并且它们在某种程度上是相关的:

    QDataStream 无法从短读取中恢复:当您使用operator&gt;&gt; 反序列化对象时,它成功解码对象所需的所有数据必须可用。因此,在确定收到所有数据之前,您不能使用它。这让我们: TCP 没有内置机制来分离“记录”中的数据。您不能只发送一些字节,然后发送一个“记录标记”,告诉接收者他已收到与记录相关的所有数据。 TCP 提供的是原始字节流。最终,您可以(半)关闭连接以向其他对等方发出传输结束的信号。

1+2 意味着您必须使用其他机制来了解(在接收方)您是否已经拥有所需的所有数据,或者您必须等待更多数据。例如,您可以引入像 \r\n 这样的带内标记(像 IRC 或 - 在一定程度上 - HTTP 做的)。

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

接收端先读取长度;然后它读取了那么多字节,然后它知道它可以解码命运。 如果没有足够的可用数据(无论是长度 - 即您收到少于 2 个字节 - 以及有效负载本身),客户端就什么也不做,等待更多。

注意:

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

【讨论】:

感谢您的回复。我更新了我的代码(请参阅我的问题的更新),但我仍然无法读取更多数据。我认为我的数学现在很好,但由于某种原因waitForReadyRead 永远阻塞,因为它没有获取所有数据,但我在服务器端做了waitForBytesWritten(); flush();。我还缺少什么? 请注意,如果我稍微更改我的代码以基于readyRead SIGNAL 工作,我确实会获得所有数据,无论它有多大。问题是:为什么它不能以另一种方式工作? waitForReadyRead() 不应该像我将客户端代码连接到 SIGNAL readyRead(又名,等待更多数据)一样吗?

以上是关于从 QLocalSocket 读取超过 2048 个字节的主要内容,如果未能解决你的问题,请参考以下文章

Qt利用QLocalSocket和QLocalServer实现IPC

2048 游戏 - AI 的平均得分不能超过 256

Python syslog 记录器不能写入超过 2048 个字符

制作一个 java 2048 游戏,滑动时它通过循环的次数超过了它应该的次数,并命中了已经改变的测试/更改数字

IPC QLocalSocket -> C,为啥我的连接失败?连接失败:[2](没有这样的文件?!)

Discord bot 嵌入消息错误 长度必须为 2048 或更少