modal event closeonescape close c sockets tcp client-server

event - close() no cierra el socket correctamente



jquery dialog width (3)

Aquí hay un código que he usado en muchos sistemas tipo Unix (por ejemplo, SunOS 4, SGI IRIX, HPUX 10.20, CentOS 5, Cygwin) para cerrar un socket:

int getSO_ERROR(int fd) { int err = 1; socklen_t len = sizeof err; if (-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&err, &len)) FatalError("getSO_ERROR"); if (err) errno = err; // set errno to the socket SO_ERROR return err; } void closeSocket(int fd) { // *not* the Windows closesocket() if (fd >= 0) { getSO_ERROR(fd); // first clear any errors, which can cause close to fail if (shutdown(fd, SHUT_RDWR) < 0) // secondly, terminate the ''reliable'' delivery if (errno != ENOTCONN && errno != EINVAL) // SGI causes EINVAL Perror("shutdown"); if (close(fd) < 0) // finally call close() Perror("close"); } }

Pero lo anterior no garantiza que se envíen escrituras con búfer.

Agraciado cierre: me llevó unos 10 años descubrir cómo cerrar una toma de corriente. Pero durante otros 10 años simplemente llamé a usleep(20000) por un ligero retraso para ''garantizar'' que el buffer de escritura se limpiara antes del cierre. Obviamente, esto no es muy inteligente, porque:

  • La demora fue demasiado larga la mayor parte del tiempo.
  • La demora fue demasiado corta parte del tiempo, ¡tal vez!
  • Una señal como SIGCHLD podría ocurrir para terminar con el usleep() (pero usualmente llamé a usleep() dos veces para manejar este caso - un truco).
  • No hay indicación de si esto funciona. Pero esto quizás no sea importante si a) los reinicios duros están perfectamente bien, y / ob) usted tiene control sobre ambos lados del enlace.

Pero hacer un buen lavado es sorprendentemente difícil. Usar SO_LINGER aparentemente no es el camino a seguir; ver por ejemplo:

Y SIOCOUTQ parece ser específico de Linux.

El shutdown(fd, SHUT_WR) nota shutdown(fd, SHUT_WR) no deja de escribir, al contrario de su nombre, y tal vez sea contrario al man 2 shutdown .

Este código flushSocketBeforeClose() espera hasta una lectura de cero bytes, o hasta que el temporizador expire. La función haveInput() es una envoltura simple para select (2) y está configurada para bloquear hasta 1 / 100th de segundo.

