Partage

chatt client/serveur

C++/Qt

Sujet résolu
28 juillet 2018 à 17:42:52

Bonjour OC,    :)

je reviens à vous car après avoir implémenté la classe Client intermédiaire, j'ai encore des challenges à résoudre. J'ai donc besoin d'un coup de main pour ce projet pas terminé.

Les erreurs que j'ai actuellement sont les suivantes :

- quand un nouveau client se connecte au serveur; les autres clients n'affichent rien

- quand j'appuie plusieurs fois sur "Envoyer" pour un des clients, il affiche "un nouveau client vient de se connecter" alors qu'il est déjà connecté.

- Le seul client qui affiche son nom est le premier client qui se connecte , et les autres n'affichent pas "un tel vient de connecter" alors que je souhaite que tous les clients affichent "un tel vient de se connecter"

J'ai ces erreurs depuis que j'ai cherché à faire une liste de Client* comme ça avait été suggéré par Koala01.

Je vous mets tout le code pour que vous puissiez m'aider.
Merci

fenclient.h

#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();


    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;
        int numberClick{0};


};

#endif // FENCLIENT_H

fenclient.cpp

#include "FenClient.h"


FenClient::FenClient()

{

    setupUi(this);

    boutonEnvoyer->setEnabled(false);
    listeMessages->setEnabled(false);

    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;

}


// Tentative de connexion au serveur

void FenClient::on_boutonConnexion_clicked()

{

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

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

    boutonConnexion->setEnabled(false);


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

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

}


// Envoi d'un message au serveur

void FenClient::on_boutonEnvoyer_clicked()

{
    numberClick++;
    if(numberClick==1)
    {

    QByteArray paquet;
    QDataStream out(&paquet, QIODevice::WriteOnly);
    QString a= tr("<strong>") + pseudo->text() + " vient de se connecter."+tr("</strong>");
    //out << (quint16) 0;
    out<<static_cast<quint16>(1);
    out<<(quint16)pseudo->text().size();
    out<<pseudo->text();
    out<<(quint16)a.size();
    out<<a;
    //out.device()->seek(0);
    //out << (quint16) (paquet.size() - sizeof(quint16));
    socket->write(paquet);

    //Client newClient(pseudo->text(),socket);
    //cli
    return;


    }

    QByteArray paquet;

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


    // On prépare le paquet à envoyer

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

    out<<static_cast<quint16>(2);
    //out << (quint16) 0;
    out<<(quint16)messageAEnvoyer.size();
    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

}


// 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();

}


// 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;

}


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

void FenClient::connecte()

{

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

}


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

void FenClient::deconnecte()

{

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

}


// 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);

}

client.h

#ifndef CLIENT_H
#define CLIENT_H

#include <QtWidgets>
#include <QtNetwork>

class Client
{
public:
    Client(QTcpSocket *socket,QString nom=QString(), QPixmap *image=nullptr);//FenClient *parent=nullptr);// {}
    //Client(Client&&);
    //Client(const Client&&);
    //Client(const Client &);
    Client &operator=(const Client &);
    void write(const QByteArray & paquet);
    bool estEgal(const Client & client) const;
    //Client(QTcpSocket *socket);
private:
    QTcpSocket *socket_m;
    QString nom_m;
    QPixmap *image_m;
};
bool operator==(const Client &a, const Client &b);

#endif // CLIENT_H

client.cpp

#include "client.h"

Client::Client(QTcpSocket *socket, QString nom, QPixmap *image):socket_m(socket),nom_m(nom),image_m(image)
{

}

void Client::write(const QByteArray &paquet){

    this->socket_m->write(paquet);
}

bool Client::estEgal(const Client &a) const
{
    return(this->socket_m==a.socket_m);
}
bool operator==(const Client& a, const Client& b)
{
    return(a.estEgal(b));
}

fenserveur.h

#ifndef HEADER_FENSERVEUR

#define HEADER_FENSERVEUR


#include <QtWidgets>

#include <QtNetwork>

#include "client.h"

