por enviar ejemplos archivos c linux sockets tcp glibc

enviar - Escribir en un socket TCP local cerrado que no falla



ejemplos de sockets en c (5)

Después de haber llamado a write() una (primera) vez (como se codificó en su ejemplo) después de que el cliente close() el socket, obtendrá el EPIPE y el SIGPIPE esperados en cualquier llamada sucesiva a write ().

Solo intente agregar otro write () para provocar el error:

... printf( "Errno before: %s/n", strerror( errno ) ); printf( "Write result: %d/n", write( client_fd, "123", 3 ) ); printf( "Errno after: %s/n", strerror( errno ) ); printf( "Errno before: %s/n", strerror( errno ) ); printf( "Write result: %d/n", write( client_fd, "A", 1 ) ); printf( "Errno after: %s/n", strerror( errno ) ); ...

La salida será:

Accepting Server sleeping Client closing its fd... Client exiting. Errno before: Success Write result: 3 Errno after: Success Errno before: Success Client status is 0, server status is 13

La salida de los dos últimos printf() s falta ya que el proceso finaliza debido a que SIGPIPE sido elevado por la segunda llamada a write() . Para evitar la finalización del proceso, puede hacer que el proceso ignore SIGPIPE .

Parece que estoy teniendo un problema con mis enchufes. A continuación, verá un código que divide un servidor y un cliente. El servidor abre un socket TCP, y el cliente se conecta a él y luego lo cierra. Los dormidos se utilizan para coordinar el tiempo. Después del cierre del lado del cliente (), el servidor intenta escribir () en su propio extremo de la conexión TCP. De acuerdo con la página de manual de write (2), esto debería darme un SIGPIPE y un EPIPE errno. Sin embargo, no veo esto. Desde el punto de vista del servidor, la escritura en un socket local, cerrado se realiza correctamente y, en ausencia del EPIPE, no puedo ver cómo el servidor debería detectar que el cliente ha cerrado el socket.

En la brecha entre el cliente que cierra su extremo y el servidor que intenta escribir, una llamada a netstat mostrará que la conexión está en un estado CLOSE_WAIT / FIN_WAIT2, por lo que el extremo del servidor debería poder rechazar la escritura.

Para referencia, estoy en Debian Squeeze, uname -r es 2.6.39-bpo.2-amd64.

¿Que está pasando aqui?

