Client Fortune
Montre comment créer un client pour un service réseau.
Cet exemple utilise QTcpSocket et est destiné à être exécuté en parallèle de l’exemple « Fortune Server » ou de l’exemple « Threaded Fortune Server ».

Cet exemple utilise un protocole de transfert de données simple basé sur l'QDataStream pour demander une ligne de texte à un serveur Fortune (issu de l'exemple « Fortune Server »). Le client demande une citation en se connectant simplement au serveur. Le serveur répond alors par un objet ` QString ` contenant le texte de la citation.
QTcpSocket prend en charge deux approches générales de la programmation réseau :
- L’approche asynchrone (non bloquante). Les opérations sont planifiées et exécutées lorsque le contrôle revient à la boucle d’événements de Qt. Une fois l’opération terminée, ` QTcpSocket ` émet un signal. Par exemple, ` QTcpSocket::connectToHost()` renvoie immédiatement un résultat, et une fois la connexion établie, ` QTcpSocket ` émet le signal ` connected()`.
- L'approche synchrone (bloquante). Dans les applications non graphiques et multithread, vous pouvez appeler les fonctions de gestion de la connexion (
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 présenter l’approche asynchrone. L’exemple « Blocking Fortune Client » illustre l’approche synchrone.
Notre classe contient des données et quelques slots 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 vers QTcpSocket, un objet QDataStream qui gère le socket, ainsi qu’une copie du texte de la prédiction actuellement affiché.
Le socket est initialisé dans le constructeur de la classe Client. Nous allons passer le widget principal en tant que parent, afin de ne pas avoir à nous soucier de la suppression du 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 la socket nouvellement créée. Nous définissons ensuite explicitement la version du protocole du flux sur QDataStream::Qt_6_5 afin de nous assurer que nous utilisons la même version que le serveur fortune, quelle que soit la version de Qt utilisée par le client et le serveur.
Les seuls signaux d’ 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 d’éventuelles erreurs de connexion :
...
connect(tcpSocket, &QIODevice::readyRead, this, &Client::readFortune);
connect(tcpSocket, &QAbstractSocket::errorOccurred,
...
}Cliquer sur le bouton « Get Fortune » déclenchera le slot « requestNewFortune() » :
void Client::requestNewFortune()
{
getFortuneButton->setEnabled(false);
tcpSocket->abort();
tcpSocket->connectToHost(hostCombo->currentText(),
portLineEdit->text().toInt());
}Comme nous permettons à l’utilisateur de cliquer sur « Get Fortune » avant que la connexion précédente ne soit complètement fermée, 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 « fortune » en appelant ` QTcpSocket::connectToHost()`, en passant le nom d’hôte et le port provenant de l’interface utilisateur en tant qu’arguments.
L’appel à ` connectToHost()` peut entraîner deux cas de figure :
- La connexion est établie. Dans ce cas, le serveur nous enverra une citation « 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ée.
Examinons d’abord le cas de 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é sans message, car le protocole du serveur Fortune se termine par la fermeture de la connexion par le serveur.
Passons maintenant à l’alternative readyRead(). Ce signal est lié à 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);
}Or, le protocole TCP repose sur l’envoi d’un flux de données ; on ne peut donc pas s’attendre à recevoir la citation « fortune » dans son intégralité 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 ; c’est à nous de nous assurer d’avoir reçu toutes les données nécessaires avant de commencer l’analyse.
Pour cela, nous utilisons une transaction de lecture QDataStream. Celle-ci lit en continu les données du flux dans un tampon interne et effectue un rollback en cas de lecture incomplète. Nous commençons par appeler startTransaction(), ce qui réinitialise également l’état du flux pour indiquer que de nouvelles données ont été reçues sur la socket. Nous poursuivons en utilisant l’opérateur de flux de ` QDataStream` pour lire la citation tirée de la socket dans un ` QString`. Une fois la lecture terminée, nous clôturons la transaction en appelant ` QDataStream::commitTransaction()`. Si nous n’avons pas reçu un paquet complet, cette fonction ramène les données du flux à leur position initiale, après quoi nous pouvons attendre un nouveau signal `readyRead()`.
Une fois la lecture effectuée avec succès, nous appelons QLabel::setText() pour afficher la citation.
Voir également Serveur de prédictions et Client de prédictions bloquant.
© 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.