class FenServeur : public QWidget

{

    Q_OBJECT


    public:

        FenServeur();

        void envoyerATous(const QString &message);


    private slots:

        void nouvelleConnexion();

        void donneesRecues();

        void deconnexionClient();


    private:

        QLabel *etatServeur;
        QLabel *affichNombreClients;

        QPushButton *boutonQuitter;


        QTcpServer *serveur;

        //QList<QTcpSocket *> clients;
        QList<Client*> clients;
        quint16 tailleMessage{0};
        int nombreClients{0};
        QListView *listeClients;
        QString pseudoNouveauClient;
        quint16 tailleNouveauPseudo{0};
        quint16 codeMessage;

};


#endif


fenserveur.cpp

#include "FenServeur.h"
#include <iostream>

FenServeur::FenServeur()

{

    // Création et disposition des widgets de la fenêtre

    etatServeur = new QLabel;
    affichNombreClients= new QLabel;

    boutonQuitter = new QPushButton(tr("Quitter"));

    connect(boutonQuitter, SIGNAL(clicked()), qApp, SLOT(quit()));


    QVBoxLayout *layout = new QVBoxLayout;

    layout->addWidget(etatServeur);
    layout->addWidget(affichNombreClients);
    layout->addWidget(boutonQuitter);

    setLayout(layout);


    setWindowTitle(tr("ZeroChat - Serveur"));


    // Gestion du serveur

    serveur = new QTcpServer(this);

    if (!serveur->listen(QHostAddress::Any, 50885)) // Démarrage du serveur sur toutes les IP disponibles et sur le port 50585

    {

        // Si le serveur n'a pas été démarré correctement

        etatServeur->setText(tr("Le serveur n'a pas pu être démarré. Raison :<br />") + serveur->errorString());

    }

    else

    {

        // Si le serveur a été démarré correctement

        etatServeur->setText(tr("Le serveur a été démarré sur le port <strong>") + QString::number(serveur->serverPort()) + tr("</strong>.<br />Des clients peuvent maintenant se connecter."));


        connect(serveur, SIGNAL(newConnection()), this, SLOT(nouvelleConnexion()));

    }


    tailleMessage = 0;

}


void FenServeur::nouvelleConnexion()

{
     nombreClients++;
    //etatServeur->setText(tr("Le serveur a été démarré sur le port <strong>") + QString::number(serveur->serverPort()) + tr("</strong>.<br />Des clients peuvent maintenant se connecter.")+
     //                    tr("Il y en a ")+QString::number(nombreClients)+tr("."));
    affichNombreClients->setText(tr("Il y en a ")+QString::number(nombreClients)+".");

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

    //envoyerATous(tr("<em>Un nouveau client vient de se connecter</em>"));//. Il y a maintenant "+
    //                //QString::number(nombreClients)+" clients");

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

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

}


void FenServeur::donneesRecues()