#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/socket.h> #include <sys/select.h> #include <netinet/tcp.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <netdb.h> #define SERVER_ADDRESS "127.0.0.7" #define SERVER_PORT 4777 #define myfail_if( test, msg ) do { if((test)){ fprintf(stderr, msg "/n"); exit(1); } } while (0) #define myfail_unless( test, msg ) myfail_if( !(test), msg ) int connect_client( char *addr, int actual_port ) { int client_fd; struct addrinfo hint; struct addrinfo *ailist, *aip; memset( &hint, ''/0'', sizeof( struct addrinfo ) ); hint.ai_socktype = SOCK_STREAM; myfail_if( getaddrinfo( addr, NULL, &hint, &ailist ) != 0, "getaddrinfo failed." ); int connected = 0; for( aip = ailist; aip; aip = aip->ai_next ) { ((struct sockaddr_in *)aip->ai_addr)->sin_port = htons( actual_port ); client_fd = socket( aip->ai_family, aip->ai_socktype, aip->ai_protocol ); if( client_fd == -1) { continue; } if( connect( client_fd, aip->ai_addr, aip->ai_addrlen) == 0 ) { connected = 1; break; } close( client_fd ); } freeaddrinfo( ailist ); myfail_unless( connected, "Didn''t connect." ); return client_fd; } void client(){ sleep(1); int client_fd = connect_client( SERVER_ADDRESS, SERVER_PORT ); printf("Client closing its fd... "); myfail_unless( 0 == close( client_fd ), "close failed" ); fprintf(stdout, "Client exiting./n"); exit(0); } int init_server( struct sockaddr * saddr, socklen_t saddr_len ) { int sock_fd; sock_fd = socket( saddr->sa_family, SOCK_STREAM, 0 ); if ( sock_fd < 0 ){ return sock_fd; } myfail_unless( bind( sock_fd, saddr, saddr_len ) == 0, "Failed to bind." ); return sock_fd; } int start_server( const char * addr, int port ) { struct addrinfo *ailist, *aip; struct addrinfo hint; int sock_fd; memset( &hint, ''/0'', sizeof( struct addrinfo ) ); hint.ai_socktype = SOCK_STREAM; myfail_if( getaddrinfo( addr, NULL, &hint, &ailist ) != 0, "getaddrinfo failed." ); for( aip = ailist; aip; aip = aip->ai_next ){ ((struct sockaddr_in *)aip->ai_addr)->sin_port = htons( port ); sock_fd = init_server( aip->ai_addr, aip->ai_addrlen ); if ( sock_fd > 0 ){ break; } } freeaddrinfo( aip ); myfail_unless( listen( sock_fd, 2 ) == 0, "Failed to listen" ); return sock_fd; } int server_accept( int server_fd ) { printf("Accepting/n"); int client_fd = accept( server_fd, NULL, NULL ); myfail_unless( client_fd > 0, "Failed to accept" ); return client_fd; } void server() { int server_fd = start_server(SERVER_ADDRESS, SERVER_PORT); int client_fd = server_accept( server_fd ); printf("Server sleeping/n"); sleep(60); printf( "Errno before: %s/n", strerror( errno ) ); printf( "Write result: %d/n", write( client_fd, "123", 3 ) ); printf( "Errno after: %s/n", strerror( errno ) ); close( client_fd ); } int main(void){ pid_t clientpid; pid_t serverpid; clientpid = fork(); if ( clientpid == 0 ) { client(); } else { serverpid = fork(); if ( serverpid == 0 ) { server(); } else { int clientstatus; int serverstatus; waitpid( clientpid, &clientstatus, 0 ); waitpid( serverpid, &serverstatus, 0 ); printf( "Client status is %d, server status is %d/n", clientstatus, serverstatus ); } } return 0; }


Esto es lo que dice la página del manual de Linux sobre write y EPIPE :

EPIPE fd is connected to a pipe or socket whose reading end is closed. When this happens the writing process will also receive a SIG- PIPE signal. (Thus, the write return value is seen only if the program catches, blocks or ignores this signal.)

Cuando Linux está utilizando una pipe o un socketpair , puede y verificará el final de lectura del par, ya que estos dos programas demostrarían:

void test_socketpair () { int pair[2]; socketpair(PF_LOCAL, SOCK_STREAM, 0, pair); close(pair[0]); if (send(pair[1], "a", 1, MSG_NOSIGNAL) < 0) perror("send"); } void test_pipe () { int pair[2]; pipe(pair); close(pair[0]); signal(SIGPIPE, SIG_IGN); if (write(pair[1], "a", 1) < 0) perror("send"); signal(SIGPIPE, SIG_DFL); }

Linux puede hacerlo, porque el kernel tiene un conocimiento innato sobre el otro extremo de la tubería o el par conectado. Sin embargo, cuando se usa connect , el estado del socket se mantiene en la pila de protocolos. Su prueba demuestra este comportamiento, pero a continuación hay un programa que lo hace todo en un solo hilo, similar a las dos pruebas anteriores:

