2010-11-15 7 views
56

Ich schreibe ein einfaches Programm, das mehrere Verbindungen zu verschiedenen Servern für die Statusüberprüfung herstellt. Alle diese Verbindungen sind auf Abruf aufgebaut; Bis zu 10 Verbindungen können gleichzeitig erstellt werden. Ich mag die Idee von Ein-Thread-pro-Socket nicht, also habe ich all diese Client-Sockets Non-Blocking gemacht und sie in einen select() -Pool geworfen.Wie wird das Socket-Timeout in C festgelegt, wenn mehrere Verbindungen hergestellt werden?

Es funktionierte großartig, bis mein Client beschwerte, dass die Wartezeit zu lang ist, bevor sie den Fehlerbericht erhalten können, wenn die Zielserver nicht mehr reagierten.

Ich habe mehrere Themen im Forum geprüft. Einige hatten vorgeschlagen, dass man das alarm() Signal verwenden oder ein Timeout im Funktionsaufruf select() einstellen kann. Aber ich habe es mit mehreren Verbindungen zu tun, anstatt mit einer. Wenn ein prozessweites Timeout-Signal auftritt, kann ich die Timeout-Verbindung zwischen allen anderen Verbindungen nicht unterscheiden.

Gibt es trotzdem eine Änderung der System-Standard Timeout-Dauer?

+0

Meinst du connect() dauert zu lange, um Timeout oder Sie sind alrea dy verbunden und durchlaufen eine lange Zeit, wenn es nichts zu lesen gibt? – Duck

+0

@Duck: Mein Problem ist, dass connect() zu lange dauert, bis es zu einem Timeout kommt. Jede Verbindung in meinem Programm ist vorübergehend; Es sollte sofort getrennt werden, nachdem ein Status-Check-Handshake-Vorgang durchgeführt wurde. In meinem Fall muss die TCP_KEEP_ALIVE-Dauer nicht individuell angepasst werden. – RichardLiu

Antwort

96

können Sie die SO_RCVTIMEO und SO_SNDTIMEO Optionen Sockel verwenden Timeouts für alle Socket-Operationen einzustellen, etwa so:

struct timeval timeout;  
    timeout.tv_sec = 10; 
    timeout.tv_usec = 0; 

    if (setsockopt (sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, 
       sizeof(timeout)) < 0) 
     error("setsockopt failed\n"); 

    if (setsockopt (sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, 
       sizeof(timeout)) < 0) 
     error("setsockopt failed\n"); 

Edit: vom setsockoptman page:

SO_SNDTIMEO ist eine Option zu setzen ein Zeitüberschreitungswert für Ausgabevorgänge. Er akzeptiert einen struct timeval-Parameter mit der Anzahl der Sekunden und Mikrosekunden, die verwendet werden, um Wartezeiten für die Ausführung von Ausgabeoperationen zu begrenzen. Wenn eine Sendeoperation so lange blockiert ist, wird sie mit einer Teilzählung oder mit dem Fehler EWOULDBLOCK zurückgegeben, wenn keine Daten gesendet wurden. In der aktuellen Implementierung wird dieser Zeitgeber jedes Mal neu gestartet, wenn zusätzliche Daten an das Protokoll geliefert werden, was impliziert, dass der Grenzwert für Ausgabeteile gilt, deren Größe von der Niedrigwassermarke bis zur Hochwassermarke für die Ausgabe reicht.

SO_RCVTIMEO ist eine Option zum Festlegen eines Zeitüberschreitungswerts für Eingabeoperationen. Er akzeptiert einen struct timeval-Parameter mit der Anzahl der Sekunden und Mikrosekunden, die verwendet werden, um Wartezeiten für den Abschluss von Eingabeoperationen zu begrenzen. In der aktuellen Implementierung wird dieser Zeitgeber jedes Mal neu gestartet, wenn zusätzliche Daten von dem Protokoll empfangen werden, und somit ist das Limit tatsächlich ein Inaktivitätszeitgeber. Wenn eine Empfangsoperation so lange blockiert wurde, ohne zusätzliche Daten zu erhalten, wird sie mit einer kurzen Anzahl oder mit dem Fehler EWOULDBLOCK zurückgegeben, wenn keine Daten empfangen wurden. Der Parameter struct timeval muss ein positives Zeitintervall darstellen. Andernfalls gibt setsockopt() den Fehler EDOM zurück.

+1

Sind Sie sicher, dass dies für connect() funktioniert? Das glaube ich nicht. – Duck

+0

Was lässt Sie denken, dass es nicht für connect() funktioniert? Ich bin sicher, dass ich sie verwendet habe, um Timeouts bei connect() -Aufrufen festzulegen. – Toby

+0

@Toby: Ich habe versucht, sowohl blockierenden Modus und nicht blockierenden Modus, aber es funktioniert nicht. Diese beiden Parameter funktionieren anscheinend nur mit send() und recv(), nicht mit connect(). – RichardLiu

10

Können Sie nicht Ihr eigenes Timeout-System implementieren?

Behalten Sie eine sortierte Liste oder besser noch einen Prioritäts-Heap, wie Heath es vorschlägt, von Timeout-Ereignissen. Verwenden Sie in Ihren Auswahl- oder Abfrageaufrufen den Zeitüberschreitungswert vom Anfang der Zeitüberschreitungsliste. Wenn diese Zeitüberschreitung eintritt, führen Sie diese Aktion an diesem Zeitlimit aus.

Diese Aktion könnte einen Socket schließen, der noch nicht verbunden ist.

+0