{

    // 1 : on reçoit un paquet (ou un sous-paquet) d'un des clients


    // On détermine quel client envoie le message (recherche du QTcpSocket du client)

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

    if (socket == 0) // Si par hasard on n'a pas trouvé le client à l'origine du signal, on arrête la méthode

        return;


    // Si tout va bien, on continue : on récupère le message

    QDataStream in(socket);

    if(codeMessage==0)
    {
    if(socket->bytesAvailable()<(int)sizeof(quint16))
    {
        return;
    }
    in>>codeMessage;
    }

    if(codeMessage==static_cast<quint16>(1))
    {
        if(socket->bytesAvailable()<(int)sizeof(quint16))
        {
            return;
        }
        in>>tailleNouveauPseudo;
        if(socket->bytesAvailable()<tailleNouveauPseudo)
        {
            return;
        }
        in>>pseudoNouveauClient;
        Client * tem = new Client(socket,pseudoNouveauClient);
        clients<< tem;

        envoyerATous(tr("<em>Un nouveau client vient de se connecter</em>"));//. Il y a maintenant "+
                        //QString::number(nombreClients)+" clients");
        //std::move(Client(socket,pseudoNouveauClient));
        //return;
    }

    if (tailleMessage == 0) // Si on ne connaît pas encore la taille du message, on essaie de la récupérer
    {
        if (socket->bytesAvailable() < (int)sizeof(quint16)) // On n'a pas reçu la taille du message en entier
        {
            return;
        }

        in >> tailleMessage; // Si on a reçu la taille du message en entier, on la récupère

    }


    // Si on connaît la taille du message, on vérifie si on a reçu le message en entier

    if (socket->bytesAvailable() < tailleMessage) // Si on n'a pas encore tout reçu, on arrête la méthode
    {
        return;
    }


    // Si ces lignes s'exécutent, c'est qu'on a reçu tout le message : on peut le récupérer !

    QString message;

    in >> message;



    // 2 : on renvoie le message à tous les clients
    std::cout<<"test du message : "<<message.toStdString()<<std::endl;
    envoyerATous(message);


    // 3 : remise de la taille du message à 0 pour permettre la réception des futurs messages
    tailleNouveauPseudo =0;
    tailleMessage = 0;

}
/*
Client FenServeur::createClientFromSocket(QTcpSocket *socket)
{
    //obtenir l'instance client en fonction de son socket

}
*/
void FenServeur::deconnexionClient()

{

    envoyerATous(tr("<em>Un client vient de se déconnecter</em>"));


    // On détermine quel client se déconnecte

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

    if (socket == 0) // Si par hasard on n'a pas trouvé le client à l'origine du signal, on arrête la méthode
    {
        return;
    }
    Client *tem = new Client(socket);
    clients.removeOne(tem);


    socket->deleteLater();

    affichNombreClients->setText(tr("Il y en a ")+QString::number(--nombreClients)+".");
}


void FenServeur::envoyerATous(const QString &message)

{

    // Préparation du paquet

    QByteArray paquet;

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


    out << (quint16) 0; // On écrit 0 au début du paquet pour réserver la place pour écrire la taille

    out << message; // On ajoute le message à la suite

    out.device()->seek(0); // On se replace au début du paquet

    out << (quint16) (paquet.size() - sizeof(quint16)); // On écrase le 0 qu'on avait réservé par la longueur du message



    // Envoi du paquet préparé à tous les clients connectés au serveur

    //QList<Client>::iterator i;

    //for(i=clients.begin();i!=clients.end();i++)
    for (int i = 0; i < clients.size(); i++)
    {
        //i->write(paquet);
        clients[i]->write(paquet);

    }


}






-
Edité par YES, man 31 juillet 2018 à 8:48:28

Vous êtes demandeur d'emploi ?
Sans diplôme post-bac ?

Devenez Développeur web junior

Je postule
Formation
en ligne
Financée
à 100%
28 juillet 2018 à 21:17:04

Lu',

 YES, man a écrit:

Les erreurs que j'ai actuellement sont les suivantes :

- quand un nouveau client se connecte au serveur; les autres clients n'affichent rien

- quand j'appuie plusieurs fois sur "Envoyer" pour un des clients, il affiche "un nouveau client vient de se connecter" alors qu'il est déjà connecté.

- Le seul client qui affiche son nom est le premier client qui se connecte , et les autres n'affichent pas "un tel vient de connecter" alors que je souhaite que tous les clients affichent "un tel vient de se connecter"

Et quels sont les causes possibles de ces erreurs selon toi? Fais une liste d'hypothèses, verifie chacune d'entre elles. Ce n'est que lorsque tu seras à court d'hypothèses à verifier qu'il faudra demander de l'aide.

Exemple pour le premier point (qui cause surement les 2 autres): Pourquoi les autres clients n'affichent rien? Est-ce à eux d'afficher ce message? Si oui, comment feraient ils pour savoir qu'un nouveau client vient de se connecter? etc...

Avec ma petite experience, je me suis rendu compte qu'on passe plus de temps à (concevoir et) debugger qu'à programmer. Good Luck!

Eug
31 juillet 2018 à 8:49:25