int a_sock = socket(PF_INET, SOCK_STREAM, 0); const int one = 1; setsockopt(a_sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); struct sockaddr_in a_sin = {0}; a_sin.sin_port = htons(4321); a_sin.sin_family = AF_INET; a_sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); bind(a_sock, (struct sockaddr *)&a_sin, sizeof(a_sin)); listen(a_sock, 1); int c_sock = socket(PF_INET, SOCK_STREAM, 0); fcntl(c_sock, F_SETFL, fcntl(c_sock, F_GETFL, 0)|O_NONBLOCK); connect(c_sock, (struct sockaddr *)&a_sin, sizeof(a_sin)); fcntl(c_sock, F_SETFL, fcntl(c_sock, F_GETFL, 0)&~O_NONBLOCK); struct sockaddr_in s_sin = {0}; socklen_t s_sinlen = sizeof(s_sin); int s_sock = accept(a_sock, (struct sockaddr *)&s_sin, &s_sinlen); struct pollfd c_pfd = { c_sock, POLLOUT, 0 }; if (poll(&c_pfd, 1, -1) != 1) perror("poll"); int erropt = -1; socklen_t errlen = sizeof(erropt); getsockopt(c_sock, SOL_SOCKET, SO_ERROR, &erropt, &errlen); if (erropt != 0) { errno = erropt; perror("connect"); } puts("P|Recv-Q|Send-Q|Local Address|Foreign Address|State|"); char cmd[256]; snprintf(cmd, sizeof(cmd), "netstat -tn | grep '':%hu '' | sed ''s/ */|/g''", ntohs(s_sin.sin_port)); puts("before close on client"); system(cmd); close(c_sock); puts("after close on client"); system(cmd); if (send(s_sock, "a", 1, MSG_NOSIGNAL) < 0) perror("send"); puts("after send on server"); system(cmd); puts("end of test"); sleep(5);

Si ejecuta el programa anterior, obtendrá una salida similar a esta:

P|Recv-Q|Send-Q|Local Address|Foreign Address|State| before close on client tcp|0|0|127.0.0.1:35790|127.0.0.1:4321|ESTABLISHED| tcp|0|0|127.0.0.1:4321|127.0.0.1:35790|ESTABLISHED| after close on client tcp|0|0|127.0.0.1:35790|127.0.0.1:4321|FIN_WAIT2| tcp|1|0|127.0.0.1:4321|127.0.0.1:35790|CLOSE_WAIT| after send on server end of test

Esto muestra que se necesitó una write para que los sockets pasen a los estados CLOSED . Para averiguar por qué ocurrió esto, un volcado de TCP de la transacción puede ser útil:

16:45:28 127.0.0.1 > 127.0.0.1 .809578 IP .35790 > .4321: S 1062313174:1062313174(0) win 32792 <mss 16396,sackOK,timestamp 3915671437 0,nop,wscale 7> .809715 IP .4321 > .35790: S 1068622806:1068622806(0) ack 1062313175 win 32768 <mss 16396,sackOK,timestamp 3915671437 3915671437,nop,wscale 7> .809583 IP .35790 > .4321: . ack 1 win 257 <nop,nop,timestamp 3915671437 3915671437> .840364 IP .35790 > .4321: F 1:1(0) ack 1 win 257 <nop,nop,timestamp 3915671468 3915671437> .841170 IP .4321 > .35790: . ack 2 win 256 <nop,nop,timestamp 3915671469 3915671468> .865792 IP .4321 > .35790: P 1:2(1) ack 2 win 256 <nop,nop,timestamp 3915671493 3915671468> .865809 IP .35790 > .4321: R 1062313176:1062313176(0) win 0

Las primeras tres líneas representan el apretón de manos de 3 vías. La cuarta línea es el paquete FIN el cliente envía al servidor, y la quinta línea es el ACK del servidor, que acusa recibo. La sexta línea es el servidor que intenta enviar 1 byte de datos al cliente con el indicador PUSH establecido. La última línea es el paquete RESET del cliente, que hace que se libere el estado TCP para la conexión, y es por eso que el tercer comando netstat no produjo ningún resultado en la prueba anterior.

Por lo tanto, el servidor no sabe que el cliente restablecerá la conexión hasta después de que intente enviarle algunos datos. El motivo del restablecimiento es porque el cliente llamó a close , en lugar de otra cosa.

El servidor no puede saber con certeza qué llamada del sistema ha emitido realmente el cliente, solo puede seguir el estado TCP. Por ejemplo, podríamos reemplazar la llamada de close con una llamada a shutdown lugar.

