OVH Cloud OVH Cloud

Perte de paquets sous TCP

16 réponses
Avatar
Jean-Christophe
Avec un soft maison qui utilise winsock2, je teste une com en TCP
entre deux PC sous XP, et en local via Ethernet ca marche a 100 %.
Par contre via Internet entre deux PC (distants de 500 km)
il y a des pertes de quelques %.
Ca reste faible et j'ai une couche soft qui s'occuppe des time-out
et des retry, donc l'ensemble fonctionne comme il faut.
Mais je ne comprends pas la raison pour laquelle des paquets
envoyes n'arrivent pas a destination: est-ce qu'en mode
TCP le transport ne garantit pas la livraison des paquets ?

6 réponses

1 2
Avatar
Le Forgeron
Le 30/07/2009 18:32, Jean-Christophe nous fit lire :
On Jul 30, 3:49 pm, Olivier Miakinen :

Pourtant, en regardant ton code il me semble bien que l'intuition
du Forgeron était bonne, et que tu ouvres et fermes la connexion
à chaque fois.



Tu parles de l'appel a fSetTxSock(), comment
puis-je proceder sans l'etablir a chaque fois ?




En mettant l'ouverture de la socket à l'exterieur de ta boucle d'emission, en
préliminaire, et en réservant la fermeture à la fin de ta transmission globale, lorsque le
dernier acquittement a été reçu. (et normalement, tu n'aurais pas à faire tes
ack/time-out/retry)!

En outre, tu sembles utilisé deux connexions, via 2 sockets: une pour chaque sens.
C'est inutile, une même socket peut servir à recevoir et a émettre.
Même dans des threads différents, l'un peut écrire dedans pendant qu'un autre lit.
(Ou alors l'implémentation de MS est nulle, et ça m'étonnerait quand même beaucoup.)


Donc en effet tu ouvres une nouvelle connexion à chaque fSendBuffer. Qui
plus est, tu commences par fermer la précédente : t'es-tu assuré que
tout avait été envoyé ?



Non, car je suppose que l'emission precedente est forcement
partie puisque je ne la termine que lorsque send() retourne zero.
La fermeture de socket ne se realise qu'en quittant fSendBuffer();
(doute : est-ce que ca se tient ?)



