Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Problème de sortie de boucle (client pop)

13 réponses
Avatar
Bart
Bonjour, la question ne concerne que le C et non les sockets (sauf si
l'erreur viendrai de là...)
J'ai posé la même question sur developpez.com sans avoir de réponse
solutionnant ce code.

Ça fait un an que je n'ai plus rien codé, et aujourd'hui je m'y suis
remis et j'ai fait un petit client pop3 qui fonctionne pas trop mal.
Il se connecte, l'authentification est correcte, le stat est bon, les
messages peuvent être lues par un puts(bufferRead); bref ça va.
Seulement lire un message sur une console, surtout quand il est long, ce
n'est pas agréable, alors j'ai voulu sauvegarder les messages.
Voilà ma fonction pour recupérer les messages (toutes les autres sont
sur le même style) :

[CODE]int retr(char *bufferRead, char *bufferWrite, long nbRetr)
{

int err = 0;
memset(bufferRead, 0, sizeof bufferRead);
memset(bufferWrite, 0, sizeof bufferRead);

/* Envoie RETR au serveur */
sprintf(bufferWrite, "RETR %d\r\n", nbRetr);
printf("%s", bufferWrite);
err = send(sock, bufferWrite, strlen(bufferWrite), 0);
if (err == -1)
{
printf("Echec de l'envoi de \"%s\"\n",bufferWrite);
exit(EXIT_FAILURE);
}

/* Reception RETR */
err = recv(sock,bufferRead,PACKET, 0);
if (err == -1)
{
printf("Impossible de récuperer \"%s\"\n",bufferWrite);
exit(EXIT_FAILURE);
}
else
{
FILE *fp;
char nomFichier[255];
sprintf(nomFichier, "msg %d.txt", nbRetr);
if ((fp=fopen(nomFichier, "w+"))==NULL)
{
printf("Impossible d'enregistrer le mail.\n");
exit(EXIT_FAILURE);
}
/**************************************/
/* La j'essaye d'implémenter la sauvegarde du mail */
/**************************************/
}
}
[/CODE]

Alors ce que j'ai essayé (entre autre, parce que j'ai fait un peu tout
et n'importe quoi... surtout n'importe quoi puisque rien n'a
véritablement marché...)

fputs(bufferRead, fp). Donc déjà là, il y a un pb. Il enregistre le
message 1 sur plusieurs fichiers.

Je me suis dit, ça doit être à cause de la taille, et puis la fin de
message est caractérisée par un "\n.\n"
Alors j'ai fait une boucle infini, avec une recherche et comparaison
(strtok et strcmp donc) sur cette fin de message. Résultat boucle
inifini en effet... Après avoir enregistré correctement le message, le
dernier bloc de données se répete...
Après divers code que j'ai pris à droite, à gauche que je n'ai pas su
inserer correctement, j'ai mis celui d'Emmanuel Delahaye :

[CODE]
do
{
err = recv (sock, bufferRead, (int) sizeof bufferRead - 1, 0);

if (err != SOCKET_ERROR)
{
size_t nb_rec = (size_t) err;
bufferRead[nb_rec] = 0;
fprintf(fp, "%s", bufferRead);
if (strstr(bufferRead, "\n.\n") != NULL)
end = 1;


}
else
end = 1;
}
while (end != 1);
fclose(fp);
[/CODE]
Avec ça, j'ai le message qui est bien écrit dans le fichier, sans
"doublon", mais je ne sors pas de ma boucle.

Qu'est-ce que je fait de mal ?

Merci

10 réponses

1 2
Avatar
Eric Levenez
Le 10/06/07 13:35, dans <466be208$0$466$, « Bart »
a écrit :

