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.
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. –
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