2012-04-21 14 views
6

Actualmente estoy escribiendo una aplicación de servidor en Linux x86_64 usando <sys/socket.h>. Después de aceptar una conexión a través de accept(), uso fdopen() para envolver el socket recuperado en una secuencia FILE*.Error de "búsqueda ilegal" al trabajar con flujos de socket con búferes de lectura no vacíos

Escribir y leer de esa corriente FILE* suele funcionar bastante bien, pero el socket se vuelve inutilizable tan pronto como escribo mientras tiene un búfer de lectura no vacío.

Para fines de demostración, he escrito un código que escucha una conexión, luego lee la entrada, línea por línea, en un búfer de lectura usando fgetc(). Si la línea es demasiado larga para caber en el búfer, no se lee por completo, sino que se lee durante la siguiente iteración.

#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"); 
} 

El código debe compilarse en gcc sin ningún parámetro especial. Ejecútelo con el número de puerto como argumento y use netcat para conectarse a él localmente.

Ahora, si intenta enviar cadenas de menos de 8 caracteres, esto funcionará sin problemas. Pero si envía una cadena que contiene más de 10 caracteres, el programa fallará. Esta entrada de la muestra:

ab 
cd 
abcdefghij 

creará esta salida:

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 

Como se ve, (con razón) sólo los primeros 8 caracteres de abcdefgh se leen, pero cuando el programa intenta enviar el ' ack 'string (que el cliente nunca recibe), y luego vacía el buffer de salida, recibimos un error Illegal seek, y la siguiente llamada a fgetc() devuelve EOF.

Si la parte fflush() está comentado, el mismo error se sigue produciendo, pero la línea

fflush failed: Illegal seek 

no se encuentra en la salida del servidor.

Si la parte fputs(ack) está comentada, todo parece funcionar según lo previsto, pero un perror() llamado manualmente desde gdb aún informa un error de "búsqueda ilegal".

Si tanto fputs(ack) y fflush() están comentadas, todo hace funcionar según lo previsto.

Desafortunadamente, no he podido encontrar ninguna buena documentación, ni discusiones de Internet sobre este problema, por lo que su ayuda sería muy apreciada.

edición

La solución que finalmente conformé es no uso fdopen() y FILE*, ya que parece que no hay manera limpia de convertir una toma de fd en un FILE* que pueden utilizarse con seguridad de r+ modo de . En su lugar, trabajé directamente en el socket fd, escribiendo mi propio código de reemplazo para fputs y fprintf.

Si alguien lo necesita, here is the code.

Respuesta

4

Claramente "r +" (lectura/escritura) modo no funciona en sockets en esta implementación, sin duda porque el código subyacente se supone que debe hacer un tratan de cambiar entre leyendo y escribiendo. Este es el caso general con stdio streams (que debe hacer algún tipo de operación de sincronización), porque en el tiempo oscuro, las implementaciones stdio reales tenían solo un contador por transmisión, y era un contador de "cantidad de caracteres restantes" para leer desde el búfer de secuencia a través de getc macro "(en modo de lectura) o" número de caracteres que se pueden escribir de forma segura en el búfer de secuencia a través de putc macro (en modo de escritura). Para ese solo contador volver a establecer, tenía que hacer una buscar el tipo de operación.

busca no se permiten en las tuberías y tomas de corriente (desde "el desplazamiento de archivos" no es significativo allí).

una solución no es para envolver un zócalo con stdio en absoluto. Otra, probablemente más fácil/mejor para sus propósitos, es envolverlo con, no o ne, pero dos corrientes stdio:

FILE *in = fdopen(newsock, "r"); 
FILE *out = fdopen(newsock, "w"); 

Hay otra falla aquí, sin embargo, porque cuando uno va a fclose una corriente, que cierra el descriptor de archivo del otro. Para solucionar esto, necesita dup el descriptor de socket una vez (en cualquiera de las dos llamadas anteriores, no importa cuál).

Si se va a utilizar select o poll o similar sobre la toma en algún momento, usted debe ir generalmente para la solución "no envuelva con stdio", ya que no hay manera portátil limpio para realizar un seguimiento de amortiguación stdio. (Hay formas específicas de implementación).

+0

Entonces, ¿hay _no_ forma limpia de usar 'FILE * streams', especialmente ** ** **' FILE * stream'? Antes de envolver el descriptor de socket en un 'FILE * stream', tenía [my own] (http://pastebin.com/LYZGn9c2) wrappers para' printf() 'y' puts() ', pero lo encontré ser feo Supongamos que es la solución más 'limpia' después de todo. –

+0

Sí. Posiblemente irónicamente, tu código "básicamente' asprintf' "es exactamente lo que tenía en mente cuando definí el valor de retorno de' snprintf' de la manera en que lo hice. :-) – torek

1

No utilice fflush() en sockets de red. Son flujos sin búfer.

Además, este código:

//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; 
} 

no ha leído una sola línea. Solo lee hasta el tamaño del búfer, que definió como 8. sock aún tendrá datos para que usted reciba, que debe recibir antes de escribir en la corriente con fputs. Por cierto, se puede sustituir todo ese bloque con

fgets(buf, bufsize, sock); 
+0

Will 'fgets()' dejar de leer en la nueva línea? – gcbenison

+0

@gcbenison: Sí, 'fgets()' no lee más que 'n-1' caracteres (porque' n'th, o 'bufsize'th en este caso, está reservado para el byte cero de terminación) pero se detiene en el primera línea nueva si se produce antes de quedarse sin espacio. – torek

+0

Simplemente vaciar el búfer de lectura ** antes ** de la escritura, por supuesto, resolvería este problema. Pero, ¿qué pasa si el procesamiento de la entrada del usuario lleva mucho tiempo, durante el cual llegan nuevos mensajes y el búfer de lectura se llena nuevamente, y una vez terminado el procesamiento estoy enviando una respuesta, sin saber que mientras tanto, los nuevos datos ¿llegado? Me gustaría obtener otro error de "búsqueda ilegal". –

0

Prueba esto:

#define BUFSIZE 88 

FILE* listen_on_port(unsigned short port) { 
... 
} 

int main(int argc, char** argv) { 
    int bufsize = BUFSIZE; 
    char buf[ BUFSIZE ]; 
0

Sí, se puede utilizar una secuencia de archivo para manejar su zócalo, al menos en Linux. Pero debe tener cuidado con esto: solo debe usar ferror() para detectar errores. Tengo un código que usa esto y funciona perfectamente en producción en un importante sitio francés.

Si usa errno o perror(), detectará cualquier error interno que la transmisión encuentre, incluso si quiere ocultárselo. Y "búsqueda ilegal" es uno de ellos.

Además, para probar las condiciones reales de EOF, debe usar feof(), ya que al devolver verdadero es mutuamente exclusivo con ferror() que devuelve un valor distinto de cero. Es porque, al usar fgetc() no tiene ningún significado para diferenciar el error de las condiciones EOF reales. Por lo tanto, probablemente debería usar mejor los fgets() como señaló otro usuario.

Por lo tanto, su prueba:

if(data == EOF) { 
    perror("EOF: Connection reset by peer"); 
    break; 
} else { 
    printf("Input line: '%s'\n", buf); 
} 

debe ser escrita como:

int sock_error = ferror(sock); 
if (sock_error) { 
    fprintf(stderr, "Error while reading: %s", strerror(sock_error)); 
} else { 
    printf("Input line: '%s'\n", buf); 
} 
Cuestiones relacionadas