it-swarm.com.de

Wie stellen Sie sicher, dass die readyRead () - Signale von QTcpSocket nicht übersehen werden können?

Wenn Sie QTcpSocket zum Empfangen von Daten verwenden, ist das zu verwendende Signal readyRead(), das signalisiert, dass neue Daten verfügbar sind. Wenn Sie sich jedoch in der entsprechenden Slot-Implementierung befinden, um die Daten zu lesen, wird keine zusätzliche readyRead() ausgegeben. Dies kann sinnvoll sein, da Sie sich bereits in der Funktion befinden, in der Sie alle verfügbaren Daten lesen. 

Problembeschreibung

Nehmen Sie jedoch die folgende Implementierung dieses Slots an:

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

Was ist, wenn einige Daten nach dem Aufruf von readAll() ankommen, aber bevor der Slot verlassen wird? Was wäre, wenn dies das letzte Datenpaket war, das von der anderen Anwendung gesendet wurde (oder zumindest das letzte für einige Zeit)? Es wird kein zusätzliches Signal ausgegeben, sodass Sie sicherstellen müssen, dass Sie alle Daten selbst lesen . 

Eine Möglichkeit, das Problem zu minimieren (aber nicht vollständig zu vermeiden)

Natürlich können wir den Slot wie folgt modifizieren:

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

Wir haben das Problem jedoch nicht gelöst. Es ist immer noch möglich, dass Daten unmittelbar nach der socket->bytesAvailable()- Prüfung ankommen (und selbst wenn/noch eine Prüfung am absoluten Ende der Funktion platziert wird, kann dies nicht gelöst werden). 

Stellen Sie sicher, dass Sie das Problem reproduzieren können

Da dieses Problem natürlich sehr selten auftritt, halte ich mich an die erste Implementierung des Slots und füge sogar ein künstliches Timeout hinzu, um sicherzustellen, dass das Problem auftritt:

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();
}

Ich lasse dann eine andere Anwendung 100.000 Byte Daten senden. Das ist, was passiert:

neue Verbindung!
32768 (oder 16K oder 48K)

Der erste Teil der Nachricht wird gelesen, aber das Ende wird nicht mehr gelesen, da readyRead() nicht mehr aufgerufen wird. 

Meine Frage ist: Was ist der beste Weg, um sicher zu gehen, dass dieses Problem nie auftritt?

Mögliche Lösung

Eine Lösung, die ich mir ausgedacht habe, ist, den gleichen Slot erneut am Ende aufzurufen und am Anfang des Slots zu prüfen, ob noch weitere Daten zum Lesen vorhanden sind:

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);
}

Da ich den Slot nicht direkt anrufe, sondern QTimer::singleShot() benutze, gehe ich davon aus, dass die QTcpSocket nicht wissen kann, dass ich den Slot erneut anrufe, sodass das Problem, dass readyRead() nicht ausgegeben wird, nicht mehr auftreten kann. 

Der Grund, warum ich den Parameter bool selfCall eingefügt habe, ist, dass der Slot, der von der QTcpSocket aufgerufen wird, nicht früher beendet werden darf. Andernfalls kann dasselbe Problem erneut auftreten, dass Daten genau zum falschen Zeitpunkt ankommen und readyRead() nicht ausgegeben wird. 

Ist das wirklich die beste Lösung, um mein Problem zu lösen? Ist das Vorhandensein dieses Problems ein Entwurfsfehler in Qt oder fehlt mir etwas?

27
Misch

Kurze Antwort

Die Dokumentation von QIODevice::readyRead() besagt:

Wenn Sie die Ereignisschleife erneut eingeben oder waitForReadyRead() in einem Steckplatz aufrufen Wenn das Signal readyRead() angeschlossen ist, wird das Signal nicht erneut ausgegeben.

So stellen Sie sicher, dass Sie

  • Don't instanziiert eine QEventLoop in Ihrem Slot,
  • Nicht rufen Sie QApplication::processEvents() in Ihrem Steckplatz auf,
  • Nicht rufen Sie QIODevice::waitForReadyRead() in Ihrem Steckplatz auf,
  • nicht verwendet dieselbe QTcpSocket-Instanz in verschiedenen Threads,

