Fortune-Client

Veranschaulicht, wie ein Client für einen Netzwerkdienst erstellt wird.

Dieses Beispiel verwendet „ QTcpSocket “ und ist für die Ausführung zusammen mit dem Beispiel „Fortune-Server“ oder dem Beispiel „Threaded Fortune-Server“ vorgesehen.

Screenshot des Fortune-Client-Beispiels

Dieses Beispiel verwendet ein einfaches, auf dem „ QDataStream “ basierendes Datenübertragungsprotokoll, um eine Textzeile von einem Fortune-Server (aus dem Beispiel „Fortune Server“ ) anzufordern. Der Client fordert ein Sprichwort an, indem er einfach eine Verbindung zum Server herstellt. Der Server antwortet daraufhin mit einem „ QString “, der den Sprichworttext enthält.

QTcpSocket unterstützt zwei allgemeine Ansätze für die Netzwerkprogrammierung:

  • Den asynchronen (nicht blockierenden) Ansatz. Operationen werden eingeplant und ausgeführt, sobald die Kontrolle an die Ereignisschleife von Qt zurückgegeben wird. Ist die Operation abgeschlossen, sendet „ QTcpSocket “ ein Signal aus. Beispielsweise kehrt „ QTcpSocket::connectToHost()“ sofort zurück, und sobald die Verbindung hergestellt ist, sendet „ QTcpSocket “ das Signal „ connected()“ aus.
  • Der synchrone (blockierende) Ansatz. In Nicht-GUI- und Multithread-Anwendungen können Sie die waitFor...() -Funktionen (z. B. QTcpSocket::waitForConnected()) aufrufen, um den aufrufenden Thread anzuhalten, bis der Vorgang abgeschlossen ist, anstatt Signale zu abzufangen.

In diesem Beispiel demonstrieren wir den asynchronen Ansatz. 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, aus denen die Benutzeroberfläche besteht, umfassen die Datenelemente einen Zeiger auf ` QTcpSocket `, ein ` QDataStream `-Objekt, das auf dem Socket arbeitet, sowie eine Kopie des aktuell angezeigten Glücksspruchs.

Der Socket wird im Konstruktor von `Client` initialisiert. Wir übergeben das Haupt-Widget als übergeordnetes Element, damit 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, daher legen wir das Stream-Gerät auf den neu erstellten Socket fest. Anschließend 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 Qt-Version Client und Server nutzen.

Die einzigen „ QTcpSocket “-Signale, die wir in diesem Beispiel benötigen, sind „ QTcpSocket::readyRead()“, das anzeigt, dass Daten empfangen wurden, und „ QTcpSocket::errorOccurred()“, mit dem wir eventuelle Verbindungsfehler abfangen:

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

Ein Klick auf die Schaltfläche „ Get Fortune “ löst den Slot „ requestNewFortune() “ aus:

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 vollständig geschlossen wurde, brechen wir zunächst die vorherige Verbindung ab, indem wir ` QTcpSocket::abort()` aufrufen. (Bei einem nicht verbundenen Socket führt diese Funktion nichts aus.) Anschließend stellen wir eine Verbindung zum Fortune-Server her, indem wir ` QTcpSocket::connectToHost()` aufrufen und dabei den Hostnamen und den Port aus der Benutzeroberfläche als Argumente übergeben.

Durch den Aufruf von ` connectToHost()` kann eines von zwei Dingen geschehen:

  • Die Verbindung wird hergestellt. In diesem Fall sendet uns der Server ein „Fortune“. „ QTcpSocket “ gibt bei jedem Empfang eines Datenblocks „ readyRead()“ aus.
  • Es tritt ein Fehler auf. Wir müssen den Benutzer darüber informieren, wenn die Verbindung fehlgeschlagen ist oder unterbrochen wurde. In diesem Fall löst ` QTcpSocket ` die Funktion ` errorOccurred()` aus, und ` Client::displayError() ` wird aufgerufen.

Betrachten wir zunächst den Fall „ errorOccurred():

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 mithilfe von ` QMessageBox::information()` in einem Dialogfeld an. `QTcpSocket::RemoteHostClosedError` wird stillschweigend ignoriert, da das Fortune-Server-Protokoll damit endet, dass der Server die Verbindung schließt.

Nun zur Alternative „ readyRead()“. Dieses Signal ist mit „ Client::readFortune() “ verknüpft:

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 Versand eines Datenstroms basiert, können wir nicht davon ausgehen, das gesamte „Fortune“ auf einmal zu erhalten. Insbesondere in einem langsamen Netzwerk können die Daten in mehreren kleinen Fragmenten empfangen werden. ` QTcpSocket ` puffert alle eingehenden Daten und löst bei jedem neuen Block, der eintrifft, ` readyRead()` aus. Es ist unsere Aufgabe, sicherzustellen, dass wir alle benötigten Daten empfangen haben, bevor wir mit der Analyse beginnen.

Zu diesem Zweck verwenden wir eine ` QDataStream `-Lese-Transaktion. Sie liest kontinuierlich Stream-Daten in einen internen Puffer ein und führt im Falle eines unvollständigen Lesevorgangs ein Rollback durch. Wir beginnen mit dem Aufruf von `startTransaction()`, wodurch auch der Stream-Status zurückgesetzt wird, um anzuzeigen, dass neue Daten über den Socket empfangen wurden. Anschließend verwenden wir den Streaming-Operator von „ QDataStream “, um das Glücksspruch aus dem Socket in ein „ QString “ einzulesen. Nach dem Einlesen schließen wir die Transaktion durch den Aufruf von „ QDataStream::commitTransaction()“ ab. Falls wir kein vollständiges Paket empfangen haben, setzt diese Funktion die Stream-Daten auf die Ausgangsposition zurück, woraufhin wir auf ein neues „readyRead()“-Signal warten können.

Nach einer erfolgreichen Lesetransaktion rufen wir ` QLabel::setText()` auf, um das Glücksspruch anzuzeigen.

Beispielprojekt @ code.qt.io

Siehe auch „Fortune-Server “ und „Blockierender Fortune-Client“.

© 2026 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.