Qt Chat multicast TCP

Tous les messages ne s'envoient pas à la suite

Le problème exposé dans ce sujet a été résolu.

Bonjour à tous !

Tout d’abord, oui j’ai déjà ouvert un topic sur cette histoire de chat multicast. Le problème est résolu et je viens pour une autre question, donc je me permet d’ouvrir un nouveau sujet, même si c’est le même projet à la base. Je n’ai pas non-plus amélioré mon code selon le post de Alexandre Laurent comme suggéré dans mon précédent sujet, car ça représente de nouvelles choses à apprendre, et pour l’instant je suis sur ce chat, je ne veux pas faire plusieurs choses en même temps.

Donc pour resituer, j’ai à réaliser le client et le serveur d’un chat multicast. J’ai mes 2 sous-projets qui compilent et qui s’exécutent sans que le compilateur ne râle. J’ai voulu essayer si tout fonctionnait. j’ai donc exécuté le client et le serveur en même temps sur le même ordinateur (le client se connecte à l’adresse 127.0.0.1) Et comme j’exécute depuis Qt Creator, je ne peux lancer qu’une seule instance du client. Si je teste dans ces conditions, ça fonctionne. J’envoie depuis le client, le serveur renvoie à tout le monde, donc au seul client connecté.

Je me suis quand même posé la question "que se passe-t-il si plusieurs clients envoient quelque chose en même temps ?" Comme j’ai un seul client d’actif, j’ai voulu simuler en faisant plusieurs envois depuis le serveur. Quand un client se connecte, le serveur envoie "un nouveau client vient de se connecter" à tout le monde. J’ai fait ma "simulation" en multipliant ce message. J’en envoie 3 en ajoutant 1, 2, et 3 à la fin. Le comportement que j’attend, c’est que le serveur envoie les 3 à la suite, donc dans le client avoir les 3 à la suite sans que l’utilisateur ne touche à rien.

Ce n’est pas ce qu’il se passe ! le client reçoit "un nouveau client vient de se connecter 1". Je tape "message 1" puis clique sur le bouton envoyer, et le client reçoit "un nouveau client vient de se connecter 2". Rebelotte 2 fois, et là je reçoit enfin "message 1" dans le client. "message 1" apparaît après avoir envoyé 3 autres messages. Je comprend que tous les envois du serveurs se mettent dans une file d’attente. Le serveur envoie un message, puis se met en attente. Et quand un nouveau message arrive, il est placé dans la file d’attente, puis le serveur regarde le premier qui vient dans la file d’attente, l’envoie à tout le monde, puis se stoppe. Je ne veux pas de se comportement, je veux que le serveur envoie tout ce qu’il y a à envoyer, puis qu’il se mette en attente. J’en déduit que je dois mettre un whie(…). Mais je ne sais pas que tester dans mon while, sachant que je n’ai pas explicitement programmé de file d’attente.

Je met le code de mon serveur ci-après pour pas casser ce que je dit.

Mes 3 annonces sont aux lignes 46, 47, et 48. Le slot qui est appelé quand un message est reçu par le serveur commence à la ligne 52("donneesRecues"), et la fonction qui permet d’envoyer à tout le monde commence à la ligne 78 ("envoyerATous").

Pouvez-vous m’aider à comprendre quelle condition tester dans mon while pour que les 3 annonces soient envoyées successivement, sans avoir besoin de recevoir un message à chaque fois ?

Merci par avance


fenetre_serveur.cpp

#include "fenetre_serveur.h"
#include <QString>

// Constructeur par défaut
fenetre_serveur::fenetre_serveur() : QWidget()
{
    // Texte d'information du serveur
    etatServeur = new QLabel(this);

    // Parametres du bouton
    boutonQuitter = new QPushButton("Quitter", this);
    QObject::connect(boutonQuitter, SIGNAL(clicked()), qApp, SLOT(quit()));

    // Disposition
    layout = new QVBoxLayout(this); //Pour ne pas s'embêter avec les ->setGeometry
    layout->addWidget(etatServeur);
    layout->addWidget(boutonQuitter);
    setWindowTitle("Fenetre du serveur");

    // Demarrage du serveur
    /* Songer a mettre ceci hors du constructeur plus tard : J'aimerai pouvoir faire plusieurs serveurs en simultanné */
    serveur = new QTcpServer(this);
    // Attente de la connexion de n'importe qui sur le port 50885.
    if( !serveur->listen(QHostAddress::Any, 50885)){ /* Si possible laisser l'utilisateur choisir son port ou un port automatique*/
        etatServeur->setText("Le serveur n'a pas pu demarrer : <br />" + serveur->errorString());
    }
    else{
        etatServeur->setText("Le serveur a démarré sur le port <strong>" + QString::number(serveur->serverPort()) + "</strong>.<br /> Des clients peuvent maintenant se connecter");
        QObject::connect(serveur, &QTcpServer::newConnection, this, &fenetre_serveur::nouvelleConnexion);
    }
}


