Ao escrever uma aplicação de rede você pode usar o modo sem bloqueio ou o modo de bloqueio. O modo sem bloqueio é mais flexível e necessário quando a aplicação tem que fazer várias coisas, como a manutenção de vários soquetes. Entretanto, se a aplicação estiver fazendo apenas uma coisa, por exemplo, ler de um soquete e escrever os dados em um arquivo ou fila, então o uso do modo de bloqueio pode reduzir significativamente a complexidade de sua aplicação. Há um pequeno problema com o modo de bloqueio, se algo der errado com a conexão que a aplicação pode nunca saber; ela esperará para sempre por dados que nunca chegarão.
Como isso pode acontecer? Imagine que sua aplicação tenha chamado o Recv e esteja aguardando os dados de um cliente. A conexão de rede do cliente não é confiável e a aplicação do cliente está passando por múltiplas retransmissões. Em algum momento a pilha TCP do cliente decide que a conexão deve ser terminada; ela notifica a aplicação cliente e limpa o soquete. Sua aplicação fica aguardando os dados de uma conexão que, no que diz respeito ao cliente, foi fechada. A única maneira de resolver o problema é terminar manualmente sua aplicação e reiniciá-la. Terminar a aplicação deixa o soquete conectado em um estado "TIME_WAIT" que, a menos que você tenha definido a opção de soquete REUSEADDR, impedirá que você reinicie imediatamente a aplicação e a tenha ligada ao soquete de escuta.
Entretanto, você pode dizer à OpenVOS para definir um limite de tempo para esperar pelos dados durante a chamada de retorno. O resultado é que após o término do tempo limite, a chamada retornará um -1 e o erro será definido como e$timeout (1081). Sua solicitação pode então tomar uma decisão sobre como proceder. Pode encerrar a conexão, dar ao cliente outra oportunidade, enviar algo ao cliente para testar a conexão, ou qualquer outra resposta apropriada da aplicação. A questão é que o diagnóstico da situação e a resposta estão sob seu controle.
O programa timeout_recv.c na figura 2 é um exemplo de como fazer isso. São necessários 2 argumentos, um número de porta para ouvir e um timeout em unidades de 1/1024 de um segundo. A figura 1 mostra um exemplo de execução, a linha de comando(em negrito e sublinhado) é ecoada porque acredito firmemente que todos os argumentos devem ser ecoados por programas interativos. Nota: estabeleci um timeout de 10 segundos (10240 / 1024). O programa informa quando chama aceita e se aceita retorna um timeout. Embora eu não tenha mencionado isso no parágrafo anterior, o tempo limite também funcionará na chamada de aceitação e o programa demonstra isso. Depois que uma conexão é feita, ele reporta quando chama o recv e o tempo que o recv devolve com um timeout ou caracteres. Destaquei todas as mensagens de timeout.
Observe que as chamadas para definir os limites de tempo são específicas para OpenVOS (e VOS); esta abordagem não funcionará sob outros sistemas operacionais.
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 - Exemplo de execução 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