Passa al contenuto principale
Quando si scrive un'applicazione di rete è possibile utilizzare la modalità non bloccante o la modalità di blocco. La modalità non bloccante è più flessibile e necessaria quando l'applicazione deve fare più cose, come la manutenzione di più prese. Se però l'applicazione sta facendo solo 1 cosa, per esempio leggere da un socket e scrivere i dati su un file o una coda, allora l'uso della modalità di blocco può ridurre significativamente la complessità dell'applicazione. C'è un piccolo problema con la modalità di blocco, se qualcosa va storto con la connessione l'applicazione potrebbe non saperlo mai; aspetterà per sempre i dati che non arriveranno mai.

 

Come può succedere? Immaginate che la vostra applicazione abbia chiamato recv e sia in attesa dei dati di un cliente. La connessione di rete del cliente è inaffidabile e l'applicazione client sta subendo ritrasmissioni multiple. Ad un certo punto lo stack TCP del client decide che la connessione deve essere terminata; notifica l'applicazione client e pulisce il socket. L'applicazione viene lasciata in attesa dei dati di una connessione che, per quanto riguarda il client, è stata chiusa. L'unico modo per risolvere il problema è terminare manualmente l'applicazione e riavviarla. La terminazione dell'applicazione lascia il socket collegato in uno stato "TIME_WAIT" che, a meno che non si sia impostata l'opzione REUSEADDR socket, impedirà di riavviare immediatamente l'applicazione e di farla legare al socket in ascolto.

 

Si può comunque dire ad OpenVOS di impostare un limite di tempo per l'attesa dei dati durante la chiamata recv. Il risultato è che dopo la scadenza del limite di tempo la chiamata restituirà un -1 ed errno sarà impostato su e$timeout (1081). La vostra richiesta può quindi decidere come procedere. Può terminare la connessione, dare al cliente un'altra possibilità, inviare qualcosa al cliente per testare la connessione, o qualsiasi altra risposta appropriata dell'applicazione. Il punto è che la diagnosi della situazione e la risposta sono sotto il vostro controllo.
Il programma timeout_recv.c in figura 2 è un esempio di come fare questo. Sono necessari 2 argomenti, un numero di porta da 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 echeggiata perché sono fermamente convinto che tutti gli argomenti debbano essere echeggiati da programmi interattivi. Nota 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 di accettazione e il programma lo dimostra. Dopo che la connessione è stata effettuata, il programma riporta quando chiama recv e il tempo che recv ha restituito con un timeout o con 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

© 2024 Stratus Technologies.