28

を使用QTcpSocketしてデータを受信する場合、使用するreadyRead()信号は で、新しいデータが利用可能であることを示します。ただし、データを読み取るために対応するスロット実装にいる場合、追加は発行されませんreadyRead()。これは、利用可能なすべてのデータを読み取っている関数に既に入っているため、理にかなっています。

問題の説明

ただし、このスロットの次の実装を想定しています。

void readSocketData()
{
    datacounter += socket->readAll().length();
    qDebug() << datacounter;
}

readAll()呼び出し後、スロットを離れる前にデータが到着した場合はどうなりますか? これが他のアプリケーションによって送信された最後のデータ パケット (または、少なくともしばらくの間最後のデータ パケット) である場合はどうなるでしょうか? 追加の信号は発行されないため、すべてのデータを自分で読み取る必要があります。

問題を最小限に抑える 1 つの方法 (ただし、完全に回避するわけではありません)

もちろん、次のようにスロットを変更できます。

void readSocketData()
{
    while(socket->bytesAvailable())
        datacounter += socket->readAll().length();
    qDebug() << datacounter;
}

ただし、問題は解決していません。-checkの直後にデータが到着する可能性は依然としてありますsocket->bytesAvailable()(関数の絶対的な最後に /another チェックを配置しても、これは解決されません)。

問題を再現できることを確認する

もちろん、この問題はめったに発生しないため、スロットの最初の実装に固執し、人工的なタイムアウトを追加して、問題が確実に発生するようにします。

void readSocketData()
{
    datacounter += socket->readAll().length();
    qDebug() << datacounter;

    // wait, to make sure that some data arrived
    QEventLoop loop;
    QTimer::singleShot(1000, &loop, SLOT(quit()));
    loop.exec();
}

次に、別のアプリケーションに 100,000 バイトのデータを送信させます。これが起こることです:

新しいつながり!
32768 (または 16K または 48K)

メッセージの最初の部分は読み取られますが、再度呼び出されることはないため、最後は読み取られませんreadyRead()

私の質問は次のとおりです。この問題が発生しないことを確認する最善の方法は何ですか?

考えられる解決策

私が思いついた解決策の 1 つは、最後に同じスロットを再度呼び出し、スロットの先頭で読み取るデータがあるかどうかを確認することです。

void readSocketData(bool selfCall) // default parameter selfCall=false in .h
{
    if (selfCall && !socket->bytesAvailable())
        return;

    datacounter += socket->readAll().length();
    qDebug() << datacounter;

    QEventLoop loop;
    QTimer::singleShot(1000, &loop, SLOT(quit()));
    loop.exec();

    QTimer::singleShot(0, this, SLOT(readSocketDataSelfCall()));
}

void readSocketDataSelfCall()
{
    readSocketData(true);
}

スロットを直接呼び出すのではなく、 を使用するため、スロットを再度呼び出していることを が認識できないQTimer::singleShot()と想定しているため、発行されない問題はもう発生しません。QTcpSocketreadyRead()

パラメーターを含めた理由bool selfCallは、によって呼び出されたスロットがQTcpSocketすぐに終了することが許可されていないためです。そうしないと、同じ問題が再び発生する可能性があり、データが正確に間違ったタイミングで到着し、送信されreadyRead()ません。

これは本当に私の問題を解決するための最良の解決策ですか? この問題の存在は Qt の設計エラーですか、それとも何か不足していますか?

4

6 に答える 6

12

簡潔な答え

状態のドキュメント:QIODevice::readyRead()

readyRead()再帰的に発行されません。waitForReadyRead()シグナルに接続されたスロット内でイベント ループまたは呼び出しを再入力するreadyRead()と、シグナルは再送信されません。

したがって、次のことを確認してください。

  • QEventLoopスロット内でインスタンス化しないでください。
  • QApplication::processEvents()スロット内で呼び出さないでください。
  • QIODevice::waitForReadyRead()スロット内で呼び出さないでください。
  • QTcpSocket異なるスレッド内で同じインスタンスを使用しないでください。

