1

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;

それ以上のデータを読み取ることができません。

4

1 に答える 1

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デー​​タ型のシリアル化の形式は既知であり、文書化されているため、変更されないと安全に想定できます。

ただし、長さを付加する「デザインパターン」は認識しています。これはおそらく、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をシリアル化します。次に、をバックトラックして0、シリアル化されたQStringの長さで上書きします。この時点では、正確にblock.size()から長さを示す先頭の整数を引いたものになります(quint16のシリアル化された長さsizeof(quint16)

繰り返しになりますが、その例がそのようにコーディングされた理由には、実際には2つの理由があり、それらは何らかの形で関連しています。

  1. QDataStreamには、短い読み取りから回復する手段がありません。オブジェクトを逆シリアル化するためにを使用する場合、オブジェクトを正常にデコードするために必要なすべてのデータが使用可能である必要があります。operator>>したがって、すべてのデータが受信されたことを確認する前に使用することはできません。これにより、次のことが可能になります。
  2. TCPには、「レコード」内のデータを分離するためのメカニズムが組み込まれていません。レコードに関連するすべてのデータを受信したことを受信者に通知する「レコードマーカー」が後に続くいくつかのバイトを送信することはできません。TCPが提供するのは、バイトの生のストリームです。最終的には、接続を(半分)閉じて、送信が終了したことを他のピアに通知できます。

1 + 2は、必要なすべてのデータがすでにあるかどうかを(受信側で)知るために他のメカニズムを使用する必要があること、またはさらにいくつかを待つ必要があることを意味します。たとえば、\r\n(IRCや-ある程度まで-HTTPのように)のような帯域内マーカーを導入できます。

フォーチュンの例の解決策は、「実際の」データ(フォーチュンメッセージを含むシリアル化されたQString)の前に、そのデータの長さ(バイト単位)を追加することです。次に、長さ(16ビット整数として)に続いてデータ自体を送信します。

受信者は最初に長さを読み取ります。次に、そのバイト数を読み取り、運勢をデコードできることを認識します。利用可能なデータが十分にない場合(長さ(つまり、受信した2バイト未満)とペイロード自体の両方で)、クライアントは単に何もせず、それ以上待機します。

ご了承ください:

  • デザインは新しいものではありません。ほとんどのプロトコルが行うことです。「標準」TCP/IPスタックでは、TCP、IP、イーサネットなどすべての「ヘッダー」に、ペイロード(または「レコード」全体)の長さを指定するフィールドがあります。
  • 「長さ」の送信では、特定のバイト順序で送信される16ビットの符号なし整数が使用されます。これはバッファにmemcpy()dされませんが、QDataStreamを使用して、格納と読み取りの両方を行います。些細なことのように思われるかもしれませんが、これで実際に使用しているプロトコルの定義は完了です
  • QDataStreamが短い読み取り(例外をスローしてデバイスにデータを残すことによってfi)から回復できた場合、QDataStreamはすでに文字列の長さを( 32ビットの符号なしビッグエンディアン整数)の後にUTF-16文字が続きます。
于 2013-02-12T15:33:16.010 に答える