und Sie sollten immer alle Daten erhalten, die von der anderen Seite gesendet werden.


Hintergrund

Das readyRead()-Signal wird von QAbstractSocketPrivate::emitReadyRead() wie folgt gesendet:

if (!emittedReadyRead && channel == currentReadChannel) {
    QScopedValueRollback<bool> r(emittedReadyRead);
    emittedReadyRead = true;
    emit q->readyRead();
}

Die Variable emittedReadyRead wird auf false zurückgesetzt, sobald der if-Block den Gültigkeitsbereich verlässt (dies geschieht durch die QScopedValueRollback). Die einzige Chance, ein readyRead()-Signal zu verpassen, besteht also, wenn der Steuerungsfluss wieder die if-Bedingung erreicht, bevor die Verarbeitung des letzten readyRead()-Signals abgeschlossen ist.

Und dies sollte nur in den oben aufgeführten Situationen möglich sein.

9
emkey08

Ich denke, das in diesem Thema erwähnte Szenario hat zwei Hauptfälle, die unterschiedlich funktionieren, aber im Allgemeinen hat QT dieses Problem überhaupt nicht und ich werde versuchen, im Folgenden zu erklären, warum.

Erster Fall: Single-Threaded-Anwendung.

Qt verwendet den Systemaufruf select (), um den geöffneten Dateideskriptor nach vorgenommenen Änderungen oder verfügbaren Operationen abzufragen. Einfaches Sprichwort in jeder Schleife Qt prüft, ob bei geöffneten Dateideskriptoren Daten zum Lesen/Schließen usw. verfügbar sind. Der Single-Thread-Anwendungsablauf sieht also so aus (Code-Teil vereinfacht).

int mainLoop(...) {
     select(...);
     foreach( descriptor which has new data available ) {
         find appropriate handler
         emit readyRead; 
     }
}

void slotReadyRead() {
     some code;
}

Was passiert also, wenn neue Daten eintreffen, während sich das Programm noch in slotReadyRead befindet? Ehrlich gesagt, nichts Besonderes. Das Betriebssystem puffert die Daten und sobald die Steuerung zur nächsten Ausführung von select () zurückkehrt, benachrichtigt das Betriebssystem die Software, dass Daten für eine bestimmte Dateihandhabung verfügbar sind. Für TCP Sockets/Dateien usw. funktioniert das absolut gleich.

Ich kann Situationen abbilden, in denen (bei wirklich langen Verzögerungen in slotReadyRead und bei einer großen Datenmenge) ein Überlauf in OS FIFO - Puffern (z. B. für serielle Schnittstellen) auftreten kann, dies hat jedoch mehr mit einem schlechten zu tun Softwaredesign statt QT- oder OS-Problemen. 

Sie sollten auf Slots wie readyRead wie auf Interrupt-Handlern aussehen und ihre Logik nur innerhalb der Abruffunktionalität beibehalten, die Ihre internen Puffer während der Verarbeitung in separaten Threads oder während der Anwendung im Leerlauf usw. abdeckt. Grund ist, dass eine solche Anwendung im Allgemeinen ist ein Massendienstsystem, und wenn es mehr Zeit für die Bearbeitung einer Anforderung aufbringt, wird die Zeitspanne zwischen zwei Anforderungen in der Warteschlange trotzdem überschrieben. 

Zweites Szenario: Multithread-Anwendung

Tatsächlich unterscheidet sich dieses Szenario nicht so sehr von 1). Erwarten Sie, dass Sie genau das entwerfen, was in jedem Ihrer Threads passiert. Wenn Sie die Hauptschleife mit "Pseudo-Interrupt-Handlern" beibehalten, werden Sie absolut in Ordnung sein und die Logik in anderen Threads verarbeiten. Diese Logik sollte jedoch mit Ihren eigenen Prefetch-Puffern anstatt mit QIODevice funktionieren. 

6
evilruff

