cela fait plusieurs semaines que je cherche à débuger ce code pour deux problèmes :
1) quand j'appuie la première fois sur le bouton envoyer du Client FenCLient, j'ai bien le nom du client qui s'affiche, et son premier message s'affiche au deuxième click , ce qui déjà n'est pas normal pour moi, car les deux devraient s'afficher pour moi lors du premier click.
2) à partir du troisième click du client sur FenClient, plus rien ne s'affiche.
Merci à toute personne qui pourrait m'expliquer pourquoi ça ne marche pas normalement, car j'ai passé des heures à modifier, débuger pour trouver la source du problème.
MERCI
Voici les morceaux de code à mon avis :
dans FenClient :
void FenClient::on_boutonEnvoyer_clicked()
{
nombreClick_onBoutonEnvoyer++;
std::cout<<"nombreClick_onBoutonEnvoyer"<<std::endl;
std::cout<<nombreClick_onBoutonEnvoyer<<std::endl;
//std::cout<<nombreClick_onBoutonEnvoyer<<std::endl;
if(nombreClick_onBoutonEnvoyer==1)
{
QByteArray paquet;
QDataStream out(&paquet, QIODevice::WriteOnly);
QString nomduClient = lineEdit_2->text();
//std::cout<<nomduClient.toStdString()<<std::endl;
std::cout<<"nombreClick_onBoutonEnvoyer1111"<<std::endl;
std::cout<<nombreClick_onBoutonEnvoyer<<std::endl;
out<<static_cast<quint16>(1);
out<<(quint32)(nomduClient.size());
out<<nomduClient;
std::cout<<nomduClient.toStdString()<<std::endl;
std::cout<<nomduClient.toStdString().size()<<std::endl;
QString messageAEnvoyer= tr("<strong>")+lineEdit_2->text()+tr("</strong> :")+
message->text();
out << (quint32)(messageAEnvoyer.size());
out << messageAEnvoyer;
//Recevoir le pseudo et l'avatar du client
socket->write(paquet);
}
if(nombreClick_onBoutonEnvoyer>=2)
{
QByteArray paquet;
QDataStream out(&paquet, QIODevice::WriteOnly);
QString messageAEnvoyer= tr("<strong>")+lineEdit_2->text()+tr("</strong> :")+
message->text();
out<<static_cast<quint16>(2);
out << (quint32)(messageAEnvoyer.size());
out << messageAEnvoyer;
socket->write(paquet);
}
message->clear();
message->setFocus();
}
ainsi que :
void FenClient::donneesRecues()
{
std::cout<<"test fenclient donnees reçues"<<std::endl;
QDataStream in(socket);
if(tailleMessage == 0)
{
if(socket->bytesAvailable()< (int)(sizeof(quint32)))
{
return;
}
in>>tailleMessage;
}
if(socket->bytesAvailable()< tailleMessage)
{
return;
}
QString messageRecu;
in>>messageRecu;
std::cout<<"dans client, message reçu vaut"+messageRecu.toStdString()<<std::endl;
//On affiche le message sur la zone de chat
listeMessages->append(messageRecu);
//on remet la taille du message à pour recevoir un nouveau message
tailleMessage = 0;
}
Et dans FenServeur :
void FenServeur::donneesRecues()
{
std::cout<<"serveyr_donnesreçues"<<std::endl;
// 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'original du signal, on//arrête la méthode
{
return;
}
//Si tout va bien, on continue : on récupère le message
QDataStream in(socket);
//quint16 identifiant;
if(identifiant==0)
{
//std::cout<<"testserveur identifiant ==0"<<std::endl;
if(socket->bytesAvailable()<(int)(sizeof(quint16)))
{
return;
}
std::cout<<"testserveur identifiant ==04"<<std::endl;
in>>identifiant;
std::cout<<"testserveur identifiant "<<QString::number(identifiant).toStdString()<<std::endl;
std::cout<<"testserveur identifiant "<<identifiant<<std::endl;
}
if(identifiant==static_cast<quint16>(1))
{
if(taillePseudo==0)
{
if(socket->bytesAvailable()<(int)sizeof(quint32))
{
std::cout<<"testserveur identifiant ==101"<<std::endl;
return;
}
in>>taillePseudo;
}
std::cout<<"testserveur identifiant ==102"<<std::endl;
if(socket->bytesAvailable()<taillePseudo)
{
return;
}
QString pseudoNouveauClient;
in>>pseudoNouveauClient;
if(socket->bytesAvailable()<(int)sizeof(quint32))
{
return;
}
in>>tailleMessage;
if(socket->bytesAvailable()<tailleMessage)
{
return;
}
QString message;
in>>message;
identifiant=0;
taillePseudo=0;
Client * client = new Client(nouveauClientSocket,pseudoNouveauClient);//,pseudoNouveauClient); //exemple passionnant d'un ui
clients << *client;
envoyerATous(pseudoNouveauClient+" vient de se connecter.");
envoyerATous(message);
std::cout<<"je fais un test pour 1."<<std::endl;
}
else if(identifiant==static_cast<quint16>(2))
{
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(quint32))
{
return;
}
in >> tailleMessage; //Si on a reçu la taille du message en entier, on la récupère en entier
std::cout<<"test taille message "<<QString::number(tailleMessage).toStdString()<<std::endl;
}
// Si on connaît ta 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
envoyerATous(message);
//3 : remise de la taille du message à 0 pour permettre la réception des futurs messages
tailleMessage = 0;
identifiant=0;
}
}
Il faudrait aussi fournir les headers, cela permet d'avoir le type des variables utilisées et certains problèmes peuvent venir de là lorsque l'on se met à manipuler des octets directement.
Ceci dit, pourquoi avoir mit un type "quint16" dans le morceau de code suivant alors que manifestement, vous manipulez des quint32 ? :
out<<static_cast<quint16>(2);
out << (quint32)(messageAEnvoyer.size());
out << messageAEnvoyer;
Concernant quint16, c'est un identificateur au départ. je l'ai mis en quint16. Etant donné qu'en réception, j'ai pris soin de récupérer un quint16, je pense que ça passe.
Les éléments envoyés par le "out" sont bien distincts.
Donc, tant qu'à l'arrivée, c'est bien traité, je pense que ça passe.
Le vrai problème dans ce code, c'est qu'il est dur a lire :
Tes fonctions sont trop longues (50 et 115 lignes)
L'indentation est bancale et non uniforme (dans le cpp de FenServeur, compare le if ligne 8 et celui ligne 21)
Les conventions de nommage changent (une petite pensée pour int nombreClick_onBoutonEnvoyer;
Du coup, il est très difficile pour nous de t'aider. Notre seule solution est de lire le code (on a pas tout le reste du projet, donc on ne peux pas lancer un debuger), mais comme je viens de te l'expliquer cela est très inconfortable.
Du coup comment régler ça?
1 : Nettoie ton code. Ré-indente le proprement, choisi UNE convention de nommage et tiens y toi jusqu'au bout.
2 : Découpe ton code en fonctions (une fonction = un verbe comme dirais notre marsupial australien favoris). De plus, les longues fonctions c'est chiant a lire et a debuger (certains disent même que + de 10 lignes par fonction, c'est trop). Une bonne chose a vérifier est que tu arrive a envoyer/recevoir des messages "complexes" (disons un id une string) AVANT d’intégrer ça a ton bouton envoyer. Dans le cas présent, il gère le choix du type de message, la construction du message et l'envoi du message. Avoir une fonction distincte pour ces taches serait un bon début.
3 : Teste (et debug si nécessaire) ces fonctions, une par une en partant du bas (si f1 utilise f2, debug f2 en premier)
4 : Quand ce sera fait, tu seras en mesure de nous dire ou le problème est dans ton code, et nous seront en mesure de lire ton code. (mais je suis prêt a parier que si tu suis correctement les 3 précédentes étapes, tu n'aura plus ce problème.
Mais si tu l'avais dis depuis le début, on aurai pu t'aide;
Le plus simple connecte toi au signale &QTcpSocket::connected de ton client, cette dernière peut alors envoyer le nom(pseudo) de ton client au serveur.
puis une autre possibilité est de créer un contrôleur client dans ton projet serveur qui peut avoir comme attribut un QTcpSocket et un QString, cette méthode te demandera de réorganiser ton code.
- Edité par EL-jos 25 juin 2018 à 17:29:18
Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .
essaie de faire la première proposition donc de te connecter à ton signal &QTcpSocket::connected pour signaler à tout tes client qu'il y a un nouveau qui vient de se connecter au réseau en envoyant le nom de ce client. donc envoie une trame qui contiendra le pseudo du client.
puis après nous allons t'aider à créer un contrôleur client dans ton projet serveur, sache que c'est une étape complexe(bah tout dépend du niveau).
- Edité par EL-jos 25 juin 2018 à 18:20:03
Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .
> @Elried, comment fait-on pour débugger une fonction séparément si il n'y a pas de code avec ? Merci pour tes explications.
Tu fait un code qui n'utilise QUE cette fonction. Ou mieux des tests unitaires. En gros tu la teste dans un contexte ou si il y a un probleme, ca viendra forcement de cette fonction
dans le cadre de mon débugage, j'ai une question : dans ce code que Mathieu a mis (je le remets dans son intégralité en fin de message), et que j'ai adapté en fonction des conseils que l'on ma donnés, j'ai besoin de comprendre une chose :
dasn la ligne de code suivante :
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; ..............
Si le return est exécuté, est-ce bien le signal readyRead() qui va permettre de relancer la fonction donnéesRecues() qui contient ce morceau de code afin de retenter d'extraire les messages du flux de données ?
Voici comment je comprends, de façon vulgarisée, de ma relecture de la doc la fonction readyRead() :
il me semble que derrière cela se passe en fait un appel récursif, qui n'est pas explicite dans le code, l'idée étant de répéter récursivement m'appel à donnesRecues tant que la donnée disponible sur le socket n'a pas été totalement exploitée.
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 (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
envoyerATous(message);
// 3 : remise de la taille du message à 0 pour permettre la réception des futurs messages
tailleMessage = 0;
}
> Si le return est exécuté, est-ce bien le signal readyRead() qui va permettre de relancer la fonction donnéesRecues() qui contient ce morceau de code afin de retenter d'extraire les messages du flux de données ?
Si tu as connecte ton slot donneesRecues a ton signal ready read, c'est l’émission de ce dernier qui vas causer l’appel de ta fonction. Par contre le return n'y change rien. readyRead est émis des que tu as des nouvelles données a lire. Ce return permet juste de quitter ta fonction sans consommer de données (donc elles seront encore la quand tu en recevras de nouvelles.
> il me semble que derrière cela se passe en fait un appel récursif, qui n'est pas explicite dans le code, l'idée étant de répéter récursivement m'appel à donnesRecues tant que la donnée disponible sur le socket n'a pas été totalement exploitée.
La doc de readyRead est pourtant explicite. Ce signal n'est pas emis récursivement. Comme donneesRecuesest appelle quand le signal est emis, donneesRecues n'est pas appellees récursivement.
"Le plus simple connecte toi au signale &QTcpSocket::connected de ton client, cette dernière peut alors envoyer le nom(pseudo) de ton client au serveur.". La deuxième solution proposée par EL-Jos est justement l'objet d'un exo proposé par Mathieu en fin de tp
connected, à ma connaissance ne fournit pas le nom de client. C'est un signal
Peux-tu préciser ce que tu veux dire ?
2) concernant la méthode avec le contrôleur pour le nom, c'est justement ce que j'ai fait dans le code que j'ai proposé avant.
Merci
EDIT : afin de proposer la solution la plus simple possible, j'ai donc décidé d'envoyer un message de la même forme que celle choisie par Mathieu.
La seule chose qui change donc dans mon code, c'est la partie où j'envoie le nom du client.
L'issue qui se pose maintenant, c'est que après le premier clic, je dois cliquer deux fois pour que mon message s'affiche si j'exécute ce morceau de code suivant. Quelqu'un saurait-il m'expliquer ?
Merci beaucoup
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";
out<<(quint16)sizeof(a.size());
out<<a;
socket->write(paquet);
out.device()->seek(0);
}
QByteArray paquet;
QDataStream out(&paquet, QIODevice::WriteOnly);
// On prépare le paquet à envoyer
QString messageAEnvoyer = tr("<strong>") + pseudo->text() +tr("</strong> : ") + message->text();
out << (quint16) 0;
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
}
La seule chose qui change donc dans mon code, c'est la partie où j'envoie le nom du client.
L'issue qui se pose maintenant, c'est que après le premier clic, je dois cliquer deux fois pour que mon message s'affiche si j'exécute ce morceau de code suivant. Quelqu'un saurait-il m'expliquer ?
Merci beaucoup
Excuse moi mais pour te dire vrai, ton code est mal conçut pour deux raison:
La première est de vouloir deux clic pour envoyer un message(ce qui est absurde) et un clic pour envoyer le nom du nouveau client connecter au réseau , car imagine par exemple un profane qui ne sait comment fonctionne ton logiciel, sachant que devant lui, il a quatre boutons tel que: bouton Connexion, bouton Déconnexion, bouton Envoyer et bien sûr celui de Quitter; alors dis moi comment un tel client sauras qu'il faut un double clic sur le bouton Envoyer ?(c'est qui est inconcevable), le plus simple et ce que je te conseille c'est d'envoyer le nom du nouveau client après un clic sur le bouton Connexion(ce qui est normal).
La deuxième est la manière dont tu envoie la taille de ton message, personnellement suis pas vraiment d'accord avec "ng a= tr("<strong>") + pseudo->text() + " vient de se connecter";
out<<(quint16)sizeof(a.size()); " car tu n'as pas besoin d'indiquer la taille de ton QString(ton message) vu qu'il sera socket dans un QByteArray donc en octet, selon moi je trouve que la meilleur façon est d'indiquer la taille de ton QByteArray comme taille de ton message.
YES, man a écrit:
Et je me réfère au conseil de El-Jos qui disait :
"Le plus simple connecte toi au signale &QTcpSocket::connected de ton client, cette dernière peut alors envoyer le nom(pseudo) de ton client au serveur.". La deuxième solution proposée par EL-Jos est justement l'objet d'un exo proposé par Mathieu en fin de tp
connected, à ma connaissance ne fournit pas le nom de client. C'est un signal
Peux-tu préciser ce que tu veux dire ?
en parlant de la connexion au signale &QTcpSocket::connected, ce dernier est un signal qui est émit dès que un client(toi) vient de se connecter à un serveur donc on peut avoir un slot du genre &Client::on_connected, pour envoyer le nom du client au serveur donc cela peut se traduire en disant: Dès que le client est connecté(&QTcpSocket::connected est émit), on fait appelle au slot &Client::on_connected, qui à son tour envoie un paquet(le nom du client) au serveur.
Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .
ok, alors maintenant j'en suis arrivé au point d'implémenter la classe Classe.
La seule chose que je me demande , c'est pourquoi si on créé une telle classe Client, pourquoi ensuite, au prix de cette réorganisation de code, pourquoi, donc ça simplifie les choses ?
C'est ça que je veux comprendre.
En effet , Mathieu dit :
"Plus délicat car ça demande un peu de réorganisation du code : au lieu d'avoir une QList de QTcpSocket, faites une QList d'objets de type Client. Il faudra créer une nouvelle classe Client qui va représenter un client. Elle aura des attributs comme : sa QTcpSocket, le pseudo (QString), pourquoi pas l'avatar (QPixmap), etc. A partir de là vous aurez alors beaucoup plus de souplesse dans votre Chat !"
Et je cherche à comprendre, pourquoi on aurait plus de souplesse.
A priori, j'ai commencé à ré-implémenter cette classe, sans la mettre comme héritant de FenClient (ce que j'avais fait avant)
Cette fois, je créé Client comme une classe enfant de FenClient :
J'arrive à envoyer son nom, et signaler sa présence.
Maintenant, je suis sur la partie créer la classe Client.
Peux-tu r m'expliquer par rapport à mes questions sur mon dernier post ?
Merci à toi
EDIT : voici, au point d'avancement actuel, le code fonctionnel avant l'implémentation de Client :
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.";
out << (quint16) 0;
out<<a;
out.device()->seek(0);
out << (quint16) (paquet.size() - sizeof(quint16));
socket->write(paquet);
return;
/*
out<<(quint16)sizeof(a.size());
out.device()->seek(0);
*/
}
QByteArray paquet;
QDataStream out(&paquet, QIODevice::WriteOnly);
// On prépare le paquet à envoyer
QString messageAEnvoyer = tr("<strong>") + pseudo->text() +tr("</strong> : ") + message->text();
out << (quint16) 0;
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
}
quand je réalisais cet projet j'avais remarqué une chose très importante par apport à la classe Client;
le contrôleur Client avait rendu mon code flexible du genre à pouvoir traiter les données indépendamment, c.à.d traiter par exemple l'avatar du client indépendamment du message ainsi que de son pseudo.
s'il faut aller plus loin c'est l'unique moyen qui va te permettre d'envoyer un message à une catégorie (groupe).
Nota: la classe Client ne peut hériter de FenClient car cela n'a pas de sens et encore moins de FenServeur.
- Edité par EL-jos 29 juin 2018 à 13:02:46
Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .
Dans ma version précédente, qui ne fonctionnement, je me souviens avoir fait , à un moment donné, un double référencement d'un pointeur du genre A->B ->C
Koala01 était intervenu en ayant deviné que je cherchais à mettre en oeuvre l'idée de la classe CLient qui devrait permettre un usage plus souple ensuite.
Cependant, si on suppose faite l'écriture de la classe Client, je ne vois pas comment, à part passer par un accesseur dans la classe Client, avoir accès à ses attributs. Et ceux qui sont venus sur le fil , ont dit que ce n'était pas a bonne méthode.
Du coup, pour éviter les accesseurs, je ne sais pas ce qui est le mieux.
oui mais dans chaque problème il existe toujours un moyen pour contourner la situation.
s'il faut te répondre, la classe client tu va la créer dans ton projet serveur et ton contrôleur FenServeur utilisera une instance de la classe Client.
ton projet FenClient n'aura pas vraiment besoin de la classe Client à ma connaissance j'avais envisager d'avoir aussi une instance Client dans mon contrôleur FenClient mais devine quoi, mon programme ne marchait plus et le pire le socket de mon instance Client n'arriver plus à se connecter au réseau.
donc dans ton projet client tu aura que le contrôleur FenClient et pour ton projet serveur tu aura une classe Client et un contrôleur FenServeur qui aura une instance Client.
je crois que je me suis répéter là :-)
- Edité par EL-jos 29 juin 2018 à 15:44:48
Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .
Salut El-Jos, et salut OC je reprends où j'en étais arrivé.
Pour ma part, dans la version précédente, ça marchait quand j'ai mis mon Client dans FenClient.
Mais là, j'ai fait comme tu dis, je l'ai mise dans FenServeur.
En fait, j'essaie déjà en quoi l'écriture d'une classe Client peut rendre l'écriture de code plus souple par la suite.
En l'occurence , je suis en train de faire les modifications dues à l'introduction de la classe Client , et je tombe sur le passage suivant qui doit être modifié :
clients.removeOne(socket);
où pour rappel, clients désigne la liste des instances de type Client et socket désigne le socket du client.
Je dois donc faire en sorte de remplacer socket par l'élément de type Client associé.
à priori, je pensais à faire un truc du genre :
clients.removeOne(leClient->getSocket());
où getSocket() serait un accesseur. Koala01 m'avait dit que ce n'était pas "beau" de faire cela.
à part cette manière de faire, je voudrais comprendre pourquoi l'utilisation d'un accesseur n'est pas la bonne manière pour ce type de configuration, et comment la restructuration de code peut me permettre de m'en passer ici ? (en gros, les principes)
Merci beaucoup
EDIT :
ou par exemple avec l'exemple suivant :
for (int i = 0; i < clients.size(); i++)
{
clients[i]->write(paquet);
}
Là aussi , avant j'utilisais un accesseur (du genre clients[i]->getsocket()->write(paquet), et je me souviens que Koala01 n'était pas du tout du d'accord (c'est le moins que je puisse dire).
- Edité par pseudo-simple 10 juillet 2018 à 18:50:54
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.
Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .
Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .
Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .
Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .
Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .
Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .
Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .