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

QTcpSocket L'approche asynchrone (non bloquante) soutient deux approches générales de la programmation réseau :
- L'approche asynchrone (non bloquante). Les opérations sont programmées et exécutées lorsque le contrôle revient à 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.
L'implémentation est très similaire à l'exemple du client Fortune, mais au lieu d'avoir QTcpSocket comme membre de la classe principale, effectuant un réseau asynchrone dans le thread principal, nous ferons toutes les opérations de réseau dans un thread séparé et utiliserons l'API bloquante de QTcpSocket.
Le but de cet exemple est de démontrer un modèle que vous pouvez utiliser pour simplifier votre code de mise en réseau, sans perdre la réactivité de votre interface utilisateur. L'utilisation de l'API réseau bloquante de Qt Network permet souvent de simplifier le code, mais en raison de son comportement bloquant, elle ne doit être utilisée que dans des threads non GUI afin d'éviter que l'interface utilisateur ne se fige. Mais contrairement à ce que beaucoup pensent, l'utilisation de threads avec QThread n'ajoute pas nécessairement une complexité ingérable à votre application.
Nous commencerons par la classe FortuneThread, qui gère le code du réseau.
class FortuneThread : public QThread { Q_OBJECT public: FortuneThread(QObject *parent = nullptr); ~FortuneThread(); void requestNewFortune(const QString &hostName, quint16 port); void run() override; signals: void newFortune(const QString &fortune); void error(int socketError, const QString &message); private: QString hostName; quint16 port; QMutex mutex; QWaitCondition cond; bool quit; };
FortuneThread est une sous-classe de QThread qui fournit une API pour planifier les demandes de fortunes, et qui possède des signaux pour délivrer les fortunes et signaler les erreurs. Vous pouvez appeler requestNewFortune() pour demander une nouvelle fortune, et le résultat est transmis par le signal newFortune(). En cas d'erreur, le signal error() est émis.
Il est important de noter que requestNewFortune() est appelé depuis le thread principal de l'interface graphique, mais que les valeurs de nom d'hôte et de port qu'il stocke seront accessibles depuis le thread de FortuneThread. Comme nous lirons et écrirons les membres de données de FortuneThread à partir de différents threads simultanément, nous utilisons QMutex pour synchroniser l'accès.
void FortuneThread::requestNewFortune(const QString &hostName, quint16 port) { QMutexLocker locker(&mutex); this->hostName = hostName; this->port = port; if (!isRunning()) start(); else cond.wakeOne(); }
La fonction requestNewFortune() stocke le nom d'hôte et le port du serveur Fortune en tant que données membres, et nous verrouillons le mutex avec QMutexLocker pour protéger ces données. Nous démarrons ensuite le thread, à moins qu'il ne soit déjà en cours d'exécution. Nous reviendrons plus tard sur l'appel à QWaitCondition::wakeOne().
void FortuneThread::run() { mutex.lock(); QString serverName = hostName; quint16 serverPort = port; mutex.unlock();
Dans la fonction run(), nous commençons par acquérir le verrou du mutex, nous récupérons le nom d'hôte et le port à partir des données membres, puis nous relâchons le verrou. Le cas contre lequel nous nous protégeons est celui où requestNewFortune() pourrait être appelé en même temps que nous récupérons ces données. QString est réentrant mais pas thread-safe, et nous devons également éviter le risque improbable de lire le nom d'hôte d'une requête, et le port d'une autre. Et comme vous l'avez peut-être deviné, FortuneThread ne peut gérer qu'une seule requête à la fois.
La fonction run() entre maintenant dans une boucle :
while (!quit) { const int Timeout = 5 * 1000; QTcpSocket socket; socket.connectToHost(serverName, serverPort);
La boucle continuera à demander des fortunes tant que quit est faux. Nous commençons notre première requête en créant un QTcpSocket sur la pile, puis nous appelons connectToHost(). Cela démarre une opération asynchrone qui, après le retour du contrôle à la boucle d'événements de Qt XML, fera en sorte que QTcpSocket émette connected() ou error().
if (!socket.waitForConnected(Timeout)) { emit error(socket.error(), socket.errorString()); return; }
Mais comme nous fonctionnons dans un thread non-GUI, nous n'avons pas à nous soucier de bloquer l'interface utilisateur. Ainsi, au lieu d'entrer dans une boucle d'événements, nous appelons simplement QTcpSocket::waitForConnected(). Cette fonction attendra, en bloquant le thread appelant, que QTcpSocket émette connected() ou qu'une erreur se produise. Si connected() est émis, la fonction renvoie true ; si la connexion a échoué ou s'est interrompue (ce qui, dans cet exemple, se produit au bout de 5 secondes), false est renvoyé. QTcpSocket::waitForConnected(), comme les autres fonctions de waitFor...(), fait partie de l'API bloquante de QTcpSocket.
Après cette déclaration, nous disposons d'une socket connectée avec laquelle nous pouvons travailler.
QDataStream in(&socket); in.setVersion(QDataStream::Qt_6_5); QString fortune;
Nous pouvons maintenant créer un objet QDataStream, en passant la socket au constructeur de QDataStream et, comme dans les autres exemples de clients, nous définissons la version du protocole de flux à QDataStream::Qt_6_5.
do { if (!socket.waitForReadyRead(Timeout)) { emit error(socket.error(), socket.errorString()); return; } in.startTransaction(); in >> fortune; } while (!in.commitTransaction());
Nous lançons une boucle qui attend les données de la chaîne de fortune en appelant QTcpSocket::waitForReadyRead(). S'il renvoie un message faux, nous abandonnons l'opération. Après cette déclaration, nous lançons une transaction de lecture du flux. Nous quittons la boucle lorsque QDataStream::commitTransaction() renvoie un message vrai, ce qui signifie que le chargement de la chaîne de caractères a réussi. La fortune résultante est livrée en émettant newFortune() :
mutex.lock(); emit newFortune(fortune); cond.wait(&mutex); serverName = hostName; serverPort = port; mutex.unlock(); }
La dernière partie de notre boucle consiste à acquérir le mutex afin de pouvoir lire en toute sécurité les données de nos membres. Nous laissons ensuite le thread s'endormir en appelant QWaitCondition::wait(). À ce stade, nous pouvons revenir à requestNewFortune() et examiner de près l'appel à wakeOne() :
void FortuneThread::requestNewFortune(const QString &hostName, quint16 port) { ... if (!isRunning()) start(); else cond.wakeOne(); }
Ce qui s'est passé ici, c'est que le thread s'est endormi dans l'attente d'une nouvelle requête, et que nous avons dû le réveiller lorsqu'une nouvelle requête est arrivée. QWaitCondition est souvent utilisé dans les threads pour signaler un appel de réveil comme celui-ci.
FortuneThread::~FortuneThread() { mutex.lock(); quit = true; cond.wakeOne(); mutex.unlock(); wait(); }
Pour terminer la présentation de FortuneThread, voici le destructeur qui met quit à true, réveille le thread et attend qu'il se termine avant de revenir. Cela permet à la boucle while dans run() de terminer son itération actuelle. Lorsque run() revient, le thread se termine et est détruit.
Passons maintenant à la classe BlockingClient :
class BlockingClient : public QWidget { Q_OBJECT public: BlockingClient(QWidget *parent = nullptr); private slots: void requestNewFortune(); void showFortune(const QString &fortune); void displayError(int socketError, const QString &message); void enableGetFortuneButton(); private: QLabel *hostLabel; QLabel *portLabel; QLineEdit *hostLineEdit; QLineEdit *portLineEdit; QLabel *statusLabel; QPushButton *getFortuneButton; QPushButton *quitButton; QDialogButtonBox *buttonBox; FortuneThread thread; QString currentFortune; };
BlockingClient est très similaire à la classe Client de l'exemple Fortune Client, mais dans cette classe, nous stockons un membre FortuneThread au lieu d'un pointeur vers QTcpSocket. Lorsque l'utilisateur clique sur le bouton "Get Fortune", le même slot est appelé, mais son implémentation est légèrement différente :
connect(&thread, &FortuneThread::newFortune, this, &BlockingClient::showFortune); connect(&thread, &FortuneThread::error, this, &BlockingClient::displayError);
Nous connectons les deux signaux newFortune() et error() de notre FortuneThread (qui sont quelque peu similaires à QTcpSocket::readyRead() et QTcpSocket::error() dans l'exemple précédent) à requestNewFortune() et displayError().
void BlockingClient::requestNewFortune() { getFortuneButton->setEnabled(false); thread.requestNewFortune(hostLineEdit->text(), portLineEdit->text().toInt()); }
Le slot requestNewFortune() appelle FortuneThread::requestNewFortune(), qui planifie la demande. Lorsque le thread a reçu une nouvelle fortune et qu'il émet newFortune(), notre slot showFortune() est appelé :
void BlockingClient::showFortune(const QString &nextFortune) { if (nextFortune == currentFortune) { requestNewFortune(); return; } currentFortune = nextFortune; statusLabel->setText(currentFortune); getFortuneButton->setEnabled(true); }
Ici, nous affichons simplement la fortune que nous avons reçue en tant qu'argument.
Voir aussi Fortune Client et Fortune Server.
© 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.