Salut et Merci Eugchriss.

Est-ce que ça ne vient pas du passage :

QTcpSocket *nouveauClient = serveur->nextPendingConnection();
    Client *tem = new Client(nouveauClient);
    clients<<tem;

?

"j'ajoute un pointeur à une liste de pointeurs"

Merci

-
Edité par YES, man 31 juillet 2018 à 9:01:07

31 juillet 2018 à 12:21:01

Théoriquement, c'est au serveur de prévenir tous le monde lorsqu'il y a un nouvel arrivant.
31 juillet 2018 à 15:26:24

c'est ce que je fais, c'est un morceau de code du serveur
31 juillet 2018 à 19:01:03

YES, man a écrit:

Est-ce que ça ne vient pas du passage :

QTcpSocket *nouveauClient = serveur->nextPendingConnection();
    Client *tem = new Client(nouveauClient);
    clients<<tem;
Pourquoi selon toi, ce serait ce morceau de code le problème et pas un autre? Il faut pousser la reflexion jusqu'au bout.

Eug
1 août 2018 à 9:13:35

Salut :)

je pense ça pour deux raisons :

1) car c'est quand j'ai ajouté ce morceau de code (ainsi qu'un autre avec la partie removeOne ) que les bugs ont commencé

2) parce que ajouter un pointeur avec l'opérateur <<, ça n'est pas ce à quoi je suis habitué et je me dis qu'il y a peut-être de la mémoire qui est conservée, et qui , du coup, aboutit à faire que ça écrit en sortie "un client vient de se connecter" alors que j'ai juste envoyé un nouveau message.

-
Edité par YES, man 1 août 2018 à 9:17:47

1 août 2018 à 9:56:59

Le soucis est que tu nous largues un IMMENSE "bout de code" (probablement l'intégralité de ton programme ?)  en espérant, en gros, que l'on fasse ton boulot à ta place.

L'écriture de code est un moyen, un outil, pas le réel "métier" du développeur. Le développeur dans son essence est là pour résoudre des problèmes "en réalité" en écrivant du code puis à résoudre encore d'autres problèmes dans le code pour qu'il fonctionne. Là, tu espères que l'on analyse l'intégralité de ton programme pour que l'on fasse la résolution de problèmes à ta place.

L'utilité d'un forum d'entraide est de faire appel à l'expérience d'autres utilisateurs pour trouver des solutions à des problèmes atomiques (qu'on ne peut pas briser en plusieurs sous-problèmes) ou proches de l'atomique. Par exemple, tu nous dirais "Voici le bout de code sensé envoyer la notification de connexion aux autres clients. Seul le nom du premier client connecté s'affiche. Pourquoi ? Voici les tests que j'ai fait, et des hypothèses, et des précisions sur les structures de données perso", on pourrait entrer dans le vif du sujet et te proposer d'autres hypothèses.

YES, man a écrit:

Les erreurs que j'ai actuellement sont les suivantes :

- quand un nouveau client se connecte au serveur; les autres clients n'affichent rien

- quand j'appuie plusieurs fois sur "Envoyer" pour un des clients, il affiche "un nouveau client vient de se connecter" alors qu'il est déjà connecté.

- Le seul client qui affiche son nom est le premier client qui se connecte , et les autres n'affichent pas "un tel vient de connecter" alors que je souhaite que tous les clients affichent "un tel vient de se connecter"

Pour ces trois problèmes, ton processus de recherche de source du problèmes est à peu près le même, et il est courant en réseau :

  • Nous sommes confrontés à un problèmes sur une interaction client -> serveur. Vérifie que le code client en rapport avec cette interaction en particulier fonctionne correctement et ne possède pas de failles en temps normal (et te dise clairement avec un message d'erreur si on n'est pas en "temps normal").
  • Côté serveur, vérifie que cette interaction soit correctement détectée, et que le serveur reconnaisse quel client l'a faite. Ensuite, vérifie que le serveur réagisse correctement (par exemple, envoie une notification à tous les autres clients) et ne possède pas de failles en temps normal (même chose : si malgré ta vérification, quelque chose cloche par exemple avec les données reçues du client, met un message d'erreur !)
  • De nouveau côté client, vérifie que le code de réception du message que le serveur est sensé envoyer fonctionne correctement.

