我在从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字节?
===编辑===
在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;
我仍然无法读取更多数据。
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数据类型的序列化格式是已知的并有文档记录。
不过,我确实认识到预先设定长度的"设计模式"。它可能来自Qt附带的财富网络示例。值得注意的区别在于,该示例没有以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()减去声明长度的前置整数(我们知道一个五元组的序列化长度是sizeof(quint16)
)
重复一遍,实际上有两个原因可以解释为什么那个例子是以这种方式编码的,而且它们在某种程度上是相关的:
- QDataStream无法从短读取中恢复:当您使用
operator>>
反序列化对象时,成功解码对象所需的所有数据都必须可用。因此,在确保收到所有数据之前,您不能使用它。这让我们想到: - TCP没有用于分离"记录"中的数据的内置机制。你不能只发送一些字节,然后加上一个"记录标记",它告诉接收者他已经收到了与记录相关的所有数据。TCP提供的是原始字节流。最终,您可以(半)关闭连接,向另一个对等方发出传输结束的信号
1+2意味着您必须使用其他机制(在接收方)来知道您是否已经拥有所需的所有数据,或者您必须等待更多数据。例如,您可以引入带内标记,如rn
(如IRC或在一定程度上的HTTP)。
fortune示例中的解决方案是在"实际"数据(带有fortune消息的序列化QString)前面加上该数据的长度(以字节为单位);则它发送长度(作为16位整数),后跟数据本身。
接收器首先读取长度;然后它读取那么多字节,然后它知道它可以解码命运如果没有足够的可用数据(长度(即您收到的数据少于2个字节)和有效负载本身),客户端只需不做任何操作,等待更多数据。
注意:
- 设计并不是新的:这是大多数协议所做的。在"标准"TCP/IP堆栈中,TCP、IP、以太网等都在其"标头"中有一个字段,用于指定有效载荷(或整个"记录")的长度
- "长度"的传输使用一个按特定字节顺序发送的16位无符号整数:它不是进入缓冲区的memcpy()d,而是在它上使用QDataStream来存储和读回它。尽管这看起来微不足道,但实际上完成了您正在使用的协议的定义
- 如果QDataStream能够从短读取中恢复(例如,通过抛出异常并将数据留在设备中),则不需要发送有效负载的长度,因为QDataStream已经发送字符串的长度(作为32位无符号bigendian整数),后跟UTF-16字符