Client-serveur : Le serveur

Création des fichiers

  • Créer une application vide appelée myserver
    • Ajouter un fichier main.cpp
    • Ajouter un fichier serverwin.cpp
    • Ajouter un fichier serverwin.h

  • Modifier le contenu du fichier serverwin.pro pour y rajouter network et adapter le commande de compilation (makefile) au réseau

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

    }


}