bool haveInput(int fd, double timeout) { int status; fd_set fds; struct timeval tv; FD_ZERO(&fds); FD_SET(fd, &fds); tv.tv_sec = (long)timeout; // cast needed for C++ tv.tv_usec = (long)((timeout - tv.tv_sec) * 1000000); // ''suseconds_t'' while (1) { if (!(status = select(fd + 1, &fds, 0, 0, &tv))) return FALSE; else if (status > 0 && FD_ISSET(fd, &fds)) return TRUE; else if (status > 0) FatalError("I am confused"); else if (errno != EINTR) FatalError("select"); // tbd EBADF: man page "an error has occurred" } } bool flushSocketBeforeClose(int fd, double timeout) { const double start = getWallTimeEpoch(); char discard[99]; ASSERT(SHUT_WR == 1); if (shutdown(fd, 1) != -1) while (getWallTimeEpoch() < start + timeout) while (haveInput(fd, 0.01)) // can block for 0.01 secs if (!read(fd, discard, sizeof discard)) return TRUE; // success! return FALSE; }

Ejemplo de uso:

if (!flushSocketBeforeClose(fd, 2.0)) // can block for 2s printf("Warning: Cannot gracefully close socket/n"); closeSocket(fd);

En lo anterior, mi getWallTimeEpoch() es similar a time(), y Perror() es un contenedor para perror().

Editar: Algunos comentarios:

  • Mi primera admisión es un poco embarazosa. El OP y Nemo desafiaron la necesidad de borrar el so_error interno antes del cierre, pero ahora no puedo encontrar ninguna referencia para esto. El sistema en cuestión era HPUX 10.20. Después de una connect() fallida connect() , simplemente al llamar a close() no se lanzó el descriptor del archivo, porque el sistema deseaba entregarme un error pendiente. Pero, como la mayoría de las personas, nunca me molesté en verificar el valor de retorno de close. Así que finalmente me quedé sin descriptores de archivos (ulimit -n), que finalmente me llamó la atención.

  • (punto muy secundario) Un comentarista se opuso a los argumentos numéricos codificados para shutdown() , en lugar de, por ejemplo, SHUT_WR para 1. La respuesta más simple es que Windows usa diferentes # define / enums, por ejemplo, SD_SEND . Y muchos otros escritores (por ejemplo, Beej) usan constantes, al igual que muchos sistemas heredados.

  • Además, siempre, siempre, configuro FD_CLOEXEC en todos mis sockets, ya que en mis aplicaciones nunca quiero que se pasen a un niño y, más importante aún, no quiero que un niño colgado me impacte.

Código de muestra para configurar CLOEXEC:

static void setFD_CLOEXEC(int fd) { int status = fcntl(fd, F_GETFD, 0); if (status >= 0) status = fcntl(fd, F_SETFD, status | FD_CLOEXEC); if (status < 0) Perror("Error getting/setting socket FD_CLOEXEC flags"); }

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.

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 root 21u IPv4 32754237 TCP localhost: 9980-> localhost: 47530 (ESTABLECIDO)

CLIENTE 17747 root 12u IPv4 32754228 TCP localhost: 47530-> localhost: 9980 (ESTABLECIDO)

Es como si el servidor nunca enviara la secuencia de apagado al cliente, y este estado se cuelga hasta que el cliente muere, dejando al servidor en un estado de espera cerrada.

SERVER 8160 root 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 de Linux: 2.6.21.7-2.fc8xen Versión de Centos: 5.4 (final)

las acciones de socket son las siguientes

SERVIDOR:

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 y 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:

El 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, solo una conexión básica de 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 intentado hasta ahora:

Apagar antes de cerrar

shutdown(fd, SHUT_WR); char buf[64]; while(read(fd, buf, 64) > 0); /* then close */

Configuración de SO_LINGER para cerrar a 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 no 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 cola causando que el socket se maneje de manera inapropiada por varios subprocesos.


Esto me suena a un error en su distribución de Linux.

La documentación de la biblioteca GNU C dice:

Cuando haya terminado de usar un socket, simplemente cierre su descriptor de archivo con close

Nada sobre borrar los indicadores de error o esperar a que se vacíen los datos o algo por el estilo.

Tu código está bien; su O / S tiene un error.


Gran respuesta de Joseph Quinsey. Tengo comentarios sobre la función haveInput . Preguntándose qué tan probable es que select devuelva un fd que no incluyó en su conjunto. Esta sería una gran falla del sistema operativo en mi humilde opinión. Ese es el tipo de cosas que verificaría si escribiera pruebas unitarias para la función de select , no en una aplicación común.

if (!(status = select(fd + 1, &fds, 0, 0, &tv))) return FALSE; else if (status > 0 && FD_ISSET(fd, &fds)) return TRUE; else if (status > 0) FatalError("I am confused"); // <--- fd unknown to function

Mi otro comentario se refiere al manejo de EINTR. En teoría, podría quedarse atascado en un bucle infinito si la select sigue devolviendo EINTR, ya que este error permite que el bucle comience nuevamente. Dado el tiempo de espera muy corto (0.01), parece muy poco probable que suceda. Sin embargo, creo que la forma adecuada de tratar con esto sería devolver los errores a la persona que llama ( flushSocketBeforeClose ). La persona que llama puede seguir llamando a haveInput mucho tiempo ya que su tiempo de espera no ha expirado y declarar la falla de otros errores.

ADDICIÓN # 1

flushSocketBeforeClose no saldrá rápidamente en caso de que la read devuelva un error. Seguirá funcionando hasta que expire el tiempo de espera. No puede confiar en el select adentro haveInput para anticipar todos los errores. read tiene errores propios (por ejemplo, EIO ).

while (haveInput(fd, 0.01)) if (!read(fd, discard, sizeof discard)) <-- -1 does not end loop return TRUE;