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.
#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);
}
#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 pseudo-simple 31 juillet 2018 à 8:48:28
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!
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.
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).
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...
à 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
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.
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 pseudo-simple 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é.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Eug
Eug