void fenetre_serveur::nouvelleConnexion()
{

    // Nouveau client
    QTcpSocket *nouveauClient = serveur->nextPendingConnection();
    clients << nouveauClient;

    // Signaux
    QObject::connect(nouveauClient, &QTcpSocket::readyRead, this, &fenetre_serveur::donneesRecues); // signal readyRead introuvable dans la doc de QTcpSocket
    QObject::connect(nouveauClient, &QTcpSocket::disconnected, this, &fenetre_serveur::deconnexionClient); //idem

    // Annonces
    fenetre_serveur::envoyerATous("Un nouveau client vient de se connecter 1"); // Annonce 1
    fenetre_serveur::envoyerATous("Un nouveau client vient de se connecter 2"); // Annonce 2
    fenetre_serveur::envoyerATous("Un nouveau client vient de se connecter 3"); // Annonce 3

}

void fenetre_serveur::donneesRecues()
{
    // Recherche du QTcpSoccket du client qui envoi
    //fenetre_serveur::envoyerATous("test de réponse\n");

    QTcpSocket *socket = qobject_cast< QTcpSocket *>(sender());
    if(socket == 0){ return; }

    // Recuperation du message
    QDataStream in(socket); /*rendre les if plus propres */
    if(tailleMessage == 0){ // Si on a pas déjà la taille du message

        if(socket->bytesAvailable() < (int)sizeof(quint16)){ // Si on a pas au moins un int, alors on a pas encore reçu le message en entier
            return;
        }
        in >> tailleMessage; // Si on au moins un entier, alors on a la taille du message
    }

    if(socket->bytesAvailable() < tailleMessage) {return;} // Si on a pas encore le message entier, on attend

    QString message;
    in >> message; // On vide entièrement in dans message;
    envoyerATous(message); // On l'envoi à tout le monde;
    tailleMessage = 0; // On se rend prêt à recevoir un nouveau message;
}

void fenetre_serveur::envoyerATous(const QString& message)
{
    QByteArray paquet; // Le paquet a envoyer est un vecteur d'octets
    QDataStream out(&paquet, QIODevice::WriteOnly); // Simplement pour pouvoir ecrire dans le paquet avec <<. C'est du sucre syntaxique
    out << (quint16) 0; // Réservation de l'emplacement pour la taille du message
    out << message; // On place le message a proprement parler
    /* Ajouter un traitement du message, pour mettre le pseudo de qui envoi, l'heure... */
    out.device()->seek(0); // On se repalce au début du paquet
    out << (quint16) (paquet.size() - sizeof(quint16)); // La taille du message compte pas dans la taille du message...

    // Envoi
    for(int i = 0; i < clients.size(); i++)
    {
        clients[i]->write(paquet); //On parcours les clients et on leur envoie un à un
    }
}

void fenetre_serveur::deconnexionClient()
{
    envoyerATous("Un client vient de se déconnecter");

    // Qui ?
    QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
    if( socket == 0) { return; }

    clients.removeOne(socket); // On retire le client
    socket->deleteLater();
    /* Mettre des else !!! */

}

Je suis convaincu que le problème vient de mon fichier source. Je met quand même le header associé pour donner les déclarations.

fenetre_serveur.h

#ifndef FENETRE_SERVEUR_H
#define FENETRE_SERVEUR_H

// fichiers par défaut
#include <QWidget>
#include <QtNetwork>
#include <QtWidgets/QLabel>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QVBoxLayout>

class fenetre_serveur : public QWidget {
    Q_OBJECT

public:
    fenetre_serveur();
    void envoyerATous(const QString& message); /*renommer en envoyer*/

private slots:
    void nouvelleConnexion(); /*renommer en client_connexion*/
    void donneesRecues(); /*renommer en recevoir*/
    void deconnexionClient(); /*renommer en client_deconnexion*/

private:
    // Elements de l'interface
    QLabel *etatServeur;
    QPushButton *boutonQuitter;
    QVBoxLayout *layout;

    // Elements de réseau
    QTcpServer *serveur;
    QList<QTcpSocket *> clients;
    quint16 tailleMessage = 0;


};

#endif // FENETRE_SERVEUR_H

Problème résolu : Le problème vient du client. Quand un socket est readyRead, le slot de réception de message associe ce socket à un flux "in". ce flux peut contenir plusieurs messages, mais le slot connait la taille du premier message, et on sort de la fonction dès que ce message est lu. Il faut tester "in.atEnd()" pour retourner au début de la fonction si on est pas encore à la fin du flux, et ainsi récupérer tous les messages à la suite. Je partage pour d’autres intéressés.

Ce sujet est verrouillé.