Ir al contenido principal
Cuando se escribe una aplicación de red se puede utilizar el modo no bloqueante o el modo bloqueante. El modo no bloqueante es más flexible y necesario cuando la aplicación tiene que hacer varias cosas, como el mantenimiento de varios enchufes. Sin embargo, si la aplicación sólo hace una cosa, por ejemplo, leer de un socket y escribir los datos en un archivo o cola, entonces el uso del modo de bloqueo puede reducir la complejidad de su aplicación de manera significativa. Hay un pequeño problema con el modo de bloqueo, si algo va mal con la conexión la aplicación puede que nunca lo sepa; esperará para siempre por datos que nunca llegarán.

 

¿Cómo puede suceder eso? Imagina que tu aplicación ha llamado a recv y está esperando los datos de un cliente. La conexión de red del cliente no es fiable y la aplicación cliente está experimentando múltiples retransmisiones. En algún momento, la pila TCP del cliente decide que la conexión debe ser terminada; notifica a la aplicación cliente y limpia el socket. La aplicación se queda a la espera de datos sobre una conexión que, en lo que respecta al cliente, se ha cerrado. La única forma de solucionar el problema es terminar manualmente la aplicación y reiniciarla. Terminar la aplicación deja el socket conectado en un estado "TIME_WAIT" que, a menos que haya configurado la opción REUSEADDR socket, le impedirá reiniciar inmediatamente la aplicación y hacer que se vincule al socket de escucha.

 

Sin embargo, puede decirle a OpenVOS que establezca un límite de tiempo para esperar los datos durante la llamada de recuperación. El resultado es que después de que el límite de tiempo expire, la llamada devolverá un -1 y errno se fijará en e$timeout (1081). Su aplicación puede entonces tomar una decisión sobre cómo proceder. Puede terminar la conexión, dar al cliente otra oportunidad, enviar algo al cliente para probar la conexión, o cualquier otra respuesta apropiada de la aplicación. El punto es que los diagnósticos de la situación y la respuesta están bajo su control.
El programa timeout_recv.c de la figura 2 es un ejemplo de cómo hacerlo. Se necesitan 2 argumentos, un número de puerto para escuchar y un tiempo de espera en unidades de 1/1024 de segundo. La figura 1 muestra un ejemplo de ejecución, la línea de comandos (en negrita y subrayada) se hace eco porque creo firmemente que todos los argumentos deben hacerse eco de los programas interactivos. Nota: He establecido un tiempo de espera de 10 segundos (10240 / 1024). El programa informa cuando llama a aceptar y si acepta devuelve un tiempo de espera. Aunque no lo mencioné en el párrafo anterior, el tiempo de espera funcionará también en la llamada de aceptación y el programa lo demuestra. Después de que se hace una conexión, el programa reporta cuando llama y el tiempo de llamada regresa con un timeout o caracteres. He resaltado todos los mensajes de timeout.

 

Tenga en cuenta que las llamadas para establecer los límites de tiempo son específicas de OpenVOS (y VOS); este enfoque no funcionará con otros sistemas operativos.

 

timeout_recv 5647 10240
command line was: timeout_recv 5647 10240
timeout_recv: 12:14:27 calling accept
timeout_recv: 12:14:37 accept returned a timeout
timeout_recv: 12:14:37 calling accept
timeout_recv: 12:14:47 accept returned a timeout
timeout_recv: 12:14:47 calling accept
timeout_recv: 12:14:48 calling recv
timeout_recv: 12:14:58 recv returned a timeout
timeout_recv: 12:14:58 calling recv
timeout_recv: 12:15:00 recv return 1 characters
timeout_recv: 12:15:00 calling recv
timeout_recv: 12:15:10 recv returned a timeout
timeout_recv: 12:15:10 calling recv
timeout_recv: 12:15:16 recv return 1 characters
timeout_recv: 12:15:16 calling recv
timeout_recv: 12:15:23 recv return 1 characters
timeout_recv: 12:15:23 calling recv
timeout_recv: 12:15:30 recv return 1 characters
timeout_recv: 12:15:30 calling recv
timeout_recv: 12:15:38 recv return 1 characters
timeout_recv: 12:15:38 calling recv
timeout_recv: 12:15:46 recv return 1 characters
timeout_recv: 12:15:46 calling recv
timeout_recv: 12:15:56 recv returned a timeout
timeout_recv: 12:15:56 calling recv
timeout_recv: client terminated connection at 12:16:00
ready 12:16:00

 