これで、反対側から送信されたすべてのデータを常に受信する必要があります。


バックグラウンド

readyRead()シグナルは次のように発行されQAbstractSocketPrivate::emitReadyRead()ます。

// Only emit readyRead() when not recursing.
if (!emittedReadyRead && channel == currentReadChannel) {
    QScopedValueRollback<bool> r(emittedReadyRead);
    emittedReadyRead = true;
    emit q->readyRead();
}

変数は、ブロックが範囲外になるとすぐにemittedReadyReadロールバックされます (によって行われます)。したがって、シグナルを見逃す唯一のチャンスは、最後のシグナルの処理が完了するに、制御フローが再び条件に到達した場合(つまり、再帰がある場合) です。falseifQScopedValueRollbackreadyRead()ifreadyRead()

また、再帰は上記の状況でのみ可能です。

于 2016-03-23T14:08:53.630 に答える
1

The problem is quite interesting.

In my program the usage of QTcpSocket is very intensive. So I've written the whole library, that breaks outgoing data into packages with a header, data identifier, package index number and maximum size, and when the next piece of data comes, I know exactly where it belongs to. Even if I miss something, when the next readyRead comes, the receiver reads all and compose received data correctly. If the communication between your programs is not so intense, you could do the same, but with timer (which is not very fast, but solves the problem.)

About your solution. I don't think it's better then this:

void readSocketData()
{
    while(socket->bytesAvailable())
    {
        datacounter += socket->readAll().length();
        qDebug() << datacounter;

        QEventLoop loop;
        QTimer::singleShot(1000, &loop, SLOT(quit()));
        loop.exec();
    }
}

The problem of both methods is the code right after leaving the slot, but before returning from emitting the signal.

Also you could connect with Qt::QueuedConnection.

于 2013-04-16T02:28:56.670 に答える
0

ファイル全体を取得する方法の例をいくつか示しますが、QNetwork API の他の部分を使用します。

http://qt-project.org/doc/qt-4.8/network-downloadmanager.html

http://qt-project.org/doc/qt-4.8/network-download.html

これらの例は、より強力な方法で TCP データを処理し、バッファーがいっぱいになった場合、およびより高いレベルの API を使用したより優れたエラー処理を示しています。

それでも下位レベルの API を使用したい場合は、バッファを処理する優れた方法を記載した投稿を次に示します。

あなたの中で次のreadSocketData()ようなことをしてください:

if (bytesAvailable() < 256)
    return;
QByteArray data = read(256);

http://www.qtcentre.org/threads/11494-QTcpSocket-readyRead-and-buffer-size

編集: QTCPSockets と対話する方法の追加の例:

http://qt-project.org/doc/qt-4.8/network-fortuneserver.html

http://qt-project.org/doc/qt-4.8/network-fortuneclient.html

http://qt-project.org/doc/qt-4.8/network-blockingfortuneclient.html

それが役立つことを願っています。

于 2013-04-15T22:03:08.360 に答える
0

ソケットからデータを受信中に a が表示される場合、(メソッドなどによって) データが送信されたQProgressDialog場合にのみ機能します。これはもちろん、前述のように信号の損失につながります。QApplication::processEvents()QProgessDialog::setValue(int)readyRead

したがって、私の回避策は、次のような processEvents コマンドを含む while ループでした。

void slot_readSocketData() {
    while (m_pSocket->bytesAvailable()) {
        m_sReceived.append(m_pSocket->readAll());
        m_pProgessDialog->setValue(++m_iCnt);
    }//while
}//slot_readSocketData

スロットが一度呼び出された場合、は呼び出し後に常に実際の番号を返すreadyReadため、追加のシグナルは無視できます。while ループが終了するのは、ストリームの一時停止時のみです。しかし、次は見逃さず、再び開始します。bytesAvailable()processEventsreadReady

于 2017-11-07T11:19:04.650 に答える