//close(c_sock); shutdown(c_sock, SHUT_WR);

La diferencia entre el shutdown y el close es que el shutdown solo gobierna el estado de la conexión, mientras que el close también gobierna el estado del descriptor de archivo que representa el socket. Un shutdown no close un zócalo.

La salida será diferente con el cambio de shutdown :

P|Recv-Q|Send-Q|Local Address|Foreign Address|State| before close on client tcp|0|0|127.0.0.1:4321|127.0.0.1:56355|ESTABLISHED| tcp|0|0|127.0.0.1:56355|127.0.0.1:4321|ESTABLISHED| after close on client tcp|1|0|127.0.0.1:4321|127.0.0.1:56355|CLOSE_WAIT| tcp|0|0|127.0.0.1:56355|127.0.0.1:4321|FIN_WAIT2| after send on server tcp|1|0|127.0.0.1:4321|127.0.0.1:56355|CLOSE_WAIT| tcp|1|0|127.0.0.1:56355|127.0.0.1:4321|FIN_WAIT2| end of test

El volcado de TCP mostrará también algo diferente:

17:09:18 127.0.0.1 > 127.0.0.1 .722520 IP .56355 > .4321: S 2558095134:2558095134(0) win 32792 <mss 16396,sackOK,timestamp 3917101399 0,nop,wscale 7> .722594 IP .4321 > .56355: S 2563862019:2563862019(0) ack 2558095135 win 32768 <mss 16396,sackOK,timestamp 3917101399 3917101399,nop,wscale 7> .722615 IP .56355 > .4321: . ack 1 win 257 <nop,nop,timestamp 3917101399 3917101399> .748838 IP .56355 > .4321: F 1:1(0) ack 1 win 257 <nop,nop,timestamp 3917101425 3917101399> .748956 IP .4321 > .56355: . ack 2 win 256 <nop,nop,timestamp 3917101426 3917101425> .764894 IP .4321 > .56355: P 1:2(1) ack 2 win 256 <nop,nop,timestamp 3917101442 3917101425> .764903 IP .56355 > .4321: . ack 2 win 257 <nop,nop,timestamp 3917101442 3917101442> 17:09:23 .786921 IP .56355 > .4321: R 2:2(0) ack 2 win 257 <nop,nop,timestamp 3917106464 3917101442>

Observe que el restablecimiento al final se produce 5 segundos después del último paquete ACK . Este reinicio se debe a que el programa se apaga sin cerrar correctamente los sockets. Es el paquete ACK del cliente al servidor antes del reinicio que es diferente al anterior. Esta es la indicación de que el cliente no usó close . En TCP, la indicación FIN es realmente una indicación de que no hay más datos que enviar. Pero como una conexión TCP es bidireccional, el servidor que recibe el FIN asume que el cliente aún puede recibir datos. En el caso anterior, el cliente de hecho acepta los datos.

Ya sea que el cliente use close o SHUT_WR para emitir un FIN , en cualquier caso, puede detectar la llegada del FIN mediante un sondeo en el socket del servidor para un evento legible. Si después de llamar, el resultado es 0 , entonces sabrá que el FIN ha llegado y puede hacer lo que desee con esa información.

struct pollfd s_pfd = { s_sock, POLLIN|POLLOUT, 0 }; if (poll(&s_pfd, 1, -1) != 1) perror("poll"); if (s_pfd.revents|POLLIN) { char c; int r; while ((r = recv(s_sock, &c, 1, MSG_DONTWAIT)) == 1) {} if (r == 0) { /*...FIN received...*/ } else if (errno == EAGAIN) { /*...no more data to read for now...*/ } else { /*...some other error...*/ perror("recv"); } }

Ahora, es trivialmente cierto que si el servidor emite SHUT_WR con el shutdown antes de que intente hacer una escritura, de hecho obtendrá el error EPIPE .

shutdown(s_sock, SHUT_WR); if (send(s_sock, "a", 1, MSG_NOSIGNAL) < 0) perror("send");

