IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Qt Discussion :

Problème de récéption avec les sockets Qt


Sujet :

Qt

  1. #1
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    82
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 82
    Points : 25
    Points
    25
    Par défaut Problème de récéption avec les sockets Qt
    Bonsoir à tous,

    Mon problème est le suivant : J'ai codé une application serveur et une application client. J'ai de même créée un protocole de communication entre les deux. J'utilise donc deux QTcpSocket.
    Le problème est que lorsque j'envoie une trame avec la méthode write(), puis une deuxième, à partir du serveur, le client reçoit les deux en une seule fois. Ce qui est très gênant, car je voudrais recevoir une trame à la fois pour l'interpréter.
    J'ai même essayé de faire une temporisation entre les deux appel à write(), mais rien n'y fait, le client a l'air d'attendre la fin de la temporisation du serveur pour récupéré les données.
    Sur la doc, j'ai trouvé la méthode waitForReadyRead(), mais ce n'est pas une bonne solution à mon avis, et je ne vois pas trop comment m'en servir de toute façon.

    Si vous avez une idée... je vous remerci d'avance.

  2. #2
    Membre éprouvé

    Profil pro
    Inscrit en
    Mai 2007
    Messages
    774
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Finistère (Bretagne)

    Informations forums :
    Inscription : Mai 2007
    Messages : 774
    Points : 969
    Points
    969
    Par défaut
    Ce n'est pas une histoire de flush() ou équivalent ?

    G.
    Un problème avec Qt ? Vous trouverez votre réponse ici : http://doc.trolltech.com/4.6/overviews.html
    En français (traduction réalisée par l'équipe Qt de DVP) : http://qt.developpez.com/doc/4.6/vues-d-ensemble/

  3. #3
    Modérateur
    Avatar de nouknouk
    Homme Profil pro
    Inscrit en
    Décembre 2006
    Messages
    1 655
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 1 655
    Points : 2 161
    Points
    2 161
    Par défaut
    Le pourquoi du comment ?

    Une erreur (très) courante est de croire qu'on recevra systématiquement le même nombre de 'paquets' que le nombre de ceux qu'on a envoyé.

    Exemple concret : l'ordi A fait trois appels successifs à la fonction write() pour envoyer des données à l'ordi B:
    - 1er appel, il écrit 5 octets (ABCDE)
    - 2ème appel, il écrit 2 octets (FG)
    - 3ème appel, il écrit 3 octets (HIJ)

    On sait maintenant que chaque fois que B va recevoir des données, il émettra le signal 'readyRead()' et qu'on pourra appeler une fonction, du type 'readAll()' pour récupérer ce qu'on a reçu.
    La première idée qui vient à l'esprit c'est que B émettra 3 fois readyRead() et que les trois appels à 'readAll()' retourneront (ABCDE) puis (FG) puis (HIJ).

    Ceci est FAUX : il se peut très bien que l'on:
    - reçoive le tout en une seule fois : une seule émission du signal readyRead() et readAll() retourne (ABCDEFGHIJ)
    - reçoive le tout en 4 fois: 4 émissions de readyRead() et ReadAll() = (AB) puis (C) puis (DEFGH) puis (IJ)
    ...etc...

    Il y a plusieurs raisons à cela:

    Pour les gros paquets : Le réseau et les couches réseaux inférieures à TCP (Ethernet, IP, ...) ne savent pas transporter des paquets d'une taille trop grande (typiquement 1300 octets et des bananes pour un paquet IP). Donc l'envoi d'un seul 'gros' paquet de données a toutes les chances d'être découpé et donc d'être reçu en pusieurs fois de l'autre côté.
    La taille maximale d'un paquet avant qu'il ne soit découpé ne peut être prévue à l'avance : ça dépend de la configuration de l'ensemble des routeurs situés entre l'ordi émetteur et celui récepteur. De plus, pendant une même connection, il se peut que les paquets empruntent des chemins différents, donc passent par des routeurs différents... donc la taille maximale peut varier dans le temps. Imprévisible, donc.

    Pour les petits paquets: à l'inverse, si ton émetteur veut envoyer une dizaine de 'petits' paquets dans un intervalle de temps très court (exemple, tu fais dix appels à write() successifs avec 2 octets à envoyer à chaque fois). Ton interface réseau est une grande maligne et va optimiser leur émission en les envoyant tous d'un coup, car ça économisera un peu de bande passante.
    En effet, un paquet est composé d'un en-tête (origine, destination, port, ...) d'environ 20 octets suivi de tes données (ici, 2 octets). Au lieu d'envoyer 10 paquets, donc 10 entêtes et dix bouts de données, soit 10 x (20+2) = 220 octets, il va envoyer un seul paquet avec toutes les données, ce qui fera en tout 1 en-tête et 10x2 octets de données, soit 40 octets.
    Ce mécanisme d'optimisation s'appelle l'algorithme de Nagle et peut (dans certains cas pour des besoins précis) être désactivé.

    En résumé, les seules choses que TCP est capable de garantir sont que:

    - quoiqu'il arrive, on recevra un jour où l'autre tous nos paquets, sauf si (bien entendu) entre temps un des deux ordis se déconnecte. Par contre, le temps de transmission n'est pas garanti. Ca peut mettre 1 microseconde comme une heure... Heum ... Bon, si ça met effectivement une heure ... ben ... changez de réseau

    - quoiqu'il arrive, l'ordre de réception sera le même que celui d'amission (A puis B puis C puis ...)

    - quoiqu'il arrive, les données reçues seront strictement identiques à celles émises (ex: on recevra pas N à la place de B).


    Quelle solution ?

    On voit bien que quoi qu'on fasse on ne pourra pas garantir que le nombre de paquets reçus sera toujours égal au nombre de paquets émis. En effet, même en désactivant l'alog. de Nagle, on garanti que le nombre de paquets reçus sera au moins égal au nombre de paquets émis, mais rien ne nous garantis que les routeurs intermédiaires ne vont pas fragmenter un paquet en deux sous-paquets.

    Donc pas de solution 'out of the box', il va falloir mettre un peu les mains dans le cambouis pour se programmer sa propre petite solution.

    Il y a deux solutions relativement simples et 'classiques' qui conviennent dans 90% des cas:


    1/ pour chaque bloc de données que l'éméteur envoie, on va systématiquement le terminer avec un ou plusieurs caractères suppélementaires qui annonceront la fin du bloc. Par exemple, en admettant qu'on transmet du texte, on décidé de terminer chaque bloc de données par un caractère de retour à la ligne ('\n'). Côté récepteur, quand on reçoit les données, on les stocke dans un buffer jusqu'à qu'on rencontre le caractère '\n'. Quand on l'a trouvé, on prend tous les caractères reçus jusque là, et on a un bloc entier de données. Les caractères qui suivront seront le début du bloc suivant.

    Cette méthode a l'avantage d'être simplissime. Son principal inconvénient est qu'il faut trouver un caractère ou une suite de caractères qu'on est certain de ne jamais trouver dans les 'vraies' données. En effet, l'exemple ci-dessus fonctionne parfaitement ... tant que les blocs de données eux-même ne contiennent par de retour chariot.

    2/ La technique de déclaration de la taille du paquet. C'est l'autre solution classique: lorsque l'émetteur désire envoyer un bloc de données, il va commencer par calculer sa taille totale en octets, puis il va envoyer la taille sur la socket (genre sous la forme de 4 octets représentant un 'int') avant le bloc de données. Côté récepteur, on saura qu'au début de chaque nouveau bloc, on recevra d'abord sa taille sur 4 octets. Une fois la taille reçue, on la décode, on la stocke et on prépare un buffer de la bonne taille pour les données à recevoir. Tant que le buffer n'est pas rempli, on ajoute ce qu'on reçoit dedans. Dès que le buffer est rempli, on a reàu un nouveau bloc de données, et on peut recommencer à chercher à décoder la taille du bloc suivant.
    Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android.

  4. #4
    Rédacteur

    Inscrit en
    Novembre 2006
    Messages
    1 272
    Détails du profil
    Informations forums :
    Inscription : Novembre 2006
    Messages : 1 272
    Points : 1 672
    Points
    1 672
    Par défaut
    Très bonne explication nouknouk en esperant que cela serve a d'autres.
    Vous voulez participer aux Tutoriels, FAQ ou Traductions et faire partie de l'équipe Qt de Developpez.
    N'hésitez pas à me contacter par MP.

  5. #5
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    82
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 82
    Points : 25
    Points
    25
    Par défaut
    A priori le flush a résolu la chose. Mais j'ai touvé une autre façon de procéder plus sûre que ce que je faisais :

    nouknouk en effet, tu as raison. Dans ce cas il me suffit de récupérer octet par octet les données de ma trame et détecter mon octet de fin de trame. Et comme le protocole que j'ai créée stipule une suite d'octets spéciaux en fin de trame, il me suffit donc de les détécter pour récupérer ma trame complète.

  6. #6
    Modérateur
    Avatar de nouknouk
    Homme Profil pro
    Inscrit en
    Décembre 2006
    Messages
    1 655
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 1 655
    Points : 2 161
    Points
    2 161
    Par défaut
    J'ai édité mon post ci-dessus entre temps, pour le compléter. Désolé pour le contretemps

    Citation Envoyé par Sleeping Lionheart Voir le message
    A priori le flush a résolu la chose. Mais j'ai touvé une autre façon de procéder plus sûre que ce que je faisais
    Effectivement, le flush oblige l'émission des données non encore envoyées (faire un flush à chaque write() est l'équivalent de la désactivation de l'algorithme de Nagle).
    Mais comme précisé ci-dessus, ce n'est pas propre, car même si ça marchera tant que tu auras des petits paquet, ça ne garanti finalement rien:
    On voit bien que quoi qu'on fasse on ne pourra pas garantir que le nombre de paquets reçus sera toujours égal au nombre de paquets émis. En effet, même en désactivant l'alog. de Nagle, on garanti que le nombre de paquets reçus sera au moins égal au nombre de paquets émis, mais rien ne nous garantis que les routeurs intermédiaires ne vont pas fragmenter un paquet en deux sous-paquets.
    Ta solution de marqueur de fin de paquet est bien la plus appropriée dans ton cas, car tu en as déjà implémenté la moitié !
    Attention cependant à bien garantir que quoi qu'il arrive, tes données 'utiles' ne contiendront jamais la même suite d'octets que ton marqueur, sinon c'est la corruption de ta connexion à tous les coups.
    Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android.

  7. #7
    Rédacteur

    Inscrit en
    Novembre 2006
    Messages
    1 272
    Détails du profil
    Informations forums :
    Inscription : Novembre 2006
    Messages : 1 272
    Points : 1 672
    Points
    1 672
    Par défaut
    Citation Envoyé par Sleeping Lionheart Voir le message
    nouknouk en effet, tu as raison. Dans ce cas il me suffit de récupérer octet par octet les données de ma trame et détecter mon octet de fin de trame. Et comme le protocole que j'ai créée stipule une suite d'octets spéciaux en fin de trame, il me suffit donc de les détécter pour récupérer ma trame complète.
    Tres bonne solution. C'est comme ça que l'on procede quand on fait de la transmission sur un reseaux ip ou autres. Enfin en UDP c'est plus lourd quand meme.
    Vous voulez participer aux Tutoriels, FAQ ou Traductions et faire partie de l'équipe Qt de Developpez.
    N'hésitez pas à me contacter par MP.

  8. #8
    Modérateur
    Avatar de nouknouk
    Homme Profil pro
    Inscrit en
    Décembre 2006
    Messages
    1 655
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 1 655
    Points : 2 161
    Points
    2 161
    Par défaut
    Citation Envoyé par superjaja Voir le message
    Tres bonne solution. C'est comme ça que l'on procede quand on fait de la transmission sur un reseaux ip ou autres.
    Oui et non, l'autre solution que j'ai exposé (envoyer la longueur du paquet à suivre) est elle aussi beaucoup utilisée ... à commencer dans le protocole IP lui-même qui contient un champ (total length) pour la longueur totale du paquet
    Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android.

  9. #9
    Rédacteur

    Inscrit en
    Novembre 2006
    Messages
    1 272
    Détails du profil
    Informations forums :
    Inscription : Novembre 2006
    Messages : 1 272
    Points : 1 672
    Points
    1 672
    Par défaut
    Citation Envoyé par nouknouk Voir le message
    Oui et non, l'autre solution que j'ai exposé (envoyer la longueur du paquet à suivre) est elle aussi beaucoup utilisée ... à commencer dans le protocole IP lui-même qui contient un champ (total length) pour la longueur totale du paquet
    Tu as tout a fait raison. ce que je voulais dire c'est qu'il faut se baser sur un protocole et non sur des astuce pas fiable comme je fais un flush() et normalement a chaque flush() j'ai la trame que je voulais. Sinon ton explication est tres bonne et se serait peut etre bien de la caser quelque part mais je sais pas trop où. Peut etre dans la FAQ Qt dans la partie reseau à voir si cela t'interresse et avec les autres membre de l'equipe.
    Vous voulez participer aux Tutoriels, FAQ ou Traductions et faire partie de l'équipe Qt de Developpez.
    N'hésitez pas à me contacter par MP.

  10. #10
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    82
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 82
    Points : 25
    Points
    25
    Par défaut
    La question, que je me pose donc est, peut-on modifier un header IP?
    Si c'est possible alors l'intégrité des données reçu n'est plus assuré. Dans mon protocole je vérifie l'intégrité, mais je suis curieux de savoir.

  11. #11
    Rédacteur

    Inscrit en
    Novembre 2006
    Messages
    1 272
    Détails du profil
    Informations forums :
    Inscription : Novembre 2006
    Messages : 1 272
    Points : 1 672
    Points
    1 672
    Par défaut
    Citation Envoyé par Sleeping Lionheart Voir le message
    La question, que je me pose donc est, peut-on modifier un header IP?
    C'est a dire ? Dans quel cas cela te serait contraignant ?
    Vous voulez participer aux Tutoriels, FAQ ou Traductions et faire partie de l'équipe Qt de Developpez.
    N'hésitez pas à me contacter par MP.

  12. #12
    Modérateur
    Avatar de nouknouk
    Homme Profil pro
    Inscrit en
    Décembre 2006
    Messages
    1 655
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 1 655
    Points : 2 161
    Points
    2 161
    Par défaut
    Citation Envoyé par Sleeping Lionheart Voir le message
    La question, que je me pose donc est, peut-on modifier un header IP?
    On peut en théorie (en passant par des 'RAW socket' qui permettent un accès très bas niveau au réseau).
    En pratique, ça n'a pour ainsi dire aucun intérêt puisque si tu ne te conformes plus au protocole standard IP, ton paquet sera plus routable sur les équipements réseaux standard (donc le net) car il détecteront que ton paquet est 'anormal' et le rejettront systématiquement.

    Citation Envoyé par superjaja Voir le message
    Sinon ton explication est tres bonne et se serait peut etre bien de la caser quelque part mais je sais pas trop où. Peut etre dans la FAQ Qt dans la partie reseau à voir si cela t'interresse et avec les autres membre de l'equipe.
    Yan (anciennement MonGaulois) m'a justement filé un accès pour la FAQ Qt hier. C'est effectivement une bonne idée de l'ajouter dedans, la question étant assez récurrente.
    Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android.

  13. #13
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    82
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 82
    Points : 25
    Points
    25
    Par défaut
    Bah si l'on se fiait uniquement à la taille donnée dans le header IP, et que imaginons celui-ci ait été modifié, ce serait dangereux je pense.
    Enfin c'est juste une question.

  14. #14
    Rédacteur

    Inscrit en
    Novembre 2006
    Messages
    1 272
    Détails du profil
    Informations forums :
    Inscription : Novembre 2006
    Messages : 1 272
    Points : 1 672
    Points
    1 672
    Par défaut
    Nouknouk à répondu à ta question.
    Sinon je crois que les RAW socket ne sont meme pas accessible avec Qt enfin je peux me tromper.

    Citation Envoyé par nouknouk
    Yan (anciennement MonGaulois) m'a justement filé un accès pour la FAQ Qt hier. C'est effectivement une bonne idée de l'ajouter dedans, la question étant assez récurrente.
    Alors on t'attend sur le forum de l'équipe.
    Vous voulez participer aux Tutoriels, FAQ ou Traductions et faire partie de l'équipe Qt de Developpez.
    N'hésitez pas à me contacter par MP.

  15. #15
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    82
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 82
    Points : 25
    Points
    25
    Par défaut
    Ok, ben je vous remercie.
    Je vais finir d'implémenter tout ça, je me resservirai de ce topic, si je rencontre un autre problème en rapport avec le sujet.

    Sur ce, bonne soirée à vous.

  16. #16
    Modérateur
    Avatar de nouknouk
    Homme Profil pro
    Inscrit en
    Décembre 2006
    Messages
    1 655
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 1 655
    Points : 2 161
    Points
    2 161
    Par défaut
    Citation Envoyé par superjaja Voir le message
    Sinon je crois que les RAW socket ne sont meme pas accessible avec Qt enfin je peux me tromper.
    Je ne crois pas non plus, les RAW socket étant assez bas niveau pour qu'on tombe dans des points techniques qui sont dépendants du système d'exploitation (à opposer à l'approche multi-OS de Qt).

    Cepedant, à titre d'information, il y a un moyen de sortir du 'carcan' de Qt en tapant dans les QAbstractSocket avec notamment le socketDescriptor, etc...
    Mais là on tombe vraiment dans des besoins très spécifiques (et nécessite l'implémentation de sa propre classe dérivée de QAbstractSocket) dont 1 personne sur 1000 aura réellement l'utilité.

    D'une façon générale 99.9% des besoins en matière de réseau se satisfont largement des deux protocoles standards, TCP et UDP.
    Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android.

  17. #17
    Rédacteur

    Inscrit en
    Novembre 2006
    Messages
    1 272
    Détails du profil
    Informations forums :
    Inscription : Novembre 2006
    Messages : 1 272
    Points : 1 672
    Points
    1 672
    Par défaut
    Merci pour la confirmation.
    Vous voulez participer aux Tutoriels, FAQ ou Traductions et faire partie de l'équipe Qt de Developpez.
    N'hésitez pas à me contacter par MP.

Discussions similaires

  1. Problème de connexion avec les sockets
    Par x-programer dans le forum Objective-C
    Réponses: 0
    Dernier message: 15/01/2013, 06h22
  2. Problème de lecture avec les Socket
    Par Kevin12 dans le forum Entrée/Sortie
    Réponses: 2
    Dernier message: 18/03/2009, 16h40
  3. Problème avec les sockets et la fonction accept
    Par projeticq dans le forum Réseau
    Réponses: 6
    Dernier message: 13/04/2007, 12h37
  4. Problème d'envoie de text avec les sockets
    Par Coussati dans le forum Web & réseau
    Réponses: 6
    Dernier message: 16/09/2005, 11h26
  5. [Win32] Problème avec les sockets
    Par mickael777 dans le forum MFC
    Réponses: 3
    Dernier message: 12/08/2005, 12h15

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo