OVH Cloud OVH Cloud

sockets asynchrone, FD_READ et nbre d'octets

27 réponses
Avatar
Nicolas Aunai
bonjour

je me demande s'il existe un nombre d'octets limités sur un socket,
annoncé par un FD_READ, pour allouer ue taille fixe et suffisante a un
buffer de reception... ?

autre petite question sur un point pas clair :
je reçoit un FD_READ, et j'ai disons 3ko sur la socket qui attendent
d'etre reçus. 3ko sur un total de 10ko que mon serveur a envoyé.

je reçoit les données annoncées par le message avec ioctlsocket et
FIONREAD, qui m'indique la quté de donnée pouvant etre lues sur un seul
recv, disons que cette quté est inférieure aux 3ko présents...et
qu'elle est égale a...2ko

aurai-je un autre FD_READ pour le 1ko restant ? ou le prochain FD_READ
se produira-t-il lorsqu'un autre paquet de données arrivera sur la
socket ?


--
Nico,
http://astrosurf.com/nicoastro

10 réponses

1 2 3
Avatar
Dominique Vaufreydaz
Bonjour,

alors disons qu'il y a n octets a lire, et m est retourné par
ioctlsocket avec FIONREAD, si m<n comment connaitre n ? je ne peux pas
allouer de mémoire pour mon buffer de réception, c'est bien dommage.



On se fout de ce qu'il y a a lire... Chaque fois que tu recois des données,
tu boucles sur un buffer de taille intelligente (la taille de la structure de données
qu'on t'a envoyer) et tu boucle jusqu'a ce qu'il n'y ai plus rien... Regarde comment
sont codé les serveur en free source (appache, ou tout autre)...

je ne vois pas comment faire dans le cas d'un trop gros nombre
d'octets.



Soit tu peux envoyer la taille a chaque fois la taille (soit 32 bits = 4 octets)
et apres avoir lu la taille, tu peux allouer le reste mais c'est pas top beau...

Doms.
--
Impose ta chance, serre ton bonheur et va vers ton risque.
A te regarder, ils s'habitueront.
René Char, Les Matinaux.
----
http://www-prima.inrialpes.fr/Vaufreydaz/
http://slmg.imag.fr/
http://slmg-index.imag.fr/
http://TitchKaRa.free.fr/
http://logiciels.ntfaqfr.com/
Avatar
Cyrille \cns\ Szymanski
> alors disons qu'il y a n octets a lire, et m est retourné par
ioctlsocket avec FIONREAD, si m<n comment connaitre n ? je ne peux pas
allouer de mémoire pour mon buffer de réception, c'est bien dommage.



En gros la réception tombe dans deux cas :
* Soit tu connais a priori la taille de l'objet que tu veux récupérer
(avec tes notations n=constante). Là pas dur, tu alloues n.
* Soit tu ne la connais pas et là c'est plus dur.

Une requête HTTP par exemple est de taille variable et terminée par un
"rnrn". La méthode crevard consiste à récupérer les octets un par un et
s'arrêter dès que strstr( buffer, "rnrn" )!=0. La méthode meilleure
consiste à rajouter un niveau :
* La couche programme demande des données à "buffer intelligent"
* "buffer intelligent" est rempli à chaque réception de FD_READ.

mais si un autre FD_READ, aucun problème alors, le restant des données
sera reçu plus tard et ajouté au buffer approprié.



Oui

--
_|_|_| CnS
_|_| for(n=0;b;n++)
_| b&=b-1; /*pp.47 K&R*/
Avatar
Cyrille \cns\ Szymanski
> le code de la réception est basé sur 3 buffers char *.



Essaie d'utiliser des buffers généraux et pas des chaînes asciiz, le
jour où tu voudras recevoir des données binaires tu auras moins de mal.


1er cas, on a trouvé la fin avant d'avoir vidé tout le recvbuf dans
buf.
dans ce cas, mettre les octets "en trop" dans le 3eme buffer appelé
'savebuf', qui sera réutilisé lors d'un prochain FD_READ avant le
vidage de recvbuf.
ensuite noter la position courante dans ce buffer, et analyser les
données du paquet de 'buf'



A mon avis c'est plus simple si tu lis un à un les octets du recvbuf
(fournis des fonctions du genre get et peek). Ça t'évitera le troisième
buffer. Le plus important c'est de recevoir toutes les données reçues en
bloc.

L'idée c'est que tu veux recevoir un bloc, dont la fin est par exemple
marquée par ".END". Alors tu vas recevoir 1 à 1 les octets. Pour cela, tu
fais un appel à recv_buf (ta fonction de buffer de réception intelligent).
Si le recvbuf contient déjà des données, tu renvoies l'octet demandé et tu
le supprimes du buffer. Si recvbuf est vide, tu fais un appel à recv() pour
le remplir au maximum (cas sockets bloquantes), soit tu renvoies un message
comme quoi le buffer est vide (cas sockets non-bloquantes) et le buffer
sera rempli à la prochaine réception de FD_READ.

a+
--
_|_|_| CnS
_|_| for(n=0;b;n++)
_| b&=b-1; /*pp.47 K&R*/
Avatar
Nicolas Aunai
"Cyrille "cns" Szymanski" a pensé très fort :
le code de la réception est basé sur 3 buffers char *.



Essaie d'utiliser des buffers généraux et pas des chaînes asciiz, le
jour où tu voudras recevoir des données binaires tu auras moins de mal.




mais je peux très bien recevoir des données binaires dans mon appli,
par exemple j'ai un cas où le body contient une image jpeg. la
transmission sera alors composée de :

"head.JPEG.taille_jpeg.END"
"body0101010010110101001....(byte du jpeg).END"
"end"


A mon avis c'est plus simple si tu lis un à un les octets du recvbuf
(fournis des fonctions du genre get et peek). Ça t'évitera le troisième
buffer. Le plus important c'est de recevoir toutes les données reçues en
bloc.



lire 1 a 1 les octets du recvbuf ? ou bien lire 1 a 1 les octets de la
socket pour les mettre dans le recvbuf ?

parce que dans mon code là, je lis déjà 1 a 1 les octets du recvbuf. il
se trouve que ce même buffer peu très bien contenir un bloc entier +
quelques bytes d'un autre... rah, ça me fait penser qu'il peut aussi
contenir les 3 blocs d'un coup... non ? :( j'avais pas prévu ça.

L'idée c'est que tu veux recevoir un bloc, dont la fin est par exemple
marquée par ".END".



oui.

Alors tu vas recevoir 1 à 1 les octets.



hum..ok

Pour cela, tu
fais un appel à recv_buf (ta fonction de buffer de réception intelligent).
Si le recvbuf contient déjà des données, tu renvoies l'octet demandé et tu
le supprimes du buffer.



quoi ?

Si recvbuf est vide, tu fais un appel à recv() pour
le remplir au maximum (cas sockets bloquantes), soit tu renvoies un message
comme quoi le buffer est vide (cas sockets non-bloquantes) et le buffer
sera rempli à la prochaine réception de FD_READ.



je comprend pas grand chose a ce que tu racontes...désolé.


a+






--
Nico,
http://astrosurf.com/nicoastro
Avatar
Nicolas Aunai
>> Pour manipuler le recvbuf tu fournis des fonctions, appelons les
recvbuf_recv et recvbuf_peek par exemple. Ce que je te propose c'est tout
simple. C'est de faire ton programme "comme" si tu recevais tes données 1
octet à la fois depuis la socket, sauf qu'au lieu d'appeler recv() tu
appelles recvbuf_recv.



n'est-ce pas déjà ce que je fais ?
seulement, que faire des octets de données restés non lues dans recvbuf
lorsque la fin d'un bloc a été détectée ?





j'ai regardé ça de plus près et décidément, c vraiment ce que je fais
déjà...
et il y a toujours les probèmes de l'allocation de la mémoire pour les
buffers (car les paquets qui arrivent ont une taille inconue), ainsi
que le problème des octets restant.

a moins que je n'ai tjrs pas compris ce que tu me raconte.


--
Nico,
http://astrosurf.com/nicoastro
Avatar
Cyrille \cns\ Szymanski
>>> Pour manipuler le recvbuf tu fournis des fonctions, appelons les
recvbuf_recv et recvbuf_peek par exemple. Ce que je te propose c'est
tout simple. C'est de faire ton programme "comme" si tu recevais tes
données 1 octet à la fois depuis la socket, sauf qu'au lieu
d'appeler recv() tu appelles recvbuf_recv.



n'est-ce pas déjà ce que je fais ?
seulement, que faire des octets de données restés non lues dans
recvbuf lorsque la fin d'un bloc a été détectée ?





Elles y restent. C'est ça qui est bien. Typiquement ton serveur suit une
boucle du genre :

1. attendre des données (ie. recvbuf est agrandi)
2. retrouver le contexte : retrouver quel client a envoyé les données
et où on en est dans le protocole
3. traiter ces données en branchant au bon endroit

Généralement j'implémente un serveur en mode bloquant d'abord, puis je le
passe en mode non bloquant ensuite. Expliciter l'emplacement de la zone
"attendre" permet de passer rapidement de l'un à l'autre.

En système bloquant cette opération est réalisée par une simple boucle :
while( ... )
{
select( ... );
traiter( ... )
}

On passe au fonctionnement asynchrone en mettant l'appel à traiter en
réponse à un FR_READ par exemple (il y a d'autres façons d'utiliser des
sockets non bloquantes).

Retrouver le contexte, c'est dans le cas où plusieurs sockets (ou
clients) sont connectés, il faut associer à l'appel de traiter une
structure qui mémorise l'état du client.

Ensuite vient la partie délicate du branchement. Généralement j'utilise
une méthode à états couplée à un pseudo switch. Le numéro de l'état est
mémorisé dans le contexte. Par exemple dans ton cas, on peut exhiber les
états suivants :

0 : pas de client
1 : client connecté, prêt à traiter une requête
2 : réception d'entête incomplète
3 : entête reçue, message incomplet
4 : requête complétée, attente de la suivante.

Le schéma est donc relativement simple (dans ce cas, mais peut être
beaucoup plus compliqué dans d'autres) :

-------------------
| |
V |
0 -> 1 -> 2 -> 3 -> 4
A |
| |
---------

La chose importante à noter à propos des états est si elle indique une
opération achevée ou inachevée.

0 : n/a
1, 4 : achevée
2, 3 : inachevée

Si tu termines sur une opération inachevée, c'est qu'une quantité
insuffisante de données ont été reçues. Si on termine sur un état achevé,
alors on peut reboucler pour traiter un nouveau message dans la foulée.

alors la fonction traiter se compose comme suit :

traiter( ... )
{
do
{
if( contexte.etat == 1 || contexte.etat == 4 )
{
contexte.etat = 2; // il est prêt et si traiter a ete appelé c'est
// qu'il y a des données à lire
}
if( contexte.etat == 2 )
{
ret = recv_entete();
if( ret != 0 )
{
// si la réception est incomplète, la valeur est nulle
traiter_entete();
contexte.etat = 3;
}
}
if( contexte.etat == 3 )
{
ret = recv_message();
if( ret != 0 )
{
traiter_message();
contexte.etat = 4; // ou 0 pour quitter
}
}
}
while ( contexte.etat == 4 ); // c'est reparti pour un tour
// sinon on est dans un état incomplet
}


Le tout est de faire un bon diagramme, de bien choisir les états et
ensuite coder c'est piece of cake.


j'ai regardé ça de plus près et décidément, c vraiment ce que je fais
déjà...
et il y a toujours les probèmes de l'allocation de la mémoire pour les
buffers (car les paquets qui arrivent ont une taille inconue), ainsi
que le problème des octets restant.

a moins que je n'ai tjrs pas compris ce que tu me raconte.



Je me demande a quoi te sert le troisième buffer. Tu dois avoir un moyen
de l'enlever.

Bon j'ai un peu détaillé l'aspect structure d'un serveur, j'espère que ça
ne sera pas tombé dans l'oreille d'un sourd... je suis en train d'écrire
là dessus alors si vous avez des suggestions n'hésitez pas.

a+
--
_|_|_| CnS
_|_| for(n=0;b;n++)
_| b&=b-1; /*pp.47 K&R*/
Avatar
Nicolas Aunai
"Cyrille "cns" Szymanski" a exposé le 06/09/2003 :

Elles y restent. C'est ça qui est bien. Typiquement ton serveur suit une
boucle du genre :



bon il se trouve que c'est pour la partie cliente de mon programme que
je cherche a faire ça, cependant la démarche est similaire.

1. attendre des données (ie. recvbuf est agrandi)



comment ça agrandi ?

2. retrouver le contexte : retrouver quel client a envoyé les données
et où on en est dans le protocole



ok.

3. traiter ces données en branchant au bon endroit



ok.

ok merci pour tous ces renseignements... encore trop tôt pour dire si
j'ai bien tout saisi... notamment quelques points restent flou :

-les octets qui restent non lus dans le recvbuf, je suppose qu'on se
trouve dans le cas 2 ou 3. par exemple si j'ai reçu une l'entete
(recv_entete(recvbuf)) et qu'il reste une partie du corps, je passe le
context.etat a 3 et je continue de lire dans le recvbuf là où j'étais.

ça implique une variable pour la position courante de lecture dans le
recvbuf, et de plus, une variable indiquant que l'on commence une
nouvelle partie.

exemple : je viens de passer comme dans le cas précédent a
context.etat=3, j'ai :

-une structure de données résultant de l'analyse de mon entete

-des octets restant dans mon recvbuf.

je dois allouer la mémoire pour mon buffer qui va contenir mon corps de
message, de la taille précisée dans l'entete (une des variables membre
de ma structure).

disons que j'ai réussi a allouer 200ko de mémoire pour mon corps de
message, je lis a partir de la position courante dans recvbuf avec
recv_message(recvbuf), cette fonction ne trouve pas le ".END" et donc,
toujours pour coller a ton schémas, retourne 0 (partie incomplete). là
je ne peut pas appeler traiter_message() et encore moins passer
context.etat a 4, je le passe donc a 1 ? et 1 serait le code pour dire
de remplir a nouveau le recvbuf ?


--
Nico,
http://astrosurf.com/nicoastro
Avatar
Nicolas Aunai
"Cyrille "cns" Szymanski" a présenté l'énoncé suivant :


ça implique une variable pour la position courante de lecture dans le
recvbuf, et de plus, une variable indiquant que l'on commence une
nouvelle partie.



Non pas obligatoirement.

Pour l'instant tout ce qui t'intéresse est que recvbuf fonctionne
"comme" le buffer de send(). Tu appelles une fonction qui copie des
données là où il faut et qui les vire du buffer. En clair tu ne
travailles pas directement dans le buffer. Ou bien tu peux travailler
dessus et utiliser des fonctions du genre peek ce qui t'évite de passer
ton temps à déplacer la mémoire, mais c'est moins propre (le buffer
recvbuf est un buffer de réception, ça ajoute de la confusion si on s'en
sert pour autre chose).



je suis bien d'accord, mais on est bien obligé d'avoir la position dans
le recvbuf de là où l'on c'était arrêté de lire la dernière fois :

recvbuf : xxxxxxxxxxxxxxxoooooooooo

si les x representent les octets lus, et les o les non lus, le dernier
x représente le 'D' d'un ".END" (sinon on ne serait pas arrété) et le
1er o représente le premier octet d'un nouveau bloc (par exemple le
corps du message). lors de la prochaine boucle, on sera dans un état 3,
entete reçue et message incomplet, il faudra commencer a lire a partir
du 1er o.

et ça, ça n'est possible qu'en ayant dans ta structure contexte une
variable indiquant qu'il faut se décaler de 15 octets (les x). ou bien
l'autre méthode consisterai a foutre des octets spéciaux a la place des
octets lus, mais ça complique le code, et de plus, va savoir ce qu'il
peut déjà y avoir dans un buffer de données binaires...

Maintenant comment le réaliser, une méthode est d'utiliser des realloc et
des memcopy mais elle peut être améliorée en faisant comme tu proposes,
un système de double pointeur début/fin/taille. Là c'est le sujet d'un
autre post si tu veux des explications. A priori il faut utiliser la
méthode la plus simple dans un premier temps, et n'optimiser qu'ensuite
si nécessaire.



realloc... realloc... j'y avais pensé mais bon, prenons encore un cas
concret :

j'ai reçu n octets, dont m<n octets correspondant a mon header.
je les copie (cas 2) dans le userbuf 1 par 1, en réallouant de 1 a chq
fois ?, étant donné que je ne connais pas la taille du header...

sinon j'alloue directement la taille reçue dans le recvbuf, elle peut
etre trop grande, trop petite (besoin d'un realloc après) ou pile poil.

disons que ça roule, j'ai fait relloc a chq octet, et j'ai pile poil la
bonne mémoire allouée, donc m octets. je traite mon header, rempli la
structure de contexte avec bodySize et BodyType, je passe a
contexte.etat=3 et vlam, dans le if contexte.etat=3, je réalloue la
taille de mon buffer pour qu'elle soit égale a BodySize. je copie alors
les n-m octets restant du recvbuf, si n-m==BodySize c perfecto on passe
a l'analyse du body etc... sinon on doit impérativement sortir de la
boucle pour appeler un recv() afin de remplir a nouveau au max le
recvbuf.

Non, tu restes à 3 qui veut dire "entête reçue et message incomplet". au
prochain appel on repart à ce niveau, recv_message() est appelé et disons
que cette fois le .END est lu, alors seulement on passe à 4.




je ne peux pas rester a 3 !! il n'y a plus d'octets non lus dans le
recvbuf, il faut bien sortir de la boucle de lecture pour le remplir !
je suis d'accord que le message est incomplet, donc 3, mais si le
recvbuf est vide... recv_message(recvbuf) ne va rien copier du tout...
d'autant plus que comme le dis Fred, pour le corps de message, pas
besoin de mettre de ".END" pour trouver la fin, étant donné qu'on
connait la taille, il n'y a qu'à lire dans le recvbuf tant qu'on a pas
copié BodySize octets. il faut penser cependant que BodySize est
certainement supérieur a la taille présente dans le recvbuf.


Ensuite on arrive à la fin de la fonction, on est dans un état achevé
donc on repart au début.



si et seulement si la fin du message se trouve bien dans le recvbuf.
je ne vois nulle part dans ton pseudo code l'endroit où tu "recharge"
ce buffer.

L'état passe à 2 qui signifie que l'entête est
incomplète. On essaie de la récupérer :
* S'il n'y a plus rien dans recvbuf, recv_entete() renvoie 0 et on sort
de la fonction
* S'il y a des choses mais pas une entête complète, idem.
* Si c'et bon on passe à l'état 3

... et ainsi de suite.



ah ! là d'accord, alors c la même chose pour le corps de message, si on
est a l'état 3 et que recv_message(recvbuf) retourne 0, on doit bien
sortir de la boucle pour recharger le recvbuf.
si je comprends, on reviendra ensuite dans la boucle avec un état 3,
donc on reprendra là où on en était. il faut donc conserver également
la position courante d'écriture dans le userbuf.

Ce qu'il faut que tu comprennes c'est le schéma suivant :

* Des données sont placées sur la pile TCP/IP
* Il y a un FD_READ, on remplit le recvbuf au maximum
* Ton programme lit le recvbuf. C'est bien de copier les données (je te
conseille de le faire bien que ce soit pas obligatoire c'est plus
propre). Les octets lus sont enlevé de recvbuf et placés dans un buffer
du contexte.

Un bon buffer a une interface similaire à la suivante:

int buffer_add_data( BUFFER *buf, char *data, size_t len );
---> cette fonction ajoute des données au buffer




mouais.... vois pas trop où ça sert.

size_t buffer_len( BUFFER *buf );
---> cette fonction renvoie la taille du buffer



vois pas trop l'utilité.

int buffer_get_data( BUFFER *buf, char *dest, size_t len );
---> cette fonction lit len octets et les copie en dest et les
données sont enlevées du buffer



ça ok.

int buffer_peek_data( BUFFER *buf, char *dest, size_t len );
---> même chose sauf que les données lues ne sont pas enlevées du
buffer



vois pas trop l'utilité non plus.

Utiliser ces objets (buffer, etats) a beaucoup d'avantages : d'abord ils
sont génériques, tout le monde sait ce que c'est et ça rend le code plus
lisible. Ensuite le code de buffer est utilisable ailleurs (donc vive le
copier coller). Et tu peux les implémenter comme tu veux ; par exemple
pour optimiser ton programme tu auras juste à changer le code relatif au
buffer et tu laisseras le reste intact (gain de temps). Et enfin le code
est plus clair.





j'pense que la première fois c assez compliqué, ensuite ça devient une
habitude...
pour l'instant faut se familiariser avec cette logique, pas évident.


--
Nico,
http://astrosurf.com/nicoastro
Avatar
Cyrille \cns\ Szymanski
> A l'init, on doit se mettre dans l'état de réception de header.



Il y a donc des états...

[...]

BYTE *pBuf; // !=NULL si on est en train de recevoir un body
BYTE *pRecv; //ptr de reception dans pBuf ou dans Hdr



Ah oui, voici où il est enregistré.

[...]

