“Illégale chercher” erreur lors du travail avec la prise des cours d'eau avec des non-vide de lire les tampons
Je suis en train d'écrire une application serveur sur Linux x86_64
à l'aide de <sys/socket.h>
.
Après avoir accepté une connexion via accept()
, j'utilise fdopen()
pour envelopper retrouvée prise dans un FILE*
flux.
L'écriture et la lecture, que FILE*
stream fonctionne généralement très bien, mais le socket devient unsusable dès que j'écris pour elle, alors qu'elle a un non-vide la mémoire tampon de lecture.
Pour les besoins de la démonstration, j'ai écrit un code qui est à l'écoute pour une connexion, puis lit l'entrée, ligne par ligne, dans une mémoire tampon de lecture à l'aide de fgetc()
. Si la ligne est trop longue pour tenir dans la mémoire tampon, ce n'est pas entièrement lu, mais au lieu de lire au cours de la prochaine itération.
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
FILE* listen_on_port(unsigned short port) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in name;
name.sin_family = AF_INET;
name.sin_port = htons(port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sock, (struct sockaddr*) &name, sizeof(name)) < 0)
perror("bind failed");
listen(sock, 5);
int newsock = accept(sock, 0, 0);
return fdopen(newsock, "r+");
}
int main(int argc, char** argv) {
int bufsize = 8;
char buf[9];
buf[8] = 0; //ensure null termination
int data;
int size;
//listen on the port specified in argv[1]
FILE* sock = listen_on_port(atoi(argv[1]));
puts("New connection incoming");
while(1) {
//read a single line
for(size = 0; size < bufsize; size++) {
data = fgetc(sock);
if(data == EOF)
break;
if(data == '\n') {
buf[size] = 0;
break;
}
buf[size] = (char) data;
}
//check if the read failed due to an EOF
if(data == EOF) {
perror("EOF: Connection reset by peer");
break;
} else {
printf("Input line: '%s'\n", buf);
}
//try to write ack
if(fputs("ack\n", sock) == EOF)
perror("sending 'ack' failed");
//try to flush
if(fflush(sock) == EOF)
perror("fflush failed");
}
puts("Connection closed");
}
Le code doit compiler avec gcc sans aucun paramètre. Exécuter avec le numéro de port comme argument et utiliser netcat pour connecter localement.
Maintenant, si vous essayez d'envoyer des chaînes de caractères qui sont plus courtes que 8 caractères, cela fonctionnera parfaitement.
Mais si vous envoyez une chaîne de caractères contenant plus de 10 caractères, le programme échoue.
Cet exemple d'entrée:
ab
cd
abcdefghij
Va créer cette sortie:
New connection incoming
Input line: 'ab'
Input line: 'cd'
Input line: 'abcdefgh'
fflush failed: Illegal seek
EOF: Connection reset by peer: Illegal seek
Connection closed
Comme vous le voyez, (à juste titre) seuls les 8 premiers caractères de abcdefgh sont lus, mais lorsque le programme tente d'envoyer le 'ack' chaîne (que le client ne receves), puis vider le tampon de sortie, nous recevons un Illegal seek
d'erreur, et le prochain appel à fgetc()
retourne EOF.
Si le fflush()
partie est commentée, la même erreur se produit encore, mais le
fflush failed: Illegal seek
ligne est manquante à partir de la sortie du serveur.
Si le fputs(ack)
partie est commentée, tout semble fonctionner comme prévu, mais un perror() manuellement appelé à partir de gdb rapports toujours un "Illégal rechercher" de l'erreur.
Si les deux fputs(ack)
et fflush()
sont commentées, tout ne fonctionner comme prévu.
Malheureusement, je n'ai pas été en mesure de trouver une bonne documentation, ni Internet discussions sur ce problème, donc votre aide serait grandement appréciée.
modifier
La solution que j'ai finalement réglé pour pas utilisation fdopen()
et FILE*
, car il semble y avoir aucun moyen propre de la conversion d'un socket fd dans un FILE*
fiable utilisé dans r+
mode.
Au lieu de cela, j'ai travaillé directement sur le socket fd, l'écriture de mon propre code de remplacement pour fputs
et fprintf
.
Si quelqu'un en a besoin, voici le code.
Vous devez vous connecter pour publier un commentaire.
Clairement "r+" (lecture/écriture) mode ne fonctionne pas sur les sockets dans cette mise en œuvre, sans doute parce que le code sous-jacent suppose qu'il doit faire une recherche pour basculer entre la lecture et l'écriture. C'est le cas général avec stdio cours d'eau (que vous devez faire un peu de type de synchronisation de l'opération), parce que, dans la pénombre du Temps, réel stdio implémentations avait un seul compteur par un ruisseau, et c'était soit un compteur de "nombre de caractères de gauche à lire à partir de la mémoire tampon via
getc
macro" (en mode lecture) ou "nombre de caractères qui peut être écrit à la mémoire tampon du flux viaputc
macro (dans le mode d'écriture). À la seule contre-ré-défini, vous devez faire une recherche de type d'opération.Cherche ne sont pas autorisés sur les pipes et les sockets (depuis "l'offset du fichier" n'est pas significative).
Une solution est de ne pas enrouler une prise avec stdio à tous. Un autre, probablement plus facile /mieux à vos besoins, est l'envelopper avec, non pas un, mais deux stdio flux:
Il y a une autre faille ici, parce que quand vous allez à
fclose
un ruisseau, qui se ferme de l'autre descripteur de fichier. Pour contourner cela, vous devezdup
le descripteur de socket fois (dans l'un des deux appels ci-dessus, il n'est pas question que l'un).Si vous avez l'intention d'utiliser
select
oupoll
ou similaire sur le socket à un certain point, vous devriez aller pour le "ne pas envelopper avec stdio" solution, car il n'y a pas de bien propre portable pour suivre les stdio mise en mémoire tampon. (Il y a de mise en œuvre de manière spécifique).FILE* streams
, en particulier d'un uniqueFILE* stream
? Avant d'envelopper le descripteur de socket dans unFILE* stream
, j'ai eu perso des wrappers pourprintf()
etputs()
, mais je l'ai trouvé laid. Supposons que c'est le plus "propre" de la solution, après tout.asprintf
" le code est exactement ce que j'avais à l'esprit lorsque j'ai défini la valeur de retour desnprintf
la façon dont je l'ai fait. 🙂Ne pas utiliser
fflush()
sur les sockets réseau. Ils sont sans tampon ruisseaux.Aussi, ce code:
n'a pas lu une seule ligne. Il ne lit jusqu'à la taille de la mémoire tampon, ce qui vous définit comme 8.
sock
encore avoir des données pour vous à recevoir ce que vous devriez recevoir avant écriture dans le flux avecfputs
. BTW, vous pouvez remplacer tout le bloc avecfgets()
arrêter de lire au retour à la ligne?fgets()
lit pas plus den-1
personnages (parce que len
th, oubufsize
th dans ce cas, est réservé à la résiliation de zéro octet), mais s'arrête au premier caractère de saut de ligne si un produit avant de manquer d'espace.Illegal seek
erreur.read()
etwrite()
des fonctions plutôt que desfget*
etfput*
. Ils sont mieux adaptés à la mémoire sans tampon ruisseaux et je pense va vous permettre de répondre alors qu'il est à l'arrivée de données sur la pile réseau.Oui, vous pouvez utiliser un flux de fichier pour gérer votre prise, au moins sur Linux.
Mais vous devez être prudent avec elle: vous ne devez utiliser ferror() pour tester les erreurs. J'ai un code qui utilisent ce et exécuter parfaitement dans la production sur un grand site français.
Si vous utilisez errno ou perror (), vous aurez toute erreur interne que le flux de rencontrer, même si elle veut le cacher à vous. Et "Illégal seek" est l'un d'entre eux.
Aussi, à tester pour de vrai EOF conditions, vous devez utiliser la fonction feof(), car lors du retour de la vraie mutuellement exclusif avec ferror() pour retourner une valeur non nulle. C'est parce que, lors de l'utilisation de fgetc() vous n'avez pas de moyen de différencier l'erreur de réel EOF conditions. Donc, vous devriez probablement mieux d'utiliser fgets() comme un autre utilisateur l'a souligné.
Donc, votre test:
Devrait être écrit comme:
Essayez ceci :