Création du projet client

Créer un nouveau projet sous QT :

Le qtCreator se trouve dans le répertoire :

C:\Qt\Qt5.12.9\Tools\QtCreator\bin\


Créez un nouveau projet Qt Widgets Application constitué de 3 fichiers :

  • main.cpp
  • FenClient.cpp
  • FenClient.h



Donner comme nom au projet QtConversationServeur comme suit :

Faites suivant (Build system).


Puis Choisissez dans Base class : QWidget  et dans Class name : FenClient

Faites suivant (Translation).

Faites suivant (Kits).

Puis Terminer

Vous obtenez ceci :


Voici la fenêtre de client que l'on veut coder :


Les différents objets sont :



NB :



Dans la fenêtre Objet, ajouter les deux slots d'appui sur les boutons comme fait sur le serveur pour le PushButton : pbConnexion et pbEnvoyer. Clic droit sur le bouton puis aller au slot et clicked().


Client.pro

Ajouter la ligne suivante pour avoir le support réseau au projet :

QT += widgets network

main.cpp

Revenons au code. Le main est toujours très simple et sans originalité : il ouvre la fenêtre.

#include "fenclient.h"


#include <QApplication>


int main(int argc, char *argv[])

{

    QApplication a(argc, argv);

    FenClient w;

    w.show();

    return a.exec();

}


FenClient.h

Je vais ici fonctionner un peu différemment en utilisant un héritage multiple, une notion que nous n'avons pas vue précédemment mais qui est assez simple à comprendre : la classe va hériter de 2 classes. L'intérêt de ce double héritage est d'éviter de devoir mettre le préfixe ui-> partout dans le code. 

#ifndef FENCLIENT_H

#define FENCLIENT_H


#include <QtWidgets>

#include <QtNetwork>

#include "ui_FenClient.h"



class FenClient : public QWidget, private Ui::FenClient

{

    Q_OBJECT


    public:

        FenClient(QWidget *parent = nullptr);

           ~FenClient();


    private slots:

        void on_boutonConnexion_clicked();

        void on_boutonEnvoyer_clicked();

        void on_message_returnPressed();

        void donneesRecues();

        void connecte();

        void deconnecte();

        void erreurSocket(QAbstractSocket::SocketError erreur);


    private:

        QTcpSocket *socket; // Représente le serveur

        quint16 tailleMessage;

};


#endif

Notez qu'on inclut ui_FenClient.h pour réutiliser la fenêtre générée.

Notre fenêtre comporte pas mal de slots qu'il va falloir implémenter, heureusement ils seront assez simples. On utilise les autoconnect pour les 3 premiers d'entre eux pour gérer les évènements de la fenêtre :

  • on_boutonConnexion_clicked() : appelé lorsqu'on clique sur le bouton "Connexion" et qu'on souhaite donc se connecter au serveur.
  • on_boutonEnvoyer_clicked() : appelé lorsqu'on clique sur "Envoyer" pour envoyer un message dans le Chat.
  • on_message_returnPressed() : appelé lorsqu'on appuie sur la touche "Entrée" lorsqu'on rédige un message. Comme cela revient au même que on_boutonEnvoyer_clicked(), on appellera cette méthode directement pour éviter d'avoir à écrire 2 fois le même code.
  • donneesRecues() : appelé lorsqu'on reçoit un sous-paquet du serveur. Ce slot sera très similaire à celui du serveur qui possède le même nom.
  • connecte() : appelé lorsqu'on vient de réussir à se connecter au serveur.
  • deconnecte() : appelé lorsqu'on vient de se déconnecter du serveur.
  • erreurSocket(QAbstractSocket::SocketError erreur) : appelé lorsqu'il y a eu une erreur sur le réseau (connexion au serveur impossible par exemple).

En plus de ça, on a 2 attributs à manipuler :

  • QTcpSocket *socket : une socket qui représentera la connexion au serveur. On utilisera cette socket pour envoyer des paquets au serveur, par exemple lorsque l'utilisateur veut envoyer un message.
  • quint16 tailleMessage : permet à l'objet de se "souvenir" de la taille du message qu'il est en train de recevoir dans son slot donneesRecues(). Il a la même utilité que sur le serveur.

FenClient.cpp

Le constructeur

Notre constructeur doit appeler setupUi() dès le début pour mettre en place les widgets sur la fenêtre. C'est justement là qu'on gagne du temps grâce à Qt Designer : on n'a pas à coder le placement des widgets sur la fenêtre.

FenClient::FenClient()