Hier einige Beispiele, wie Sie die gesamte Datei abrufen können, jedoch einige andere Teile der QNetwork-API verwenden:

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

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

Diese Beispiele zeigen eine bessere Methode zum Umgang mit den TCP -Daten und wenn die Puffer voll sind, und eine bessere Fehlerbehandlung mit einer höheren API.

Wenn Sie immer noch die untergeordnete API verwenden möchten, finden Sie hier einen Beitrag, mit dem Sie die Puffer gut behandeln können:

Führen Sie in Ihrer readSocketData() so etwas aus:

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

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

BEARBEITEN: Zusätzliche Beispiele für die Interaktion mit 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

Hoffentlich hilft das.

0
phyatt

Das gleiche Problem hatte ich sofort mit dem readyRead-Slot. Ich stimme der akzeptierten Antwort nicht zu. Es löst das Problem nicht. Die Verwendung von bytesAvailable als Amartel beschrieben war die einzige zuverlässige Lösung, die ich gefunden habe. Qt :: QueuedConnection hatte keine Auswirkung. Im folgenden Beispiel deserialisiere ich einen benutzerdefinierten Typ, sodass Sie leicht eine minimale Bytegröße vorhersagen können. Es fehlen niemals Daten.

void MyFunExample::readyRead()
{
    bool done = false;

    while (!done)
    {

        in_.startTransaction();

        DataLinkListStruct st;

        in_ >> st;

        if (!in_.commitTransaction())
            qDebug() << "Failed to commit transaction.";

        switch (st.type)
        {
        case  DataLinkXmitType::Matrix:

            for ( int i=0;i<st.numLists;++i)
            {
                for ( auto it=st.data[i].begin();it!=st.data[i].end();++it )
                {
                    qDebug() << (*it).toString();
                }
            }
            break;

        case DataLinkXmitType::SingleValue:

            qDebug() << st.value.toString();
            break;

        case DataLinkXmitType::Map:

            for (auto it=st.mapData.begin();it!=st.mapData.end();++it)
            {
                qDebug() << it.key() << " == " << it.value().toString();
            }
            break;
        }

        if ( client_->QIODevice::bytesAvailable() < sizeof(DataLinkListStruct) )
            done = true;
    }
}   
0
user761576

Wenn eine QProgressDialog angezeigt werden soll, während Daten von einem Socket empfangen werden, funktioniert dies nur, wenn QApplication::processEvents() gesendet wird (z. B. durch die QProgessDialog::setValue(int)-Methode). Dies führt natürlich zum Verlust von readyRead-Signalen, wie oben erwähnt.

Mein Workaround war also eine while-Schleife, die den Befehl processEvents enthält, wie zum Beispiel:

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

Wenn der Steckplatz einmal aufgerufen wird, können zusätzliche readyRead-Signale ignoriert werden, da bytesAvailable() nach dem Aufruf von processEvents immer die tatsächliche Nummer zurückgibt. Erst beim Anhalten des Streams endet die while-Schleife. Aber das nächste readReady wird nicht übersehen und startet es erneut.

0
landydoc

Das Problem ist sehr interessant.

In meinem Programm ist der Einsatz von QTcpSocket sehr intensiv. Ich habe also die gesamte Bibliothek geschrieben, die ausgehende Daten in Pakete mit einem Header, einer Datenkennung, einer Paketindexnummer und einer maximalen Größe aufteilt, und wenn die nächsten Daten kommen, weiß ich genau, wo sie hingehören. Selbst wenn ich etwas vermisse, wenn die nächste readyRead kommt, liest der Empfänger alles und verfasst die empfangenen Daten korrekt. Wenn die Kommunikation zwischen Ihren Programmen nicht so intensiv ist, können Sie dasselbe tun, aber mit dem Timer (der nicht sehr schnell ist, aber das Problem löst).

Über Ihre Lösung Ich denke nicht, dass es besser ist als das:

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

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

Das Problem beider Methoden ist der Code direkt nach dem Verlassen des Steckplatzes, jedoch bevor er das Signal wieder ausgibt.

Sie könnten sich auch mit Qt::QueuedConnection verbinden.

0
Amartel