Passa al contenuto principale
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 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 – 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 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