Client Fortune

Démontre comment créer un client pour un service réseau.

Cet exemple utilise QTcpSocket et est destiné à être exécuté en même temps que l'exemple Fortune Server ou l'exemple Threaded Fortune Server.

Capture d'écran de l'exemple du client Fortune

Cet exemple utilise un simple protocole de transfert de données basé sur QDataStream pour demander une ligne de texte à un serveur Fortune (de l'exemple Fortune Server ). Le client demande une fortune en se connectant simplement au serveur. Le serveur répond alors par une adresse QString qui contient le texte de la fortune.

QTcpSocket supporte deux approches générales de la programmation en réseau :

  • L'approche asynchrone (non bloquante). Les opérations sont programmées et exécutées lorsque le contrôle revient dans la boucle d'événements de Qt. Lorsque l'opération est terminée, QTcpSocket émet un signal. Par exemple, QTcpSocket::connectToHost() revient immédiatement, et lorsque la connexion a été établie, QTcpSocket émet connected().
  • L'approche synchrone (bloquante). Dans les applications non-GUI et multithread, vous pouvez appeler les fonctions waitFor...() (par exemple, QTcpSocket::waitForConnected()) pour suspendre le thread appelant jusqu'à ce que l'opération soit terminée, au lieu de vous connecter à des signaux.

Dans cet exemple, nous allons démontrer l'approche asynchrone. L'exemple Blocking Fortune Client illustre l'approche synchrone.

Notre classe contient des données et quelques emplacements privés :

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

Outre les widgets qui composent l'interface graphique, les membres de données comprennent un pointeur QTcpSocket, un objet QDataStream qui opère sur la socket et une copie du texte de fortune actuellement affiché.

Le socket est initialisé dans le constructeur du client. Nous passerons le widget principal comme parent, de sorte que nous n'aurons pas à nous soucier de la suppression de la socket :

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

Le protocole est basé sur QDataStream, nous définissons donc le périphérique de flux sur le socket nouvellement créé. Nous définissons ensuite explicitement la version du protocole du flux à QDataStream::Qt_6_5 pour nous assurer que nous utilisons la même version que le serveur de fortune, quelle que soit la version de Qt que le client et le serveur utilisent.

Les seuls signaux QTcpSocket dont nous avons besoin dans cet exemple sont QTcpSocket::readyRead(), qui indique que des données ont été reçues, et QTcpSocket::errorOccurred(), que nous utiliserons pour détecter toute erreur de connexion :

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

En cliquant sur le bouton Get Fortune, vous invoquez le slot requestNewFortune():

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

Comme nous autorisons l'utilisateur à cliquer sur Get Fortune avant que la connexion précédente n'ait fini de se fermer, nous commençons par interrompre la connexion précédente en appelant QTcpSocket::abort(). (Sur un socket non connecté, cette fonction ne fait rien.) Nous procédons ensuite à la connexion au serveur de fortune en appelant QTcpSocket::connectToHost(), en passant le nom d'hôte et le port de l'interface utilisateur comme arguments.

À la suite de l'appel à connectToHost(), l'une des deux choses suivantes peut se produire :

  • La connexion est établie. Dans ce cas, le serveur nous enverra une fortune. QTcpSocket émettra readyRead() chaque fois qu'il recevra un bloc de données.
  • Une erreur se produit. Nous devons informer l'utilisateur si la connexion a échoué ou a été interrompue. Dans ce cas, QTcpSocket émettra errorOccurred(), et Client::displayError() sera appelé.

Commençons par le cas 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);
}

Nous affichons toutes les erreurs dans une boîte de dialogue à l'aide de QMessageBox::information(). QTcpSocket::RemoteHostClosedError est ignoré silencieusement, car le protocole du serveur de fortune se termine par la fermeture de la connexion par le serveur.

Passons maintenant à l'alternative readyRead(). Ce signal est connecté à Client::readFortune():

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

Le protocole TCP est basé sur l'envoi d'un flux de données, il ne faut donc pas s'attendre à recevoir l'intégralité de la fortune en une seule fois. En particulier sur un réseau lent, les données peuvent être reçues en plusieurs petits fragments. QTcpSocket met en mémoire tampon toutes les données entrantes et émet readyRead() pour chaque nouveau bloc qui arrive, et c'est à nous de nous assurer que nous avons reçu toutes les données dont nous avons besoin avant de commencer à les analyser.

Pour ce faire, nous utilisons une transaction de lecture QDataStream. Elle continue à lire les données du flux dans une mémoire tampon interne et les reprend en cas de lecture incomplète. Nous commençons par appeler startTransaction() qui réinitialise également le statut du flux pour indiquer que de nouvelles données ont été reçues sur le socket. Nous utilisons l'opérateur de streaming de QDataStream pour lire la fortune de la socket dans un QString. Une fois la lecture terminée, nous achevons la transaction en appelant QDataStream::commitTransaction(). Si nous n'avons pas reçu un paquet complet, cette fonction restaure les données du flux à la position initiale, après quoi nous pouvons attendre un nouveau signal readyRead().

Après une transaction de lecture réussie, nous appelons QLabel::setText() pour afficher la fortune.

Exemple de projet @ code.qt.io

Voir aussi Fortune Server et Blocking 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.