Zum Hauptinhalt springen
Wenn Sie eine Netzwerkanwendung schreiben, können Sie den nicht blockierenden Modus oder den blockierenden Modus verwenden. Der nicht-blockierende Modus ist flexibler und wird benötigt, wenn die Anwendung mehrere Dinge tun muss, z. B. mehrere Sockets bedienen. Wenn die Anwendung jedoch nur eine Aufgabe erfüllt, z. B. das Lesen von einem Socket und das Schreiben der Daten in eine Datei oder eine Warteschlange, dann kann die Verwendung des blockierenden Modus die Komplexität Ihrer Anwendung erheblich reduzieren. Es gibt ein kleines Problem mit dem blockierenden Modus: Wenn etwas mit der Verbindung schief geht, wird die Anwendung es nie erfahren; sie wird ewig auf Daten warten, die nie ankommen werden.

 

Wie kann das passieren? Stellen Sie sich vor, dass Ihre Anwendung recv aufgerufen hat und auf Daten von einem Client wartet. Die Netzwerkverbindung des Clients ist unzuverlässig, und die Client-Anwendung muss mehrere Übertragungen wiederholen. Irgendwann entscheidet der TCP-Stack des Clients, dass die Verbindung beendet werden muss; er benachrichtigt die Client-Anwendung und räumt den Socket auf. Ihre Anwendung wartet nun auf Daten über eine Verbindung, die aus Sicht des Clients bereits geschlossen wurde. Die einzige Möglichkeit, das Problem zu beheben, besteht darin, Ihre Anwendung manuell zu beenden und neu zu starten. Das Beenden der Anwendung hinterlässt den verbundenen Socket in einem "TIME_WAIT"-Zustand, der, sofern Sie nicht die Socket-Option REUSEADDR gesetzt haben, verhindert, dass Sie die Anwendung sofort neu starten und sie an den hörenden Socket binden können.

 

Sie können OpenVOS jedoch anweisen, ein Zeitlimit zu setzen, wie lange auf Daten während des recv-Aufrufs gewartet werden soll. Das Ergebnis ist, dass nach Ablauf des Zeitlimits der Aufruf eine -1 zurückgibt und errno auf e$timeout (1081) gesetzt wird. Ihre Anwendung kann dann entscheiden, wie sie weiter vorgehen will. Sie kann die Verbindung beenden, dem Client eine weitere Chance geben, etwas an den Client senden, um die Verbindung zu testen, oder jede andere anwendungsgerechte Antwort geben. Der Punkt ist, dass die Diagnose der Situation und die Reaktion unter Ihrer Kontrolle liegen.
Das Programm timeout_recv.c in Abbildung 2 ist ein Beispiel dafür, wie das geht. Es benötigt 2 Argumente, eine Portnummer zum Abhören und eine Zeitüberschreitung in Einheiten von 1/1024 einer Sekunde. Abbildung 1 zeigt ein Ausführungsbeispiel. Die Befehlszeile(fett und unterstrichen) wird als Echo ausgegeben, da ich der festen Überzeugung bin, dass alle Argumente von interaktiven Programmen als Echo ausgegeben werden sollten. Beachten Sie, dass ich eine Zeitüberschreitung von 10 Sekunden (10240 / 1024) eingestellt habe. Das Programm meldet, wenn es accept aufruft und wenn accept eine Zeitüberschreitung zurückgibt. Obwohl ich es im vorigen Abschnitt nicht erwähnt habe, funktioniert die Zeitüberschreitung auch beim Aufruf von accept und das Programm demonstriert dies. Nachdem eine Verbindung hergestellt wurde, meldet es, wenn es recv aufruft und die Zeit, die recv entweder mit einer Zeitüberschreitung oder mit Zeichen zurückgegeben hat. Ich habe alle Timeout-Meldungen hervorgehoben.

 

Beachten Sie, dass die Aufrufe zum Setzen der Zeitlimits OpenVOS- (und VOS-) spezifisch sind; dieser Ansatz funktioniert nicht unter anderen Betriebssystemen.

 

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

 

Abbildung 1 - Beispiel für die Ausführung von 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);
}
}

 

Abbildung 2 - timeout_recv.c

© 2024 Stratus Technologies.