{

    ui->setupUi(this);


    socket = new QTcpSocket(this);

    connect(socket, SIGNAL(readyRead()), this, SLOT(donneesRecues()));

    connect(socket, SIGNAL(connected()), this, SLOT(connecte()));

    connect(socket, SIGNAL(disconnected()), this, SLOT(deconnecte()));

    connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(erreurSocket(QAbstractSocket::SocketError)));


    tailleMessage = 0;

}

En plus de setupUi(), on fait quelques initialisations supplémentaires indispensables :

  • On crée l'objet de type QTcpSocket qui va représenter la connexion au serveur.
  • On connecte les signaux qu'il est susceptible d'envoyer à nos slots personnalisés.
  • On met tailleMessage à 0 pour permettre la réception de nouveaux messages.

Notez qu'on ne se connecte pas au serveur dans le constructeur. On prépare juste la socket, mais on ne fera la connexion que lorsque le client aura cliqué sur le bouton "Connexion" (il faut bien lui laisser le temps de rentrer l'adresse IP du serveur !).

Slot on_boutonConnexion_clicked()

Ce slot se fait appeler dès que l'on a cliqué sur le bouton "Connexion" en haut de la fenêtre.

// Tentative de connexion au serveur

void FenClient::on_pbConnexion_clicked()

{

    // On annonce sur la fenêtre qu'on est en train de se connecter

    tbMessage->append(tr("<em>Tentative de connexion en cours...</em>"));

    pbConnexion->setEnabled(false);


    socket->abort(); // On désactive les connexions précédentes s'il y en a

    socket->connectToHost(leIPServeur->text(), sbPortServeur->value()); // On se connecte au serveur demandé

}

  1. Dans un premier temps, on affiche sur la zone de messages "listeMessages" au centre de la fenêtre que l'on est en train d'essayer de se connecter.
  2. On désactive temporairement le bouton "Connexion" pour empêcher au client de retenter une connexion alors qu'une tentative de connexion est déjà en cours.
  3. Si la socket est déjà connectée à un serveur, on coupe la connexion avec abort(). Si on n'était pas connecté, cela n'aura aucun effet, mais c'est par sécurité pour que l'on ne soit pas connecté à 2 serveurs à la fois.
  4. On se connecte enfin au serveur avec la méthode connectToHost(). On utilise l'IP et le port demandés par l'utilisateur dans les champs de texte en haut de la fenêtre.
Slot on_boutonEnvoyer_clicked()

Ce slot est appelé lorsqu'on essaie d'envoyer un message (le bouton "Envoyer" en bas à droite a été cliqué).

// Envoi d'un message au serveur

void FenClient::on_boutonEnvoyer_clicked()

{

    QByteArray paquet;

    QDataStream out(&paquet, QIODevice::WriteOnly);


    // On prépare le paquet à envoyer

    QString messageAEnvoyer = tr("<strong>") + pseudo->text() +tr("</strong> : ") + message->text();


    out << (quint16) 0;

    out << messageAEnvoyer;

    out.device()->seek(0);

    out << (quint16) (paquet.size() - sizeof(quint16));


    socket->write(paquet); // On envoie le paquet


    message->clear(); // On vide la zone d'écriture du message

    message->setFocus(); // Et on remet le curseur à l'intérieur

}

Ce code est similaire à celui de la méthode envoyerATous() du serveur. Il s'agit d'un envoi de données sur le réseau.

  1. On prépare un QByteArray dans lequel on va écrire le paquet qu'on veut envoyer.
  2. On construit ensuite la QString contenant le message à envoyer. Vous noterez qu'on met le nom de l'auteur et son texte directement dans la même QString. Idéalement, il vaudrait mieux séparer les deux pour avoir un code plus logique et plus modulable, mais cela aurait compliqué le code de ce chapitre bien délicat, donc ça sera dans les améliorations à faire à la fin.
  3. On calcule la taille du message.
  4. On envoie le paquet ainsi créé au serveur en utilisant la socket qui le représente et sa méthode write().
  5. On efface automatiquement la zone d'écriture des messages en bas pour qu'on puisse en écrire un nouveau et on donne le focus à cette zone immédiatement pour que le curseur soit placé dans le bon widget.
Slot on_message_returnPressed()

Ce slot est appelé lorsqu'on a appuyé sur la touche "Entrée" après avoir rédigé un message.
Cela a le même effet qu'un clic sur le bouton "Envoyer", nous appelons donc le slot que nous venons d'écrire :