Problème: send() rend la main lorsqu'il a accepté les données pour son buffer, pas
lorsqu'elles sont arrivés en face.
Ton code indique le contraire de ton indication: il ferme lorsque send() a accepté le
dernier octet. Pas retourné 0.
L'utilisation de Sleep me laisse sans voix.
(là, un petit select() d'éviterait de perdre du cpu pour rien, mais va falloir partir sur
des tutoriaux ou des bouquins, c'est trop long pour ici).

Et comme tu fermes rapidement la socket, il est impossible à TCP de te remonter le fait
qu'il a finit par échouer (un transport fiable, ça ne veut pas dire un transport capable
de l'impossible, mais un transport capable de prévenir d'une impossibilité; en ce sens,
TCP est un transport fiable... mais ton programme a coupé les ponts bien trop vite.
En théorie, close() et variantes peuvent remonter qu'il y a eu un soucis (surtout en mode
bloquant, ce qui est dans ton cas une bonne chose)... tu les ignores, et il est trop tard
en général pour réagir, de toutes façons (juste tu peux savoir que ça n'allait pas).

TCP est fiable, mais la rupture de connexion n'est pas négocié: chaque coté peut prendre
l'initiative de ne plus communiquer, sans avoir l'accord de l'autre coté.

Non, cette socket est bloquante, et je suppose que la fonction
send() ne rend la main qu'apres avoir envoye les donnees.
C'est pour cela que je boucle dessus pour envoyer
tous les blocs de donnees les uns apres les autres.



Et ça irait bien si tu n'ouvrais pas une connexion par segments.
(c'est du gâchis de ressources... il faut un temps de malade pour établir une connexion
TCP, comparativement à l'utiliser pour transmettre des données;)

Analogie: tu fais des frites chez McDo.
Ton code actuel remplit la friteuse d'huile, la chauffe, fait une portion de frites,
vidange la friteuse et la passe au lave-vaisselle... pour CHAQUE portion de frites!

Dans la limite de la législation alimentaire, ne te semblerait-il pas plus économique de:
Remplir la friteuse d'huile et la chauffer,
puis faire des portions de frites les unes après les autres selon la demande,
et seulement en fin de service, vidanger la friteuse et la laver.
?

Et je manque d'analogie pour expliquer que les codes d'erreurs, tes acks, doivent aussi
remonter dans la même socket, et non dans une socket séparée (même avec un thread séparé
de traitement).
(en fait, si ta communication est monodirectionnelle, tu n'as pas besoin d'avoir un flux
remontant: tant que la communication TCP est debout, la socket est contente et tu n'as pas
d'erreur. Par contre, si tu dois avoir un traitement des cas d'erreurs avec diagnostique
et tout, recevoir des messages de celui à l'autre bout peut être utile, parce que la
socket seule va être un peu trop "simple")

D'apres toi il semble que le probleme se situe a l'emission ?
Et non a la reception : est-ce que fListen() te semble correct ?
Comment m'y prendre pour envoyer sans ouvrir une sock via fSetTxSock
() ?



D'après moi, mais je ne sais pas comment le dire gentiment, le problème est dans la
conception de la solution utilisée... faudrait revoir l'architecture en posant le problème
simplement (problématique dont on ne nous a rien dit, alors pour aider, je vais être un
peu sec sur le sujet).

Voilà, rien de personnel, mais rester détaché n'est pas forcément facile.
Avatar
Jean-Christophe
On Jul 30, 10:25 pm, Le Forgeron

> comment puis-je proceder sans l'etablir a chaque fois ?
En mettant l'ouverture de la socket à l'exterieur de ta boucle d'emissi on, en
préliminaire, et en réservant la fermeture à la fin de ta transmiss ion globale, lorsque le
dernier acquittement a été reçu. (et normalement, tu n'aurais pas à faire tes
ack/time-out/retry)!



Ok, je vais faire ces modifs et tester en LAN,
quand ce sera ok je passerai au WAN.

En outre, tu sembles utilisé deux connexions, via 2 sockets: une pour c haque sens.
C'est inutile, une même socket peut servir à recevoir et a émettre.
Même dans des threads différents, l'un peut écrire dedans pendant q u'un autre lit.
(Ou alors l'implémentation de MS est nulle, et ça m'étonnerait quan d même beaucoup.)



Je croyais qu'il fallait une socket pour [IP:PORT] du PC local
et une autre pour [IP:PORT] du PC distant ?

Problème: send() rend la main lorsqu'il a accepté les données pour son buffer, pas
lorsqu'elles sont arrivés en face.



Ok, je comprends.

Ton code indique le contraire de ton indication: il ferme lorsque send() a accepté le
dernier octet. Pas retourné 0.



Oui tu as raison.

L'utilisation de Sleep me laisse sans voix.



Dommage, mais je crois que tu veux dire qu'il ne sert a rien ?

(là, un petit select() d'éviterait de perdre du cpu pour rien, mais v a falloir partir sur
des tutoriaux ou des bouquins, c'est trop long pour ici).



Ok je vais eplucher la doc de select();

Analogie: tu fais des frites chez McDo.
Ton code actuel remplit la friteuse d'huile, la chauffe, fait une portion de frites,
vidange la friteuse et la passe au lave-vaisselle... pour CHAQUE portion de frites!
Dans la limite de la législation alimentaire, ne te semblerait-il pas p lus économique de:
Remplir la friteuse d'huile et la chauffer,
puis faire des portions de frites les unes après les autres selon la de mande,
et seulement en fin de service, vidanger la friteuse et la laver.



Bon, pas ma peine de me parler d'une facon aussi stupide, ce n'est
pas
parce-que je n'ecris pas tous les jours des softs de com que je suis
debile.
Je prefere largement que tu me parles avec des termes techniques,
meme
si apres je dois plonger dans la doc pour comprendre les informations.

Et je manque d'analogie pour expliquer que les codes d'erreurs, tes acks, doivent aussi
remonter dans la même socket, et non dans une socket séparée (mêm e avec un thread séparé
de traitement).



Ok.

(en fait, si ta communication est monodirectionnelle, tu n'as pas besoin d'avoir un flux
remontant



Non, la com entre les deux PC se fait bien dans les deux sens.
Le meme programme tourne sur les deux PC et chacun
peut prendre l'initiative d'envoyer des donees,
c'est pourquoi je pensais qu'il fallait deux sockets.

tant que la communication TCP est debout, la socket est contente et tu n' as pas
d'erreur. Par contre, si tu dois avoir un traitement des cas d'erreurs av ec diagnostique
et tout, recevoir des messages de celui à l'autre bout peut être util e, parce que la
socket seule va être un peu trop "simple")



Ok.

D'après moi, mais je ne sais pas comment le dire gentiment, le problè me est dans la
conception de la solution utilisée... faudrait revoir l'architecture en posant le problème
simplement (problématique dont on ne nous a rien dit, alors pour aider, je vais être un
peu sec sur le sujet).



Mais on peut tres bien s'exprimer sans etre desagreable, et ca ne
tient qu'a toi.
Je sais combien il peut etre penible parfois d'expliquer une technique
a des
gens qui ne sont pas du metier, mais les prendre de haut n'est pas
plus efficace.

En ce qui concerne le soft, je l'ai ecrit pour apprendre a utiliser
les sockets,
le cahier des charges est qu'il doit juste permettre d'envoyer du
texte (chat)
et des fichiers d'un PC a un autre. Au debut j'etais parti sur UDP
parce-que
c'est plus facile a implementer, ca marchait nickel en LAN mais pas en
WAN.
Quand j'ai compris pourquoi je me suis decide a implementer le mode
TCP.

Voilà, rien de personnel, mais rester détaché n'est pas forcément facile.



Je ne vois pas comment cela aurait pu etre
personnel, puisqu'on ne se connait pas.
Pour moi, pas de probleme, tu as bien detaille les points a corriger.
Merci pour toutes ces infos.
Avatar
Olivier Miakinen
Le 31/07/2009 01:57, Jean-Christophe répondait au Forgeron :

En outre, tu sembles utilisé deux connexions, via 2 sockets: [...]



Je croyais qu'il fallait une socket pour [IP:PORT] du PC local
et une autre pour [IP:PORT] du PC distant ?



Une socket en mode connecté (TCP) contient à la fois les deux adresses
et les deux ports (« local » et « remote »). Et on peut faire aussi bien
du send() que du recv() dessus.

Au passage, si les deux côtés émettent en même temps, chacun peut
profiter de son envoi de données pour accuser réception du dernier
paquet reçu (« ACK »), ce qui peut diviser par deux le nombre de
paquets échangés.

L'utilisation de Sleep me laisse sans voix.



Dommage, mais je crois que tu veux dire qu'il ne sert a rien ?



Je ne sais pas ce que voulait dire le Forgeron, mais oui, moi je dis
qu'il ne sert à rien. Soit tu configures la socket en mode asynchrone et
tu feras un select() pour attendre qu'il y ait quelque chose à faire,
soit tu la laisses en mode synchrone et l'attente se fera au sein du
send() ou du recv().

(là, un petit select() d'éviterait de perdre du cpu pour rien, mais va falloir partir sur
des tutoriaux ou des bouquins, c'est trop long pour ici).



Ok je vais eplucher la doc de select();



L'immense intérêt du select(), c'est surtout quand tu peux avoir
plusieurs sources de données, par exemple le clavier où tu tapes des
commandes et une socket pour envoyer et recevoir des données. Dans ce
cas tu peux configurer le tout en mode asynchrone, et tu fais un
select() sur l'ensemble des sockets et file descriptors. Dès qu'il y a
quelque chose à faire, le select() te rend la main en te disant sur quoi
il y a des choses à lire (ou sur quoi tu peux écrire), et il n'y a plus
qu'à le faire.

Si tu n'as qu'une seul socket et rien d'autre à faire, alors pas besoin
de select() : tu laisses la socket en mode synchrone, et c'est elle qui
bloquera tant qu'il n'y a rien à lire ou que tu ne peux pas écrire.

Analogie: tu fais des frites chez McDo.
[...]



Bon, pas ma peine de me parler d'une facon aussi stupide, ce n'est
pas
parce-que je n'ecris pas tous les jours des softs de com que je suis
debile.
Je prefere largement que tu me parles avec des termes techniques,
meme
si apres je dois plonger dans la doc pour comprendre les informations.



Bien. Je ne te parlerai donc pas de façon stupide pour te dire que
Google groupes est une daube en tant que nouvelleur : il vaudrait mieux
utiliser un vrai logiciel de nouvelles sur un vrai serveur NNTP.

Mais si vraiment tu ne peux pas faire autrement, alors tu dois couper
toi-même les lignes à une longueur plus petite que la sienne, ou bien ne
pas couper du tout et le laisser faire. Parce que là, en coupant un tout
petit peu plus loin que Google groupes, ça fait une alternance de lignes
longues et de lignes courtes qui est tout à fait insupportable.

Au passage, pour le Forgeron : toi tu utilises un vrai nouvelleur, mais
tes lignes sont beaucoup trop longues. Ce serait sympa de remettre la
valeur par défaut (72 caractères).

(en fait, si ta communication est monodirectionnelle, tu n'as pas besoin d'avoir un flux
remontant



Non, la com entre les deux PC se fait bien dans les deux sens.
Le meme programme tourne sur les deux PC et chacun
peut prendre l'initiative d'envoyer des donees,
c'est pourquoi je pensais qu'il fallait deux sockets.



À partir du moment où la connexion est établie, ce quel qu'en soit
l'initiateur, la socket est parfaitement symétrique. Bien sûr, si tu
veux répondre « OK » à chaque paquet reçu et que ça ne se mélange pas
avec les données que tu vas envoyer toi-même, alors il faudra deux
sockets, mais tout semble indiquer que ces « OK » sont inutiles si tu
fais les choses comme il faut.

Cordialement,
--
Olivier Miakinen
Avatar
Jean-Christophe
On Jul 31, 8:12 am, Olivier Miakinen :

Une socket en mode connecté (TCP) contient à la fois les
deux adresses et les deux ports (« local » et « remote »).



Ok. Je suppose qu'apres qu'un PC ait recu des donnees,
celui-ci peut connaitre les IP:PORT de l'emetteur ?

Et on peut faire aussi bien du send() que du recv() dessus.



En meme temps ?
Est-ce qu'au meme instant, une meme socket peut etre
en cours de reception de donnees dans une thread,
et en cours d'emission dans une autre ?

Au passage, si les deux côtés émettent en même temps,
chacun peut profiter de son envoi de données pour accuser
réception du dernier paquet reçu (« ACK »), ce qui peut
diviser par deux le nombre de paquets échangés.



Mais heureusement, une fois les modifs faites,
ces ACK n'auront plus aucune raison d'etre.

Soit tu configures la socket en mode asynchrone et tu feras
un select() pour attendre qu'il y ait quelque chose à faire,
soit tu la laisses en mode synchrone et l'attente se fera
au sein du send() ou du recv().



Ok.

L'immense intérêt du select(), c'est surtout quand tu
peux avoir plusieurs sources de données, par exemple le
clavier où tu tapes des commandes et une socket pour
envoyer et recevoir des données. Dans ce cas tu peux
configurer le tout en mode asynchrone, et tu fais un
select() sur l'ensemble des sockets et file descriptors.
Dès qu'il y a quelque chose à faire, le select() te rend
la main en te disant sur quoi il y a des choses à lire
(ou sur quoi tu peux écrire), et il n'y a plus qu'à le
faire. Si tu n'as qu'une seul socket et rien d'autre
à faire, alors pas besoin de select() : tu laisses la
socket en mode synchrone, et c'est elle qui bloquera
tant qu'il n'y a rien à lire ou que tu ne peux pas écrire.



Etre bloquant en emission serait parfait, mais pas en
reception, car c'est justement parce-que la fonction
accept() est bloquante que j'ai du la faire tourner
dans une thread distincte de celle du traitement
des messages Windows (sinon ca freeze la fenetre)
D'autre part il est necessaire de pouvoir envoyer
tout en etant en etat "listen" pour la reception.

Bien. Je ne te parlerai donc pas de façon stupide
pour te dire que Google groupes est une daube en tant
que nouvelleur : il vaudrait mieux utiliser un vrai
logiciel de nouvelles sur un vrai serveur NNTP.
Mais si vraiment tu ne peux pas faire autrement, alors
tu dois couper toi-même les lignes à une longueur plus
petite que la sienne, ou bien ne pas couper du tout
et le laisser faire. Parce que là, en coupant un tout
petit peu plus loin que Google groupes, ça fait une
alternance de lignes longues et de lignes courtes
qui est tout à fait insupportable.



Ah, ok, j'essaierai d'y faire attention - bien que
cette limite de longueur de ligne soit un archaisme.

À partir du moment où la connexion est établie,
ce quel qu'en soit l'initiateur, la socket
est parfaitement symétrique.



N'importe quel PC peut prendre
l'initiative d'envoyer des donnees ?

Bien sûr, si tu veux répondre « OK » à chaque
paquet reçu et que ça ne se mélange pas avec
les données que tu vas envoyer toi-même,
alors il faudra deux sockets,



Le premier octet de chaque bloc de donnees est un
identificateur qui dirige un switch vers le traitement
approprie des donnees qui suivent : ca peut etre
du handshake, du texte, un bout de fichier, etc ...
et il n'y a aucun risque de melange.

mais tout semble indiquer que ces « OK » sont
inutiles si tu fais les choses comme il faut.



Oui, et c'est bien le but !

Je me pose une autre question :
Soient deux PC "A" et "B" faisant tourner le meme soft,
"A" connait l'IP de "B" et etablit la com entre eux :
est-ce que cela ne signifie pas qu'il y a un serveur
et un client, et par suite, une certaine asymetrie ?
Une fois la connection etablie, est-ce que cela permet
tout de meme a n'importe quel des deux PC de prendre
l'initiative d'envoyer des donnees a l'autre ?
Avatar
Olivier Miakinen
Le 31/07/2009 11:35, Jean-Christophe a écrit :

Une socket en mode connecté (TCP) contient à la fois les
deux adresses et les deux ports (« local » et « remote »).



Ok. Je suppose qu'apres qu'un PC ait recu des donnees,
celui-ci peut connaitre les IP:PORT de l'emetteur ?



Même *avant* qu'il ait effectivement reçu des données ! L'information se
trouve dans le résultat de la fonction accept(). Par ailleurs tu peux
aussi retrouver cette info par getpeername() (IP et port distants) et
getsockname() (IP et port locaux).

Et on peut faire aussi bien du send() que du recv() dessus.



En meme temps ?
Est-ce qu'au meme instant, une meme socket peut etre
en cours de reception de donnees dans une thread,
et en cours d'emission dans une autre ?



En principe oui, d'autant que les fonctions send() et recv() travaillent
sur des buffers internes, et pas sur l'envoi de trames ethernet !

Au passage, si les deux côtés émettent en même temps,
chacun peut profiter de son envoi de données pour accuser
réception du dernier paquet reçu (« ACK »), ce qui peut
diviser par deux le nombre de paquets échangés.



Mais heureusement, une fois les modifs faites,
ces ACK n'auront plus aucune raison d'etre.



Problème de vocabulaire ici : je parlais du ACK des trames TCP, pas des
« OK » que tu avais rajoutés ! Les ACK au niveau TCP ont bien sûr
toujours lieu d'être, et si l'échange est bidirectionnel chaque paquet
envoyé d'un côté peut servir de ACK à un paquet envoyé de l'autre. Si
l'échange n'est que dans un seul sens, alors des ACK seront envoyés
quand même, sans données.

Etre bloquant en emission serait parfait, mais pas en
reception, car c'est justement parce-que la fonction
accept() est bloquante que j'ai du la faire tourner
dans une thread distincte de celle du traitement
des messages Windows (sinon ca freeze la fenetre)
D'autre part il est necessaire de pouvoir envoyer
tout en etant en etat "listen" pour la reception.



Avec des sockets asynchrones et la fonction select(), il n'y a pas
besoin de plusieurs threads.

À partir du moment où la connexion est établie,
ce quel qu'en soit l'initiateur, la socket
est parfaitement symétrique.



N'importe quel PC peut prendre
l'initiative d'envoyer des donnees ?



Oui, absolument. Une fois la connexion établie, on ne peut plus savoir
qui l'a établie : la socket est vraiment symétrique.

Je me pose une autre question :
Soient deux PC "A" et "B" faisant tourner le meme soft,
"A" connait l'IP de "B" et etablit la com entre eux :
est-ce que cela ne signifie pas qu'il y a un serveur
et un client, et par suite, une certaine asymetrie ?



L'asymétrie existe à la connexion uniquement. Et encore, on pourrait
très bien imaginer que chacun soit serveur et se mette en attente d'une
nouvelle connexion par accept(), puis que le premier à avoir des données
à transmettre fasse le connect(). Une fois la connexion établie, chacun
peut l'utiliser à sa guise.

Une fois la connection etablie, est-ce que cela permet
tout de meme a n'importe quel des deux PC de prendre
l'initiative d'envoyer des donnees a l'autre ?



Oui.

--
Olivier Miakinen
Avatar
Jean-Christophe
On Jul 31, 11:30 am, Olivier Miakinen :

> Ok. Je suppose qu'apres qu'un PC ait recu des donnees,
> celui-ci peut connaitre les IP:PORT de l'emetteur ?
Même *avant* qu'il ait effectivement reçu des données !
L'information se trouve dans le résultat de la fonction
accept(). Par ailleurs tu peux aussi retrouver cette
info par getpeername() (IP et port distants)
et getsockname() (IP et port locaux).



D'accord.

> Est-ce qu'au meme instant, une meme socket peut etre
> en cours de reception de donnees dans une thread,
> et en cours d'emission dans une autre ?
En principe oui, d'autant que les fonctions send()
et recv() travaillent sur des buffers internes,
et pas sur l'envoi de trames ethernet !



Bon, dans ce cas c'est parfait.

Problème de vocabulaire ici : je parlais du ACK des
trames TCP, pas des «OK» que tu avais rajoutés !



Oui, je n'aurais pas du employer ce terme, c'est
parce-que dans mon algo il s'agit bien d'un acknowledge.

Les ACK au niveau TCP ont bien sûr toujours lieu d'être,
et si l'échange est bidirectionnel chaque paquet envoyé
d'un côté peut servir de ACK à un paquet envoyé de l'autre.
Si l'échange n'est que dans un seul sens, alors des ACK
seront envoyés quand même, sans données.



Bien recu.

> Etre bloquant en emission serait parfait, mais pas en
> reception, car c'est justement parce-que la fonction
> accept() est bloquante que j'ai du la faire tourner
> dans une thread distincte de celle du traitement
> des messages Windows (sinon ca freeze la fenetre)
> D'autre part il est necessaire de pouvoir envoyer
> tout en etant en etat "listen" pour la reception.
Avec des sockets asynchrones et la fonction select(),
il n'y a pas besoin de plusieurs threads.



Je ne vais pas tout modifier en meme temps, sinon en cas
de probleme je vais ramer pour debugger; mais a terme
ce sera bien sur plus simple sans thread supplementaire.

> N'importe quel PC peut prendre
> l'initiative d'envoyer des donnees ?
Oui, absolument. Une fois la connexion établie,
on ne peut plus savoir qui l'a établie :
la socket est vraiment symétrique.



Parfait.

> Soient deux PC "A" et "B" faisant tourner le meme soft,
> "A" connait l'IP de "B" et etablit la com entre eux :
> est-ce que cela ne signifie pas qu'il y a un serveur
> et un client, et par suite, une certaine asymetrie ?
L'asymétrie existe à la connexion uniquement. Et encore,
on pourrait très bien imaginer que chacun soit serveur
et se mette en attente d'une nouvelle connexion par
accept(), puis que le premier à avoir des données à
transmettre fasse le connect(). Une fois la connexion
établie, chacun peut l'utiliser à sa guise.



Si je peux m'en passer je m'en passerai,
mais je prends note de cette information.

> Une fois la connection etablie, est-ce que cela permet
> tout de meme a n'importe quel des deux PC de prendre
> l'initiative d'envoyer des donnees a l'autre ?
Oui.



Nickel, merci Olivier.
1 2