Quando si scrive un'applicazione di rete è possibile utilizzare la modalità non bloccante o la modalità bloccante. La modalità non bloccante è più flessibile e necessaria quando l'applicazione deve eseguire più operazioni, come gestire più socket. Se invece l'applicazione esegue una sola operazione, ad esempio leggere da un socket e scrivere i dati in un file o in una coda, l'utilizzo della modalità bloccante può ridurre significativamente la complessità dell'applicazione. La modalità bloccante presenta un piccolo problema: se si verifica un errore nella connessione, l'applicazione potrebbe non accorgersene e attendere all'infinito dati che non arriveranno mai.
Come può succedere? Immagina che la tua app abbia chiamato recv e stia aspettando dati da un client. La connessione di rete del client non è affidabile e l'app client sta subendo più ritrasmissioni. A un certo punto, lo stack TCP del client decide che la connessione deve essere interrotta; lo comunica all'app client e pulisce il socket. La vostra applicazione rimane in attesa di dati su una connessione che, per quanto riguarda il client, è stata chiusa. L'unico modo per risolvere il problema è terminare manualmente l'applicazione e riavviarla. La chiusura dell'applicazione lascia il socket connesso in uno stato "TIME_WAIT" che, a meno che non abbiate impostato l'opzione socket REUSEADDR, vi impedirà di riavviare immediatamente l'applicazione e di collegarla al socket in ascolto.
È tuttavia possibile indicare a OpenVOS di impostare un limite di tempo per l'attesa dei dati durante la chiamata recv. Il risultato è che, allo scadere del limite di tempo, la chiamata restituirà un -1 e errno sarà impostato su e$timeout (1081). L'applicazione potrà quindi decidere come procedere. Potrà terminare la connessione, dare al client un'altra possibilità, inviare qualcosa al client per testare la connessione o qualsiasi altra risposta appropriata all'applicazione. Il punto è che la diagnosi della situazione e la risposta sono sotto il controllo dell'utente.
Il programma timeout_recv.c nella figura 2 è un esempio di come farlo. Accetta 2 argomenti, un numero di porta su cui ascoltare e un timeout in unità di 1/1024 di secondo. La figura 1 mostra un esempio di esecuzione, la riga di comando (in grassetto e sottolineata) viene ripetuta perché sono fermamente convinto che tutti gli argomenti debbano essere ripetuti dai programmi interattivi. Si noti che ho impostato un timeout di 10 secondi (10240 / 1024). Il programma segnala quando chiama accept e se accept restituisce un timeout. Anche se non l'ho menzionato nel paragrafo precedente, il timeout funzionerà anche sulla chiamata accept e il programma lo dimostra. Dopo aver stabilito una connessione, segnala quando chiama recv e il tempo che recv ha restituito con un timeout o dei caratteri. Ho evidenziato tutti i messaggi di timeout.
Si noti che le chiamate per impostare i limiti di tempo sono specifiche di OpenVOS (e VOS); questo approccio non funzionerà con altri sistemi operativi.
timeout_recv 5647 10240command line was: timeout_recv 5647 10240 timeout_recv: 12:14:27 calling accepttimeout_recv: 12:14:37 accept returned a timeouttimeout_recv: 12:14:37 calling accepttimeout_recv: 12:14:47 accept returned a timeouttimeout_recv: 12:14:47 calling accepttimeout_recv: 12:14:48 calling recvtimeout_recv: 12:14:58 recv returned a timeouttimeout_recv: 12:14:58 calling recvtimeout_recv: 12:15:00 recv return 1 characterstimeout_recv: 12:15:00 calling recvtimeout_recv: 12:15:10 recv returned a timeouttimeout_recv: 12:15:10 calling recvtimeout_recv: 12:15:16 recv return 1 characterstimeout_recv: 12:15:16 calling recvtimeout_recv: 12:15:23 recv return 1 characterstimeout_recv: 12:15:23 calling recvtimeout_recv: 12:15:30 recv return 1 characterstimeout_recv: 12:15:30 calling recvtimeout_recv: 12:15:38 recv return 1 characterstimeout_recv: 12:15:38 calling recvtimeout_recv: 12:15:46 recv return 1 characterstimeout_recv: 12:15:46 calling recvtimeout_recv: 12:15:56 recv returned a timeouttimeout_recv: 12:15:56 calling recvtimeout_recv: client terminated connection at 12:16:00ready 12:16:00 |
Figura 1 – Esempio di esecuzione di 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 callsand 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