Cette procédure te permettra de voir (dumoins de commencer à voir) si tu as de gros problèmes avec ton architecture (la première fois que j'avais fait du réseau je me suis rendu compte que je n'avais aucun moyen facile de reconnaître qui avait envoyé un message au serveur, ce qui rend difficile l'association avec un pseudo par exemple), et avec un peu de chance tu trouveras le ou les bouts de code qui casse(nt).

-
Edité par Hoshiqua 1 août 2018 à 10:02:30

1 août 2018 à 10:02:09

YES, man a écrit:

c'est ce que je fais, c'est un morceau de code du serveur


Je voit que l'appel de la fonction membre "envoyerATous", qui je suppose est en charge d'envoyer un message quelconque à tous les clients, est commentée...
1 août 2018 à 23:48:08

Salut,

merci deedolith,

à force de tests, j'ai fini par situer une erreur. Elle est ici (et je ne sais comment débuger pour l'instant)

if(socket->bytesAvailable() < static_cast<quint64>(sizeof(quint16)))
        {
            std::cout<<"test avant l'envoi à tous 11"<<std::endl;
            std::cout<<socket->bytesAvailable()<<std::endl;
            std::cout<<static_cast<int>(sizeof(quint16))<<std::endl;
            return;
        }


dans le if, j'obtiens des nombres qui sont plus grands à gauche qu'à droite(=2), donc ce qui est entre accolades, devait être sauté. Pourtant, ce qui est entre accolades est exécuté, alors que ça ne devrait pas être le cas.

Sauriez-vous pourquoi ?

Merci

EDIT : il y avait un point virgule de trop, je l'ai vue. Maintenant, je passe aux autres erreurs

-
Edité par YES, man 2 août 2018 à 0:11:41

8 août 2018 à 13:20:25

Bonjour OC :)

je reviens sur le fil, car il semble que les choses avancent de mon côté. enfin ...

EN effet, en réinitiaisant 

codeMessage = 0;

à la fin de la fonction donneesRecues, les messages sont mieux dispatchés ,et ce que je veux voir apparaît.

PAr contre, subsiste encore quelque chose à résoudre, c'est que lorsque je clique sur "Envoyer" avec un message à envoyer, je dois cliquer une deuxième fois sur le bouton pour que mon message apparaisse. Normalement, une seule fois devrait suffire.

Et ce challenge était déjà là avant.

Sauriez-vous m'aider ?

Merci et à plus

-
Edité par YES, man 8 août 2018 à 14:01:04

9 août 2018 à 16:41:34

Bonjour ,

une idée ?

Merci

12 août 2018 à 11:15:52

Salut :)

je continue à faire mes tests, et j'ai réglé en partie le problème de l'apparition des messages avec quand même une question.

Dans un slot qui est donnéesRecues(), j'ai envie de faire apparaître deux messages à deux moments différents.

J'utilise pour cela la célèbre fonction write appliquée à un QTcpSocket.

Le premier message s'affiche s'affiche instantanément.

Le deuxième non. Je fois appuyer une première fois et un message vide apparaît. Puis lorsque je réappuie, mon bon message s'apparaît.

J'ai donc l'impression que l'utilisation de la fonction write se heurte à l'utilisation de temporisation et que dans une même fonction, c'est comme si on ne pourrait pas faire deux "write" de suite sur la même QTcpSocket, car si j'utilise une seule write() (donc une seule écriture sur QTcpSocket, le pb semble se régler)

Or j'aimerais avoir la lattitude de faire ce que je veux dans l'avenir (par exemple d'utiliser deux écritures de type write()) d''où la recherche d'explication pour comprendre ce phénomène.

Merci

-
Edité par YES, man 12 août 2018 à 11:16:37

chatt client/serveur

× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
  • Editeur
  • Markdown