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 été démarré avec l'adresse <strong>")+
getIPaddress() +
("</strong><br /> et le numé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é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
}
}