Tengo un servidor multiproceso (grupo de subprocesos) que maneja una gran cantidad de solicitudes (hasta 500/seg para un nodo), usando 20 subprocesos. Hay un hilo de escucha que acepta conexiones entrantes y las pone en cola para que procesen los hilos del manejador. Una vez que la respuesta está lista, los hilos escriben al cliente y cierran el socket. Todo parecía estar bien hasta hace poco, un programa cliente de prueba comenzó a colgar al azar después de leer la respuesta. Después de una gran cantidad de excavaciones, parece que el cerrar() del servidor no desconecta realmente el socket. He agregado algunas impresiones de depuración al código con el número de descriptor de archivo y obtengo este tipo de salida.close() no está cerrando el socket correctamente
Processing request for 21
Writing to 21
Closing 21
El valor de retorno de close() es 0 o se imprimirá otra instrucción de depuración. Después de esta salida con un cliente que se cuelga, lsof muestra una conexión establecida.
SERVER 8160 21u raíz IPv4 TCP 32754237 localhost: 9980-> localhost: 47530 (establecido)
CLIENTE 17747 12u raíz IPv4 TCP localhost 32754228: 47530-> localhost: 9980 (establecido)
Es tan si el servidor nunca envía la secuencia de apagado al cliente, y este estado se cuelga hasta que se mata al cliente, dejando el servidor en un estado de espera cerrada
SERVIDOR 8160 raíz 21u IPv4 32754237 TCP localhost: 9980-> localhost: 47530 (CLOSE_WAIT)
Además, si el cliente tiene un tiempo de espera especificado, se agotará el tiempo de espera en lugar de colgar. También puedo ejecutar manualmente
call close(21)
en el servidor de gdb, y el cliente se desconectará. Esto sucede quizás una vez cada 50,000 solicitudes, pero puede que no suceda por períodos prolongados. versión
Linux: 2.6.21.7-2.fc8xen Centos versión: 5.4
acciones de socket (final) son los siguientes
SERVER:
int client_socket; struct sockaddr_in client_addr; socklen_t client_len = sizeof (client_addr);
while(true) {
client_socket = accept(incoming_socket, (struct sockaddr *)&client_addr, &client_len);
if (client_socket == -1)
continue;
/* insert into queue here for threads to process */
}
Luego, el hilo toma el socket y genera la respuesta.
/* get client_socket from queue */
/* processing request here */
/* now set to blocking for write; was previously set to non-blocking for reading */
int flags = fcntl(client_socket, F_GETFL);
if (flags < 0)
abort();
if (fcntl(client_socket, F_SETFL, flags|O_NONBLOCK) < 0)
abort();
server_write(client_socket, response_buf, response_length);
server_close(client_socket);
server_write and server_close.
void server_write(int fd, char const *buf, ssize_t len) {
printf("Writing to %d\n", fd);
while(len > 0) {
ssize_t n = write(fd, buf, len);
if(n <= 0)
return;// I don't really care what error happened, we'll just drop the connection
len -= n;
buf += n;
}
}
void server_close(int fd) {
for(uint32_t i=0; i<10; i++) {
int n = close(fd);
if(!n) {//closed successfully
return;
}
usleep(100);
}
printf("Close failed for %d\n", fd);
}
CLIENTE:
lado del cliente está utilizando libcurl v 7.27.0
CURL *curl = curl_easy_init();
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, write_tag);
res = curl_easy_perform(curl);
Nada especial, simplemente una conexión básica rizo. El cliente se bloquea en tranfer.c (en libcurl) porque el socket no se percibe como cerrado. Está esperando más datos del servidor.
Cosas que he probado hasta ahora:
apagado antes del cierre
shutdown(fd, SHUT_WR);
char buf[64];
while(read(fd, buf, 64) > 0);
/* then close */
Configuración SO_LINGER a cerrar por la fuerza en 1 segundo
struct linger l;
l.l_onoff = 1;
l.l_linger = 1;
if (setsockopt(client_socket, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1)
abort();
Estos han hecho ninguna diferencia. Cualquier idea sería muy apreciada.
EDITAR - Esto terminó siendo un problema de seguridad de subprocesos dentro de una biblioteca de colas causando que el socket se maneje de manera inapropiada por varios subprocesos.
¿Es 100% positivo que no haya ningún otro subproceso que pueda estar utilizando el socket cuando llama 'close'? ¿Cómo haces tus lecturas sin bloqueo? –
Me temo que acabo de iniciar sesión aquí y recordé este problema. Más tarde descubrí que había un problema de seguridad de subprocesos en una cola utilizada para pasar las conexiones. No hubo error aquí. Perdón por la desinformación. – DavidMFrey