Client-serveur : Le serveur

Création des fichiers

Le code

main.cpp

Modifier dans un premier temps main.cpp (ce code ressemble au code minimim classique déjà vu):

#include <QApplication>

#include "serverwin.h"


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

{

   QApplication app(argc, argv);


   serverwin fenetre; //on créé une instance de l'objet serverwin

   fenetre.show(); //et on l'affiche


   return app.exec();

}

serverwin.h

Le code présenté ici a été écrit avec QT version 4sur un Raspberry Pi:

#ifndef SERVERWIN_H

#define SERVERWIN_H


#include <QtGui> //pour une version QT5 remplacer QtGui par QtWidgets

#include <QtNetwork>


class serverwin : public QWidget        //description de l'objet serverwin

{

   Q_OBJECT

public:

   serverwin();

   void sendtoclient(const QString &message);

private slots:  //les slots de gestion des évènements

   void connexion();

   void getdata();

   void deconnexion();

   QString getIPaddress();


private:

   QLabel *serverstatus;//affiche l'état du serveur

   QLabel *messagelbl; //affiche le message du client

   QPushButton *quitBtn;

   QTcpServer *server;

   QList<QTcpSocket *> client;

   quint16 tailleMessage;

};


#endif // SERVERWIN_H

server.cpp

C'est là que les choses se compliquent :

#include "serverwin.h"


serverwin::serverwin()

{

 //------- Construction de la fenetre du serveur ----------------------------------

   serverstatus = new QLabel; // étiquette (label) qui affiche le port et l'adresse du serveur

   messagelbl = new QLabel; //étiquette qui affiche le message du client

   quitBtn = new QPushButton("Quitter le serveur"); //le bouton pour quitter le serveur

   connect(quitBtn, SIGNAL(clicked()),qApp, SLOT(quit())); //la gestion du clic sur le bouton quitter

//pour que les widgets soient bien placées par rapport à la fenêtre on utilise un Layout

// et on place les widgest à l'interieur

   QVBoxLayout *layout = new QVBoxLayout;

   layout->addWidget(serverstatus);

   layout->addWidget(messagelbl);

   layout->addWidget(quitBtn);

   setLayout(layout);

   setWindowTitle("Serveur");


Dans le partie suivante, on créé une instance de l'objet TCPServer qui est le coeur du disposif.

Il s'agit de l'initialisation du serveur. Il est prêt à accepter tous les clients (any) qui communiquent avec lui via le port précisé (ici 40110)

Si le port est déjà utilisé par un serveur ou s'il n'y a pas d'interface réseau, e serveur ne peut pas s'initialiser.

Le numéro de port doit être compris en général entre 1024 et 65535 (beacoup de numéros sont déjà occupés avant 1024.

 //------- Gestion du serveur ------------------------------------------------------

   server=new QTcpServer(this);

   messagelbl->setText("Attente de client");

   if (!server->listen(QHostAddress::Any, 40110)) //utilisation du port 40110 pour le serveur

   { //erreur lors du démarrage du serveur

       serverstatus->setText(("Le serveur n'a pas pu être démarré :<br />")+

              server->errorString());

   }

   else

   { //le serveur a réussi à s'initialiser

       serverstatus->setText(("Le serveur a &eacute;t&eacute; d&eacute;marr&eacute; avec l'adresse <strong>")+

                             getIPaddress() +                            

               ("</strong><br />  et le num&eacute;ro de port ")+

                             QString::number(server->serverPort())+("</strong>.<br />"));

       //on autorise la connexion d'un client. S'il ne doit y avoir qu'un seul client il faudra modifier cette ligne

       connect(server, SIGNAL(newConnection()),this,SLOT(connexion()));

   }

   tailleMessage = 0;//comme il n'y a pas encore eu de message la taille du message est nulle

}

Il n'est pas aisé de trouver l'adresse IP d'un ordinateur, car il y en a plusieurs (IPv4, IPv6, localhost, etc...

Cette fonction recherche l'adresse dans un réseau donné.

//*************** Rend l'adresse IP *******************************************************************************

QString serverwin::getIPaddress()

{//On part du principe que le réseau est en 192.168.x.y

   QStringList adresse;

   QString resultat;

   foreach(QHostAddress address, QNetworkInterface::allAddresses())

//allAddresses rend toutes les adresses IP. Il faut filtrer

   {

       if (address.isInSubnet(QHostAddress::parseSubnet("192.168.0.0/16")))

       {

           resultat = address.toString();

           return resultat;

       }


   }

               return "Adresse Introuvable";//Le serveur n'est pas dans le même réseau peut-être

}


Un socket est une connexion entre un client et un serveur. Lorsque le serveur autorise la connexion, il créé un socket TCP qui va lui permettre d'identifier le lient lors des échanges.

//************** Procedure de connexion ***************************************************************************

void serverwin::connexion()

{

   sendtoclient("<em> Le client est connecté </em>");

   QTcpSocket *nouveauClient = server->nextPendingConnection();

   client << nouveauClient;

   connect(nouveauClient, SIGNAL(readyRead()), this, SLOT(getdata()));

   connect(nouveauClient, SIGNAL(disconnected()), this, SLOT(deconnexion()));

}

//************** Procedure de récupération du message*******************************************************

void serverwin::getdata()

{

   QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());

   if (socket==0) //on n'a pas trouvé l'émetteur du message et on quitte

       return;

   QDataStream in(socket);

   if (tailleMessage == 0)

   {

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

               return;//on n'a pas recu la taille -> on quitte

       in>> tailleMessage; //on récupère la taille reçue

   }

   //on verifie que le message est complet

   if (socket->bytesAvailable() < tailleMessage)

       return; //on n'a pas tout recu on ne peut pas traiter l'info. On quitte

   QString message;

   in>>message;

   sendtoclient(message); //on envoie le message au(x) client en echo (option bien sûr)

   messagelbl->clear();     //on affiche le message sur la fenêtre du serveur

   messagelbl->setText(message);

   tailleMessage=0;     //on remet la variable de taille à 0

}

//************** Procedure de déconnexion du client *********************************************************

void serverwin::deconnexion()

{

   sendtoclient("<em> Un client vient de se d&eacute;connecter </em>");

   //On détermine le client (s'il y en a plusieurs)

   QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());

   if (socket==0)   //on n'a pas trouvé le client -> on quitte la procedure

       return;

   client.removeOne(socket);

   socket->deleteLater();

}

Pour envoyer un message au client, on utilise l'objet de flux binaire QDataStream qui envoie des données à un interface d'E/S décrit par l'objet QIODevice

//************** Procedure d'envoie de message au client **************************************************

void serverwin::sendtoclient(const QString &message)

{

   QByteArray paquet;

out est une instance  de QDataStream qui transmet un tableau d'octets : paquet.

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

   out << (quint16) 0;// 0 au début du paquet pour réserver la place nécessaire pour le nbre de paquets

   out <<message; //on écrit le message

   out.device()->seek(0); //on se remet au debut de paquet

   out <<(quint16)(paquet.size() - sizeof(quint16)); //on remplace le 0

   //on envoie aux clients

   for

           (int i=0; i<client.size();i++)

   {

       client[i]->write(paquet); //on envoie les paquest de données au client

   }


}