Si, en cambio, desea que el cliente indique un reinicio inmediato en el servidor, puede forzar que eso suceda en la mayoría de las pilas TCP habilitando la opción de permanencia, con un tiempo de espera de espera de espera de 0 antes de close llamada.

struct linger lo = { 1, 0 }; setsockopt(c_sock, SOL_SOCKET, SO_LINGER, &lo, sizeof(lo)); close(c_sock);

Con el cambio anterior, la salida del programa se convierte en:

P|Recv-Q|Send-Q|Local Address|Foreign Address|State| before close on client tcp|0|0|127.0.0.1:35043|127.0.0.1:4321|ESTABLISHED| tcp|0|0|127.0.0.1:4321|127.0.0.1:35043|ESTABLISHED| after close on client send: Connection reset by peer after send on server end of test

El send recibe un error inmediato en este caso, pero no es EPIPE , es ECONNRESET . El volcado de TCP refleja esto también:

17:44:21 127.0.0.1 > 127.0.0.1 .662163 IP .35043 > .4321: S 498617888:498617888(0) win 32792 <mss 16396,sackOK,timestamp 3919204411 0,nop,wscale 7> .662176 IP .4321 > .35043: S 497680435:497680435(0) ack 498617889 win 32768 <mss 16396,sackOK,timestamp 3919204411 3919204411,nop,wscale 7> .662184 IP .35043 > .4321: . ack 1 win 257 <nop,nop,timestamp 3919204411 3919204411> .691207 IP .35043 > .4321: R 1:1(0) ack 1 win 257 <nop,nop,timestamp 3919204440 3919204411>

El paquete RESET llega justo después de que se complete el protocolo de enlace de 3 vías. Sin embargo, el uso de esta opción tiene sus peligros. Si el otro extremo tiene datos no leídos en el búfer de socket cuando llega el RESET , esos datos se eliminarán, lo que provocará la pérdida de los datos. Forzar el envío de un RESET se usa generalmente en los protocolos de estilo de solicitud / respuesta. El remitente de la solicitud puede saber que no puede haber datos perdidos cuando recibe la respuesta completa a su solicitud. Entonces, es seguro para el remitente de la solicitud forzar que se envíe un RESET en la conexión.


Sospecho que lo que está sucediendo es que el zócalo del lado del servidor todavía es válido, por lo que su llamada de escritura está haciendo un intento válido de escribir en su descriptor de archivos a pesar de que su sesión TCP está en un estado cerrado. Si estoy completamente equivocado házmelo saber.


Supongo que se está ejecutando en la pila TCP detectando un envío fallido e intentando retransmitir. ¿Las llamadas subsiguientes a write() fallan silenciosamente? En otras palabras, intente escribir cinco veces en el socket cerrado y vea si finalmente obtiene un SIGPIPE. Y cuando dice que la escritura "tiene éxito", ¿obtiene un resultado de retorno de 3?


Tiene dos sockets: uno para el cliente y otro para el servidor. Ahora su cliente está haciendo el cierre activo. Esto significa que la terminación de la conexión de TCP ha sido iniciada por el cliente (se ha enviado un segmento tcp FIN desde el envío del cliente).

En esta etapa, verá el socket del cliente en el estado FIN_WAIT1. Ahora, ¿cuál es el estado del socket del servidor ahora? Está en estado CLOSE_WAIT. Por lo tanto, el socket del servidor no está cerrado.

El FIN del servidor no ha sido enviado todavía. (Por qué - ya que la aplicación no ha cerrado el socket). En esta etapa, está escribiendo sobre el socket del servidor para que no esté recibiendo un error.

Ahora, si desea ver el error, simplemente escriba close (client_fd) antes de escribir sobre el socket.

close(client_fd); printf( "Write result: %d/n", write( client_fd, "123", 3 ) );

Aquí, el socket del servidor ya no está en el estado CLOSE_WAIT, por lo que puede ver que el valor de retorno de escritura es -ve para indicar el error. Espero que esto se aclare.