Bonjour, la question ne concerne que le C et non les sockets (sauf si
l'erreur viendrai de là...)
J'ai posé la même question sur developpez.com sans avoir de réponse
solutionnant ce code.

Ça fait un an que je n'ai plus rien codé, et aujourd'hui je m'y suis
remis et j'ai fait un petit client pop3 qui fonctionne pas trop mal.
Il se connecte, l'authentification est correcte, le stat est bon, les
messages peuvent être lues par un puts(bufferRead); bref ça va.
Seulement lire un message sur une console, surtout quand il est long, ce
n'est pas agréable, alors j'ai voulu sauvegarder les messages.
Voilà ma fonction pour recupérer les messages (toutes les autres sont
sur le même style) :

[CODE]int retr(char *bufferRead, char *bufferWrite, long nbRetr)
{

int err = 0;
memset(bufferRead, 0, sizeof bufferRead);


Là tu effaces la taille d'un pointeur (soit typiquement 32 bits) et non pas
la taille de la zone pointée. Étrange.

memset(bufferWrite, 0, sizeof bufferRead);


Idem, avec en plus une mauvaise utilisation de bufferRead.

/* Envoie RETR au serveur */
sprintf(bufferWrite, "RETR %drn", nbRetr);


Là tu écris dans bufferWrite plus de 32 bits (donc aucun intérêt à l'avoir
effacé).

printf("%s", bufferWrite);


Un petit flush permettrait d'afficher le message que printf bufferise en
interne.

err = send(sock, bufferWrite, strlen(bufferWrite), 0);
if (err == -1)
{
printf("Echec de l'envoi de "%s"n",bufferWrite);
exit(EXIT_FAILURE);
}

/* Reception RETR */
err = recv(sock,bufferRead,PACKET, 0);


Tiens, là tu utilises PACKET comme taille alors qu'au dessus tu avais essayé
(à tort) d'utiliser sizeof...

if (err == -1)
{
printf("Impossible de récuperer "%s"n",bufferWrite);


Là c'est bufferRead, pas bufferWrite.

exit(EXIT_FAILURE);
}
else
{
FILE *fp;
char nomFichier[255];
sprintf(nomFichier, "msg %d.txt", nbRetr);
if ((fp=fopen(nomFichier, "w+"))==NULL)
{
printf("Impossible d'enregistrer le mail.n");
exit(EXIT_FAILURE);
}
/**************************************/
/* La j'essaye d'implémenter la sauvegarde du mail */
/**************************************/
}
}
[/CODE]

Alors ce que j'ai essayé (entre autre, parce que j'ai fait un peu tout
et n'importe quoi... surtout n'importe quoi puisque rien n'a
véritablement marché...)

fputs(bufferRead, fp). Donc déjà là, il y a un pb. Il enregistre le
message 1 sur plusieurs fichiers.


Comme à chaque message tu ouvres un nouveau fichier, c'est un peut normal,
non ? Et comme tu n'as pas posté le code d'écriture, tu as aussi pu te
tromper.

Je me suis dit, ça doit être à cause de la taille, et puis la fin de
message est caractérisée par un "n.n"
Alors j'ai fait une boucle infini, avec une recherche et comparaison
(strtok et strcmp donc) sur cette fin de message. Résultat boucle
inifini en effet... Après avoir enregistré correctement le message, le
dernier bloc de données se répete...
Après divers code que j'ai pris à droite, à gauche que je n'ai pas su
inserer correctement, j'ai mis celui d'Emmanuel Delahaye :

[CODE]
do
{
err = recv (sock, bufferRead, (int) sizeof bufferRead - 1, 0);


Là tu veux lire la taille d'un pointeur moins 1 char, ce qui sur une
architecture 32 bits veut dire lire 24 bits. Est-ce vraiment ce que tu
voulais ?


if (err != SOCKET_ERROR)


Le valeur à tester est -1

{
size_t nb_rec = (size_t) err;
bufferRead[nb_rec] = 0;


Ton tableau est indéxé sur le code de retour de recv ? C'est sensé vouloir
faire quoi.

fprintf(fp, "%s", bufferRead);


Là je ne comprends plus, ton 24 bits (max) que tu as lu est considéré comme
une chaîne de caractère terminé par un 0. C'est cela que tu espérais ?

if (strstr(bufferRead, "n.n") != NULL)


Dans un protocole type TCP/IP, tu n'as aucune garantie que ces 3 caractères
soient envoyées dans un même paquet. Sans parler du fait que c'est
généralement CRLF et non LF la fin de ligne.

end = 1;


}
else
end = 1;
}
while (end != 1);
fclose(fp);
[/CODE]
Avec ça, j'ai le message qui est bien écrit dans le fichier, sans
"doublon", mais je ne sors pas de ma boucle.