// Appuyer sur la touche Entrée a le même effet que cliquer sur le bouton "Envoyer"

void FenClient::on_message_returnPressed()

{

    on_boutonEnvoyer_clicked();

}

Slot donneesRecues()

Voilà à nouveau le slot compliqué, idem le serveur ! la réception de données fonctionne de la même manière :

// On a reçu un paquet (ou un sous-paquet)

void FenClient::donneesRecues()

{

    /* Même principe que lorsque le serveur reçoit un paquet :

    On essaie de récupérer la taille du message

    Une fois qu'on l'a, on attend d'avoir reçu le message entier (en se basant sur la taille annoncée tailleMessage)

    */

    QDataStream in(socket);


    if (tailleMessage == 0)

    {

        if (socket->bytesAvailable() < (int)sizeof(quint16))

             return;


        in >> tailleMessage;

    }


    if (socket->bytesAvailable() < tailleMessage)

        return;



    // Si on arrive jusqu'à cette ligne, on peut récupérer le message entier

    QString messageRecu;

    in >> messageRecu;


    // On affiche le message sur la zone de Chat

    listeMessages->append(messageRecu);


    // On remet la taille du message à 0 pour pouvoir recevoir de futurs messages

    tailleMessage = 0;

}

La seule différence ici en fait, c'est qu'on affiche le message reçu dans la zone de Chat à la fin :listeMessages->append(messageRecu);

Slot connecte()

Ce slot est appelé lorsqu'on a réussi à se connecter au serveur.

// Ce slot est appelé lorsque la connexion au serveur a réussi

void FenClient::connecte()

{

    listeMessages->append(tr("<em>Connexion réussie !</em>"));

    boutonConnexion->setEnabled(true);

}

Tout bêtement, on se contente d'afficher "Connexion réussie" dans la zone de Chat pour que le client sache qu'il est bien connecté au serveur.
On réactive aussi le bouton "Connexion" qu'on avait désactivé, pour permettre une nouvelle connexion à un autre serveur.

Slot deconnecte()

Ce slot est appelé lorsqu'on est déconnecté du serveur.

// Ce slot est appelé lorsqu'on est déconnecté du serveur

void FenClient::deconnecte()

{

    listeMessages->append(tr("<em>Déconnecté du serveur</em>"));

}

On affiche juste un message sur la zone de texte pour que le client soit au courant.

Slot erreurSocket()

Ce slot est appelé lorsque la socket a rencontré une erreur.

// Ce slot est appelé lorsqu'il y a une erreur

void FenClient::erreurSocket(QAbstractSocket::SocketError erreur)

{

    switch(erreur) // On affiche un message différent selon l'erreur qu'on nous indique

    {

        case QAbstractSocket::HostNotFoundError:

            listeMessages->append(tr("<em>ERREUR : le serveur n'a pas pu être trouvé. Vérifiez l'IP et le port.</em>"));

            break;

        case QAbstractSocket::ConnectionRefusedError:

            listeMessages->append(tr("<em>ERREUR : le serveur a refusé la connexion. Vérifiez si le programme \"serveur\" a bien été lancé. Vérifiez aussi l'IP et le port.</em>"));

            break;

        case QAbstractSocket::RemoteHostClosedError:

            listeMessages->append(tr("<em>ERREUR : le serveur a coupé la connexion.</em>"));

            break;

        default:

            listeMessages->append(tr("<em>ERREUR : ") + socket->errorString() + tr("</em>"));

    }


    boutonConnexion->setEnabled(true);

}

La raison de l'erreur est passée en paramètre. Elle est de type QAbstractSocket::SocketError (c'est une énumération).

On fait un switch pour afficher un message différent en fonction de l'erreur. Toutes les erreurs possibles n'ont pas été traitées, lisez la doc pour connaître les autres raisons d'erreurs que l'on peut gérer.

La plupart des erreurs que je gère ici sont liées à la connexion au serveur. J'affiche un message intelligible en français pour que l'on comprenne la raison de l'erreur.
Le cas "default" est appelé pour les erreurs que je n'ai pas gérées. J'affiche le message d'erreur envoyé par la socket.


FenClient.cpp en entier


Test du client de conversation et améliorations

Testez le Chat sur votre poste et sur un autre poste en simultané.

  • Un serveur
  • Deux clients

Cela va nous permettre de simuler une conversation en interne sur notre ordinateur. Cela utilisera le réseau, mais à l'intérieur de votre propre machine.

Créé avec HelpNDoc Personal Edition: Générateur de documentation d'aide HTML gratuit