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