Qu'est-ce que je fait de mal ?


La liste est longue...

--
Éric Lévénez -- <http://www.levenez.com/>
Unix is not only an OS, it's a way of life.

Avatar
Bart
Déjà, merci d'avoir répondu.

[snip bufferRead, bufferWrite]

En effet, en testant tout et n'importe quoi, j'ai laissé des erreurs
dont les mauvaises utilisations avec bufferRead, sizeof, etc...
Je pense les avoir corrigées désormais.

Comme à chaque message tu ouvres un nouveau fichier, c'est un peut normal,
non ? Et comme tu n'as pas posté le code d'écriture, tu as aussi pu te
tromper.


Oui et non.
Je me suis sans doute mal exprimé. Pour faire simple, disons que le
message 1 va être morcellé dans les fichiers "msg 1.txt", "msg 2.txt" et
"msg 3.txt"

Pour ce qui est de l'écriture, c'est simple.

-- main.c --
long nbMsg = listing(bufferRead, bufferWrite); /* cette fonction récuper
le nombre de message par STAT, où j'applique un sscanf. La réponse est
toujours juste */

long msgAlire;
for (msgAlire=1; msgAlire<=nbMsg; msgAlire++)
{
retr(bufferRead, bufferWrite, msgAlire);

}

et dans la fonction retr... c'est comme j'ai dit un peu plus en avant.
fputs(bufferRead, fp); C'est simple ça le code d'écriture.
D'où mes recherches sur d'autres façons d'écrire.


Après divers code que j'ai pris à droite, à gauche que je n'ai pas su
inserer correctement, j'ai mis celui d'Emmanuel Delahaye :
^^^^^^^^^^^^^^^^^^^^^^^^^


