Fortune-Client

Zeigt, wie man einen Client für einen Netzwerkdienst erstellt.

Dieses Beispiel verwendet QTcpSocket und sollte zusammen mit dem Fortune Server-Beispiel oder dem Threaded Fortune Server-Beispiel ausgeführt werden.

Screenshot of the Fortune Client example

Dieses Beispiel verwendet ein einfaches QDataStream-basiertes Datenübertragungsprotokoll, um eine Textzeile von einem Fortune-Server (aus dem Fortune-Server-Beispiel ) anzufordern. Der Client fordert ein Vermögen an, indem er sich einfach mit dem Server verbindet. Der Server antwortet daraufhin mit einer QString, die den Glückstext enthält.

QTcpSocket unterstützt zwei allgemeine Ansätze zur Netzwerkprogrammierung:

  • Der asynchrone (nicht-blockierende) Ansatz. Operationen werden geplant und ausgeführt, wenn die Kontrolle an die Ereignisschleife von Qt zurückkehrt. Wenn die Operation beendet ist, gibt QTcpSocket ein Signal aus. Zum Beispiel kehrt QTcpSocket::connectToHost() sofort zurück, und wenn die Verbindung hergestellt wurde, sendet QTcpSocket connected ().
  • Der synchrone (blockierende) Ansatz. In Nicht-GUI- und Multithread-Anwendungen können Sie die Funktionen waitFor...() (z. B. QTcpSocket::waitForConnected()) aufrufen, um den aufrufenden Thread anzuhalten, bis der Vorgang abgeschlossen ist, anstatt eine Verbindung zu Signalen herzustellen.

In diesem Beispiel wird der asynchrone Ansatz demonstriert. Das Beispiel " Blocking Fortune Client" veranschaulicht den synchronen Ansatz.

Unsere Klasse enthält einige Daten und ein paar private Slots:

class Client : public QDialog
{
    Q_OBJECT

public:
    explicit Client(QWidget *parent = nullptr);

private slots:
    void requestNewFortune();
    void readFortune();
    void displayError(QAbstractSocket::SocketError socketError);
    void enableGetFortuneButton();

private:
    QComboBox *hostCombo = nullptr;
    QLineEdit *portLineEdit = nullptr;
    QLabel *statusLabel = nullptr;
    QPushButton *getFortuneButton = nullptr;

    QTcpSocket *tcpSocket = nullptr;
    QDataStream in;
    QString currentFortune;
};

Neben den Widgets, die die grafische Benutzeroberfläche bilden, gehören zu den Datenmitgliedern ein QTcpSocket -Zeiger, ein QDataStream -Objekt, das auf dem Socket arbeitet, und eine Kopie des aktuell angezeigten Glückstextes.

Der Socket wird im Client-Konstruktor initialisiert. Wir übergeben das Hauptwidget als Elternteil, so dass wir uns nicht um das Löschen des Sockets kümmern müssen:

Client::Client(QWidget *parent)
    : QDialog(parent)
    , hostCombo(new QComboBox)
    , portLineEdit(new QLineEdit)
    , getFortuneButton(new QPushButton(tr("Get Fortune")))
    , tcpSocket(new QTcpSocket(this))
{
    ...
    in.setDevice(tcpSocket);
    in.setVersion(QDataStream::Qt_6_5);

Das Protokoll basiert auf QDataStream, also setzen wir das Stream-Gerät auf den neu erstellten Socket. Dann setzen wir die Protokollversion des Streams explizit auf QDataStream::Qt_6_5, um sicherzustellen, dass wir dieselbe Version wie der Fortune-Server verwenden, unabhängig davon, welche Version von Qt der Client und der Server verwenden.

Die einzigen QTcpSocket -Signale, die wir in diesem Beispiel benötigen, sind QTcpSocket::readyRead(), das signalisiert, dass Daten empfangen wurden, und QTcpSocket::errorOccurred(), das wir verwenden werden, um eventuelle Verbindungsfehler abzufangen:

    ...
    connect(tcpSocket, &QIODevice::readyRead, this, &Client::readFortune);
    connect(tcpSocket, &QAbstractSocket::errorOccurred,
    ...
}

Wenn Sie auf die Schaltfläche Get Fortune klicken, wird der Slot requestNewFortune() aufgerufen:

void Client::requestNewFortune()
{
    getFortuneButton->setEnabled(false);
    tcpSocket->abort();
    tcpSocket->connectToHost(hostCombo->currentText(),
                             portLineEdit->text().toInt());
}

Da wir dem Benutzer erlauben, auf Get Fortune zu klicken, bevor die vorherige Verbindung geschlossen wurde, brechen wir die vorherige Verbindung zunächst ab, indem wir QTcpSocket::abort() aufrufen. (Bei einem nicht verbundenen Socket bewirkt diese Funktion nichts.) Anschließend wird eine Verbindung zum Fortune-Server hergestellt, indem QTcpSocket::connectToHost() aufgerufen wird, wobei der Hostname und der Port der Benutzeroberfläche als Argumente übergeben werden.

Als Ergebnis des Aufrufs von connectToHost() kann eines von zwei Dingen passieren:

  • Die Verbindung wird aufgebaut. In diesem Fall sendet uns der Server ein Vermögen. QTcpSocket sendet jedes Mal, wenn es einen Datenblock empfängt, readyRead().
  • Ein Fehler tritt auf. Wir müssen den Benutzer informieren, wenn die Verbindung fehlgeschlagen ist oder unterbrochen wurde. In diesem Fall gibt QTcpSocket die Meldung errorOccurred() aus, und Client::displayError() wird aufgerufen.

Lassen Sie uns zuerst den Fall errorOccurred() durchgehen:

void Client::displayError(QAbstractSocket::SocketError socketError)
{
    switch (socketError) {
    case QAbstractSocket::RemoteHostClosedError:
        break;
    case QAbstractSocket::HostNotFoundError:
        QMessageBox::information(this, tr("Fortune Client"),
                                 tr("The host was not found. Please check the "
                                    "host name and port settings."));
        break;
    case QAbstractSocket::ConnectionRefusedError:
        QMessageBox::information(this, tr("Fortune Client"),
                                 tr("The connection was refused by the peer. "
                                    "Make sure the fortune server is running, "
                                    "and check that the host name and port "
                                    "settings are correct."));
        break;
    default:
        QMessageBox::information(this, tr("Fortune Client"),
                                 tr("The following error occurred: %1.")
                                 .arg(tcpSocket->errorString()));
    }

    getFortuneButton->setEnabled(true);
}

Wir zeigen alle Fehler in einem Dialog mit QMessageBox::information() an. QTcpSocket::RemoteHostClosedError wird stillschweigend ignoriert, da das Fortune-Server-Protokoll mit dem Schließen der Verbindung durch den Server endet.

Nun zu der Alternative readyRead(). Dieses Signal ist mit Client::readFortune() verbunden:

void Client::readFortune()
{
    in.startTransaction();

    QString nextFortune;
    in >> nextFortune;

    if (!in.commitTransaction())
        return;

    if (nextFortune == currentFortune) {
        QTimer::singleShot(0, this, &Client::requestNewFortune);
        return;
    }

    currentFortune = nextFortune;
    statusLabel->setText(currentFortune);
    getFortuneButton->setEnabled(true);
}

Da TCP auf dem Senden eines Datenstroms basiert, können wir nicht erwarten, dass wir das gesamte Vermögen in einem Zug erhalten. Vor allem in einem langsamen Netzwerk können die Daten in mehreren kleinen Fragmenten empfangen werden. QTcpSocket puffert alle eingehenden Daten und sendet readyRead() für jeden neu eintreffenden Block, und es ist unsere Aufgabe, sicherzustellen, dass wir alle benötigten Daten erhalten haben, bevor wir mit dem Parsing beginnen.

Zu diesem Zweck verwenden wir eine QDataStream Lese-Transaktion. Sie liest die Daten des Datenstroms in einen internen Puffer und rollt sie im Falle eines unvollständigen Lesevorgangs zurück. Wir beginnen mit dem Aufruf von startTransaction(), der auch den Stream-Status zurücksetzt, um anzuzeigen, dass neue Daten auf dem Socket empfangen wurden. Wir fahren fort, indem wir den Streaming-Operator von QDataStream verwenden, um das Vermögen vom Socket in eine QString zu lesen. Nach dem Lesen schließen wir die Transaktion ab, indem wir QDataStream::commitTransaction() aufrufen. Wenn wir kein komplettes Paket empfangen haben, stellt diese Funktion die Daten des Streams auf die Ausgangsposition zurück, woraufhin wir auf ein neues readyRead()-Signal warten können.

Nach einem erfolgreichen Lesevorgang rufen wir QLabel::setText() auf, um das Vermögen anzuzeigen.

Beispielprojekt @ code.qt.io

Siehe auch Fortune Server und Blocking Fortune Client.

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.