Figura 1 - Ejemplo de ejecución de timeout_recv
/* *****************************************************************
timeout_recv written by NSDavids Stratus CAC
10-04-23 version 1.0 initial release
This is a demonstration of timing out blocking accept and recv calls
and returning control to the program.
***************************************************************** */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define bzero(s, len)             memset((char *)(s), 0, len)
int errno;
void s$set_io_time_limit(short int *, long int *, short int *);
int s$c_get_portid_from_fildes(int file_des);
/* this routine returns the current time as a string */
char * GetCurrentTime (char * szT)
{
time_t tTime;
struct tm *tmLT;
tTime = time ((time_t *) 0);
tmLT = localtime (&tTime);
sprintf (szT, "%02ld:%02ld:%02ld",
tmLT -> tm_hour,
tmLT -> tm_min,
tmLT -> tm_sec);
return (szT);
}
/* this routine sets the time out value on the port associated
with the socket. These calls are VOS specific, this program
is not portable to other environments. i.e. Windows or Linux */
int SetTimeOut (int iSocket, int iTimeLimit, char * szType)
{
long lPortID;
short sPortID;
long lTimeLimit;
short sError;
if ((lPortID = s$c_get_portid_from_fildes(iSocket)) == -1)
{
printf ("timeout_recv: Error getting port ID of %s socketn", szType);
exit (-1);
}
sPortID = (short)lPortID;
if (iTimeLimit > 0)
{
lTimeLimit = iTimeLimit;
s$set_io_time_limit (&sPortID, &lTimeLimit, &sError);
if (sError != 0)
{
printf ("timeout_recv: Error %d setting time out of %s socketn",
szType, sError);
exit (sError);
}
}
return (0);
}
main (argc, argv)
int    argc;
char   *argv [];
{
int    iCliLen;
struct sockaddr_in sockCliAddr, sockServAddr;
short sPortNo;
int    iBytes;
int    iTimeLimit;
char   szT [32];
int    iListenSock, iAcceptedSock;
#define BUFFERLEN 100
char   szBuffer [BUFFERLEN];
/* process the arguments */
if (argc == 3)
{
sPortNo = atoi (argv [1]);
iTimeLimit = atoi (argv [2]);
printf ("command line was: timeout_recv %d %dnn", sPortNo, iTimeLimit);
}
else
{
printf
("nUsage: timeout_recv <port_number> <time_limit in 1/1024s of a sec)n");
exit (-1);
}
/* set up a listening socket */
if ((iListenSock = socket (AF_INET, SOCK_STREAM, 0)) < 0)
printf ("timeout_recv: Error %d can't open stream socket", errno);
bzero ( (char *) &sockServAddr, sizeof (sockServAddr));
sockServAddr.sin_family        = AF_INET;
sockServAddr.sin_addr.s_addr   = htonl (INADDR_ANY);
sockServAddr.sin_port          = htons (sPortNo);
if (bind (iListenSock,
(struct sockaddr *) &sockServAddr, sizeof (sockServAddr)) < 0)
{
printf ("timeout_recv: Error %d can't bind local addressn", errno);
exit (errno);
}
listen (iListenSock, 5);
SetTimeOut (iListenSock, iTimeLimit, "Listening");
/* In most cases I expect that an application will just block waiting for
a connection. However, I wanted to show that you can also timeout the
call to accept */
iAcceptedSock = 0;
while (iAcceptedSock < 1)
{
printf ("timeout_recv: %s calling acceptn", GetCurrentTime (szT));
iAcceptedSock = accept (iListenSock,
(struct sockaddr *) &sockCliAddr, &iCliLen);
if (iAcceptedSock < 0)
{
if (errno == 1081)
printf ("timeout_recv: %s accept returned a timeoutn",
GetCurrentTime (szT));
else
{
printf ("timeout_recv: %s accept returned the unexpected error %dn",
GetCurrentTime (szT), errno);
exit (errno);
}
iAcceptedSock = 0;
}
}
/* set the timeout on the newly accepted socket */
SetTimeOut (iAcceptedSock, iTimeLimit, "Accepted");
/* main loop, call recv and process the return value. If the return value is
-1 then it is an error. An errno of 1081 (e$timeout) is expected, report
it and continue. Any other error is unexpected and fatal. If the return
value is 0 it means the client terminated the connection, report it
and exit. If the return value is > 0 it indicates the number of
characters received, report it and continue the loop. */
while (1)
{
printf ("timeout_recv: %s calling recvn", GetCurrentTime (szT));
iBytes = recv (iAcceptedSock, szBuffer, BUFFERLEN, 0);
if (iBytes == -1)
{
if (errno == 1081)
printf ("timeout_recv: %s recv returned a timeoutn",
GetCurrentTime (szT));
else
{
printf ("timeout_recv: %s recv returned the unexpected error %dn",
GetCurrentTime (szT), errno);
exit (errno);
}
}
else
if (iBytes == 0)
{
printf ("timeout_recv: client terminated connection at %sn",
GetCurrentTime (szT));
exit (0);
}
else
printf ("timeout_recv: %s recv return %d charactersn",
GetCurrentTime (szT), iBytes);
}
}

 

Figura 2 - timeout_recv.c