pBuf à NULL indique qu'on n'est pas en train de recevoir un
body mais bien un header.



C'est la condition pour savoir à quel état on est...

[...]

if( Context.cbToReceive == 0 )
{
//on a un 'truc' complet; ce 'truc' est soit un header,
//soit un buffer; pour le savoir on teste pBuf
if( pBuf == NULL )
{



[...]

}
else
{



[...]

Et là on branche sur le bon état.

}
}




On peut le faire comme ça évidemment, j'y vois quand même quelques
objections :

* En réalité, la structure en if avec pBuf masque celle d'un automate.
Pourquoi ne pas utiliser clairement un automate alors ? C'est
un peu plus long a écrire mais beaucoup plus clair.

* Visiblement tu n'as jamais débuggé le code que tu viens d'écrire...
Expliciter l'état a l'avantage qu'on sait précisément où on en est
dans le protocole et donc le déboguage est facilité. Dans ton cas,
les conditions vont être du genre "si pBuf est nul mais que pBody ne
l'est pas c'est que..., si pBuf est non nul et que cbRoRecv est plus
petit que..." ce qui est moins clair que "on est à l'état 1, 2 ou 3".

* Il faut aussi penser à l'évolution. Le jour où il veut améliorer son
programme et désire recevoir des objets plus complexes il va avoir
du mal à mettre son code à jour. J'aimerais bien voir la tête du
if/else dans le cas de réception d'une entête HTTP... Avec un automate
il "suffit" d'ajouter un état.


Et de plus, les automates ça fonctionne, ça se met sous forme de
diagramme, ça fait beau et ça impressionne les gens ;-)

--
PS: ta signature est incorrecte, le délimiteur doit être
tiret+tiret+espace : "-- ".

--
_|_|_| CnS
_|_| for(n=0;b;n++)
_| b&=b-1; /*pp.47 K&R*/
Avatar
Nicolas Aunai
bon voila des petites modif :

traiter( ... )
{
do
{
if( contexte.etat == 1 || contexte.etat == 4 )
{
contexte.etat = 2; // il est prêt et si traiter a ete appelé
c'est
// qu'il y a des données à lire
}
if( contexte.etat == 2 )
{
ret = recv_entete();

/*recv_entete copie 1 a 1 les octets de recvbuf dans userbuf tout
en :
- si nbr octet de userbuf >4 : vérifie les 4 dernier
- réallouant le userbuf de 1 octet.*/

if( ret != 0 )
{
// si la réception est incomplète, la valeur est nulle
/*noter la position de lecture dans le recvbuf pour que l'état
3 reprenne a partir
de là*/
traiter_entete();

/*traiter_entete découpe la chaine et stocke dans la structure
HEADER :
- BodyType
- BodySize (pour l'allocation du body)
*/
/*on peut libérer la mémoire de userbuf*/
free(userbuf)
contexte.etat = 3;
}
else if(ret==0)//l'entete ne tient pas toute entière dans le
recvbuf
{
/*sortie de boucle a contexte.etat==2 pour remplir le recvbuf
}

}
if( contexte.etat == 3 )
{
ret = recv_message();


/*on a BodySize normalement, on a qu'à allouer BodySize octets
pour userbuf
on reçoit tant qu'on a pas copié BodySize octets du recvbuf vers
userbuf.
si nbr_octet_copies==BodySize, retour !=0
si <BodySize ça veut dire que le recvbuf contient pas tout le
body, on retourne 0
*/

if( ret != 0 )
{

/*noter la position dans le recvbuf pour que la prochaine
reception reprenne a
partir de là (il peut déjà contenir un prochain header)*/
traiter_message();

/*selon BodyType on fait des trucs, notamment on dirige le
buffer vers le module
approprié a son traitement.
*/
/*on en a fini avec le message on peut libérer la mémoire.*/
free(userbuf);
contexte.etat = 4; // ou 0 pour quitter
}
if(ret==0)//body pas entier dans le recvbuf
{
/*code de sortie de boucle a contexte.etat==3, on doit
recharger le recvbuf*/
}
}
}
while ( contexte.etat == 4 || code_sortie!=RECHARGE_RECVBUF); // c'est
reparti pour un tour
// sinon on est dans un état incomplet
}


voila.... quelques modifications de la structure du programme...


--
Nico,
http://astrosurf.com/nicoastro
1 2 3