(pour les erreurs "if (err != SOCKET_ERROR)" et size_t nb_rec = (size_t)
err; bufferRead[nb_rec] = 0;") Je me suis inspiré du code de cette
personne car il marchait assez bien, sauf la sortie de boucle.



fprintf(fp, "%s", bufferRead);


Là je ne comprends plus, ton 24 bits (max) que tu as lu est considéré comme
une chaîne de caractère terminé par un 0. C'est cela que tu espérais ?


J'avoue que j'espèrais seulement que ça écrive dans le fichier. Et de
plus comme la règle est de poster son code pour montrer quand il y a un
problème c'est que j'ai fait. Du moins, celui contenant le "moins" de
problème...

if (strstr(bufferRead, "n.n") != NULL)


Dans un protocole type TCP/IP, tu n'as aucune garantie que ces 3 caractères
soient envoyées dans un même paquet. Sans parler du fait que c'est
généralement CRLF et non LF la fin de ligne.



Comment connaître la fin du message dans ce cas ?


Avatar
Eric Levenez
Le 10/06/07 15:50, dans <466c01b0$0$31435$, « Bart »
a écrit :

Déjà, merci d'avoir répondu.

[snip bufferRead, bufferWrite]

En effet, en testant tout et n'importe quoi, j'ai laissé des erreurs
dont les mauvaises utilisations avec bufferRead, sizeof, etc...
Je pense les avoir corrigées désormais.


Peut-être, faudrait voir...

Comme à chaque message tu ouvres un nouveau fichier, c'est un peut normal,
non ? Et comme tu n'as pas posté le code d'écriture, tu as aussi pu te
tromper.


Oui et non.
Je me suis sans doute mal exprimé. Pour faire simple, disons que le
message 1 va être morcellé dans les fichiers "msg 1.txt", "msg 2.txt" et
"msg 3.txt"

Pour ce qui est de l'écriture, c'est simple.

-- main.c --
long nbMsg = listing(bufferRead, bufferWrite); /* cette fonction récuper
le nombre de message par STAT, où j'applique un sscanf. La réponse est
toujours juste */

long msgAlire;
for (msgAlire=1; msgAlire<=nbMsg; msgAlire++)
{
retr(bufferRead, bufferWrite, msgAlire);

}

et dans la fonction retr... c'est comme j'ai dit un peu plus en avant.
fputs(bufferRead, fp); C'est simple ça le code d'écriture.
D'où mes recherches sur d'autres façons d'écrire.


Faudrait voir... Dans chaque boucle, tu doit lire un buffer et l'écrire.
C'est bien ce que tu fais ?

Ce qu'il faut bien voir c'est que le buffer en lecture est écrasé à chaque
lecture et donc il ne peut servir de buffer temporaire.


Après divers code que j'ai pris à droite, à gauche que je n'ai pas su
inserer correctement, j'ai mis celui d'Emmanuel Delahaye :
^^^^^^^^^^^^^^^^^^^^^^^^^


(pour les erreurs "if (err != SOCKET_ERROR)" et size_t nb_rec = (size_t)
err; bufferRead[nb_rec] = 0;") Je me suis inspiré du code de cette
personne car il marchait assez bien, sauf la sortie de boucle.


Je suppose que SOCKET_ERROR c'est une windowserie quelconque, donc du non
standard Posix.

fprintf(fp, "%s", bufferRead);


Là je ne comprends plus, ton 24 bits (max) que tu as lu est considéré comme
une chaîne de caractère terminé par un 0. C'est cela que tu espérais ?


J'avoue que j'espèrais seulement que ça écrive dans le fichier. Et de
plus comme la règle est de poster son code pour montrer quand il y a un
problème c'est que j'ai fait. Du moins, celui contenant le "moins" de
problème...

if (strstr(bufferRead, "n.n") != NULL)


Dans un protocole type TCP/IP, tu n'as aucune garantie que ces 3 caractères
soient envoyées dans un même paquet. Sans parler du fait que c'est
généralement CRLF et non LF la fin de ligne.



Comment connaître la fin du message dans ce cas ?


Il faut bufferiser dans une zone intermédiaire en faisant plusieurs accès en
lecture. C'est ensuite dans cette zone qu'il faut tester cette fin de
message. Une façon de faire est de mettre dans cette zone juste des lignes
terminées par un CRLF. Il faut tenir compte qu'une grande ligne pourrait
faire déborder la zone et qu'elle ne se terminerait pas forcément par ce
CRLF. Ensuite si tu trouves les 3 caractères ".rn" et si un buffer qui se
terminait par CRLF a été trouvé juste avant, alors là tu as une fin de
fichier.

--
Éric Lévénez -- <http://www.levenez.com/>
Unix is not only an OS, it's a way of life.



Avatar
Bart
[snip]

Bon je vais tenter de faire tout ça. Merci des conseils.
Pour SOCKET_ERROR j'ai oublier de préciser, c'est un #define qui vaut -1
#define SOCKET_ERROR -1
Avatar
Antoine Leca
En news:466be208$0$466$,
Bart va escriure:
{
FILE *fp;
char nomFichier[255];


FILENAME_LEN; c'est fait pour cela. Par exemple, il y a des systèmes de
fichiers qui permettent 255 caractères, ce serait bête de provoquer un
débordement pour si peu ;-)

Par contre, cela va te « forcer » à contrôler ce qui sort de sprintf, en
utilisant snprint.

Une autre solution c'est de calculer explicitement la longueur maximum
(d'après le protocole, nbRetr doit être inférieur à 2 Gi, donc 10 chiffres
donc 19 char. Mais bon, je trouve cela plus délicat, surtout si tu remplace
"msg " par "/chemin/utilisateur/local/jour/heure/message "...)


sprintf(nomFichier, "msg %d.txt", nbRetr);
if ((fp=fopen(nomFichier, "w+"))==NULL)


D'abord ce que tu as reçu est en format CR+LF (normalement...), pas en
format texte (lignes terminées par n); donc ÀMHA un format binaire serait
meilleur.

Ensuite, je suppose que le fichier n'existe pas... donc aucune raison
d'essayer d'ouvrir pour le lire ultérieurement, cela risque même de poser
des problèmes si ta bibliothèque standard insiste pour que le fichier existe
déjà dans un tel cas (devrait pas, mais bon).

Donc, "wb"


Alors ce que j'ai essayé (entre autre, parce que j'ai fait un peu tout
et n'importe quoi... surtout n'importe quoi puisque rien n'a
véritablement marché...)

fputs(bufferRead, fp).


Cf. au-dessus. Ton bufferRead contient du binaire, donc tu devrais utiliser
fwrite().

Donc déjà là, il y a un pb. Il enregistre le
message 1 sur plusieurs fichiers.

Je me suis dit, ça doit être à cause de la taille, et puis la fin de
message est caractérisée par un "n.n"


<HS sujet="protocole POP">
Non. La fin du message est caractérisée par une ligne contenant uniquement
"." suivi de l'indication de fin de ligne (CR+LF si le serveur est conforme,
LF seul si c'est un serveur Unix mal embouché).
S'il y a plus d'une ligne, le drapeau peut se réduire à CRLF.CRLF
("1512.1512" en ASCII) dans le cas général (plus le timeout). Si le
message est vide, le contenu est réduit à .CRLF (".1512").
Par ailleurs, un point en début de ligne (qui sera suivi d'un autre point,
en toute logique) doit être écarté.
</HS>

Alors j'ai fait une boucle infini, avec une recherche et comparaison
(strtok et strcmp donc) sur cette fin de message.


strtok() : tu cherches les ennuis.
Sachant qu'avec POP il est très peu probable que le paquet reçu ait été
fractionné (ou plus exactement, si tu t'occupes de ce genre de détails, il y
a plein d'autres choses à revoir), un simple strcmp() « devrait » faire
l'affaire ici; reste à rechercher la bonne chaîne; le plus robuste, à mon
sens, est de rechercher d'abord les fins de ligne (1512), vider le
tampon, re-remplir si besoin est, vérifier si le (nouveau) premier caractère
est un point, si oui voir s'il est suivi de 1512, si oui c'est fini, si
non (. en début de ligne) sauter le point initial et recommencer avec ce qui
est une nouvelle ligne.


Antoine

Avatar
Antoine Leca
En news:466c2ff7$0$28554$,
Bart va escriure:
[snip]

Bon je vais tenter de faire tout ça. Merci des conseils.
Pour SOCKET_ERROR j'ai oublier de préciser, c'est un #define qui vaut
-1 #define SOCKET_ERROR -1


Donc si tu veux tenir compte de la (juste) remarque d'Éric, tu devrais
ajouter

#ifndef SOCKET_ERROR
#define SOCKET_ERROR (-1)
#endif


Antoine

Avatar
Eric Levenez
Le 11/06/07 12:57, dans <f4j9r4$qsa$, « Antoine
Leca » a écrit :

En news:466c2ff7$0$28554$,
Bart va escriure:
[snip]

Bon je vais tenter de faire tout ça. Merci des conseils.
Pour SOCKET_ERROR j'ai oublier de préciser, c'est un #define qui vaut
-1 #define SOCKET_ERROR -1


Donc si tu veux tenir compte de la (juste) remarque d'Éric, tu devrais
ajouter

#ifndef SOCKET_ERROR
#define SOCKET_ERROR (-1)
#endif


En espérant que cette constante ne soit pas définie comme -2 dans un include
Posix...

--
Éric Lévénez -- <http://www.levenez.com/>
Unix is not only an OS, it's a way of life.


Avatar
Antoine Leca
En news:C293471F.AA6D2%, Eric Levenez va escriure:
Le 11/06/07 12:57, dans <f4j9r4$qsa$, « Antoine

En news:466c2ff7$0$28554$,
Bart va escriure:
[snip]

Bon je vais tenter de faire tout ça. Merci des conseils.
Pour SOCKET_ERROR j'ai oublier de préciser, c'est un #define qui
vaut -1 #define SOCKET_ERROR -1


Donc si tu veux tenir compte de la (juste) remarque d'Éric, tu
devrais ajouter

#ifndef SOCKET_ERROR
#define SOCKET_ERROR (-1)
#endif


En espérant que cette constante ne soit pas définie comme -2 dans un
include Posix...


Bah ?

Si un vendeur « s'amuse » à cela, soit c'est pour em***er ses clients (en
gros, les obliger à rester chez lui, donc faire la même chose que l'on
reproche généralement à M$), soit c'est qu'il n'a rien compris à la propreté
des espaces de noms (tout comme les gars de FTP software en 1993-94 :-D)...,
ou encore les deux à la fois.

Maintenant, si un comité Posix ou X/Open ou SC14 ou similaire « s'amuse » à
définir SOCKET_ERROR (avec une autre valeur que -1) dans un projet de norme,
même pour un obscur include même pas utilisé souvent, le dit projet est
immédiatement mort-né.


Antoine



Avatar
espie
In article <f4k13a$clb$,
Antoine Leca wrote:
En news:C293471F.AA6D2%, Eric Levenez va escriure:
Le 11/06/07 12:57, dans <f4j9r4$qsa$, « Antoine

En news:466c2ff7$0$28554$,
Bart va escriure:
[snip]

Bon je vais tenter de faire tout ça. Merci des conseils.
Pour SOCKET_ERROR j'ai oublier de préciser, c'est un #define qui
vaut -1 #define SOCKET_ERROR -1


Donc si tu veux tenir compte de la (juste) remarque d'Éric, tu
devrais ajouter

#ifndef SOCKET_ERROR
#define SOCKET_ERROR (-1)
#endif


En espérant que cette constante ne soit pas définie comme -2 dans un
include Posix...


Bah ?

Si un vendeur « s'amuse » à cela, soit c'est pour em***er ses clients (en
gros, les obliger à rester chez lui, donc faire la même chose que l'on
reproche généralement à M$), soit c'est qu'il n'a rien compris à la propreté
des espaces de noms (tout comme les gars de FTP software en 1993-94 :-D)...,
ou encore les deux à la fois.

Maintenant, si un comité Posix ou X/Open ou SC14 ou similaire « s'amuse » à
définir SOCKET_ERROR (avec une autre valeur que -1) dans un projet de norme,
même pour un obscur include même pas utilisé souvent, le dit projet est
immédiatement mort-né.



Moi dans ce cas precis, je ne definirais pas SOCKET_ERROR. Ca donne
l'impression que c'est un truc magique. Meme si c'est bizarre, la
convention, sous Unix, c'est que les appels systemes rates renvoient -1.

Ca ne rend pas le code plus clair de definir une constante qui vaut -1,
justement parce que c'est une pratique non standard...

Sans compter que tout programmeur unix lira sans broncher

err = recv(...);
if (err == -1) {
}




Avatar
Antoine Leca
En news:f4mpb1$1j0n$,
Marc Espie va escriure:
Moi dans ce cas precis, je ne definirais pas SOCKET_ERROR.


Et on rentre alors dans une affaire de style et de goût, et comme nous le
savons bien il n'y a pas de réponse « définitive ».

Ca donne l'impression que c'est un truc magique.


Oui.

Ca ne rend pas le code plus clair de definir une constante qui vaut
-1, justement parce que c'est une pratique non standard...


Ce n'est (peut-être) pas standard sous Unix, mais cela l'est sous Windows.
Et aussi dans un certain nombre de normes de programmation (la raison pour
laquelle c'est arrivé jusqu'à ce groupe).


Antoine

1 2