Hmm ... das wäre eine gute Idee, aber es würde einige Anstrengungen erfordern. Ich hatte gehofft etwas wie setsockopt() Funktionsaufruf, der Verbindungszeitlimitdauer einzeln einstellen konnte. Übrigens, was passiert mit select(), wenn ich einen Connection-pending Socket in einem anderen Thread schließe? Wird es zu einer gewissen Situation führen? – RichardLiu

+0

Diese Lösung ist die sauberste und robusteste und hat keine Upvotes? Hier ist meins. – SquareRootOfTwentyThree

+0

So habe ich es immer gemacht, und es funktioniert gut. Ich vermute, dass es tragbarer ist als die anderen oben vorgeschlagenen Lösungen. –

12

ist nicht sicher, ob ich das Problem vollständig verstehen, aber denk, es ist im Zusammenhang ich einen hatte, ist mit Qt mit TCP-Socket-Kommunikation, die alle nicht-blockierend, Windows und Linux ..

wollten bekommen eine schnelle Benachrichtigung, wenn ein bereits verbundener Client fehlgeschlagen ist oder vollständig verschwunden ist und nicht länger als 900 Sekunden gewartet wird, bis das Trennungssignal ausgelöst wurde. Der Trick, um dies zu erreichen, bestand darin, die Socketoption TCP_USER_TIMEOUT der SOL_TCP-Schicht auf den erforderlichen Wert in Millisekunden zu setzen.

Dies ist eine vergleichsweise neue Option, siehe http://tools.ietf.org/html/rfc5482, aber anscheinend funktioniert es gut, versuchte es mit WinXP, Win7/X64 und Kubuntu 12.04/x64, meine Wahl von 10 s erwies sich ein wenig länger, aber viel besser als alles andere, was ich vorher probiert habe ;-)

das einzige Problem, das ich fand, war, die richtigen includes zu finden, da dies anscheinend nicht zum Standard-Socket hinzugefügt wird (noch ..), also endlich ich sie selbst wie folgt definiert:

#ifdef WIN32 
    #include <winsock2.h> 
#else 
    #include <sys/socket.h> 
#endif 

#ifndef SOL_TCP 
    #define SOL_TCP 6 // socket options TCP level 
#endif 
#ifndef TCP_USER_TIMEOUT 
    #define TCP_USER_TIMEOUT 18 // how long for loss retry before timeout [ms] 
#endif 

diese Socket-Option Einstellung funktioniert nur, wenn der Client bereits verbunden ist, die Codezeilen wie folgt aussehen:

int timeout = 10000; // user timeout in milliseconds [ms] 
setsockopt (fd, SOL_TCP, TCP_USER_TIMEOUT, (char*) &timeout, sizeof (timeout)); 

und dem Scheitern eines ersten Verbindungs ​​durch einen Timer gefangen wird gestartet, wenn() Aufruf verbinden, denn es wird kein Signal von Qt für das geschehen, wird das Verbindungssignal nicht erhöht werden, da wird sein wird, keine Verbindung, und das Trennsignal auch nicht angehoben werden, da es eine Verbindung wurde noch nicht ..

+0

Diese Antwort hat mir geholfen, nachdem ich es mit KEEPALIVE Einstellungen nicht ausprobiert habe. Vielen Dank! – sthlm58

+0

Danke!Dies half mir, die 15-minütige Verzögerung durch den TCP-Retransmit-Timer zu lösen. – brokenfoot

4

connect Timeout hat mit einem nicht-blockierenden Socket (NYS documentation auf connect) behandelt werden. Sie erhalten connect, um sofort zurückzukehren, und dann verwenden Sie select, um mit einem Timeout für den Abschluss der Verbindung zu warten.

Dies wird auch hier erklärt: Operation now in progress error on connect(function) error.

int wait_on_sock(int sock, long timeout, int r, int w) 
{ 
    struct timeval tv = {0,0}; 
    fd_set fdset; 
    fd_set *rfds, *wfds; 
    int n, so_error; 
    unsigned so_len; 

    FD_ZERO (&fdset); 
    FD_SET (sock, &fdset); 
    tv.tv_sec = timeout; 
    tv.tv_usec = 0; 

    TRACES ("wait in progress tv={%ld,%ld} ...\n", 
      tv.tv_sec, tv.tv_usec); 

    if (r) rfds = &fdset; else rfds = NULL; 
    if (w) wfds = &fdset; else wfds = NULL; 

    TEMP_FAILURE_RETRY (n = select (sock+1, rfds, wfds, NULL, &tv)); 
    switch (n) { 
    case 0: 
     ERROR ("wait timed out\n"); 
     return -errno; 
    case -1: 
     ERROR_SYS ("error during wait\n"); 
     return -errno; 
    default: 
     // select tell us that sock is ready, test it 
     so_len = sizeof(so_error); 
     so_error = 0; 
     getsockopt (sock, SOL_SOCKET, SO_ERROR, &so_error, &so_len); 
     if (so_error == 0) 
      return 0; 
     errno = so_error; 
     ERROR_SYS ("wait failed\n"); 
     return -errno; 
    } 
} 
+0

Große Antwort! Kann ich wissen, was ist der empfohlene Wert für Timeout hier, d. H. Für die Verbindung Timeout? – Coder

-4

Natürlich ist die erste Antwort die BESTE. Kann ich etwas hinzufügen?

...

Nach dem 2 setsockopt Sie können steuern, wenn der Kunde den Timeout-Test bestanden, oder mit diesem fehlgeschlagen:

nach dem

n = readline(sockd, recvline, MAXLINE); 

Sie Einsatz muss

if (n <= 0){ 
    if(write(sockd,"ERROR. Timeout di 5sec scaduto, sii piu' veloce\n",MAXLINE)<0) 
     err_sys("errore nella write"); 
    close(sockd); 
    sockd = 0; 
    break; 
} 
Verwandte Themen