Wie setze ich das Socket-Timeout in C, wenn mehrere Verbindungen hergestellt werden?

Lesezeit: 8 Minuten

Benutzeravatar von RichardLiu
RichardLiu

Ich schreibe ein einfaches Programm, das mehrere Verbindungen zu verschiedenen Servern zur Statusprüfung herstellt. Alle diese Verbindungen werden nach Bedarf erstellt; Bis zu 10 Verbindungen können gleichzeitig erstellt werden. Ich mag die Idee von einem Thread pro Socket nicht, also habe ich alle diese Client-Sockets nicht blockierend gemacht und sie in einen select()-Pool geworfen.

Es hat großartig funktioniert, bis sich mein Client darüber beschwert hat, dass die Wartezeit zu lang ist, bevor er den Fehlerbericht erhalten kann, wenn die Zielserver nicht mehr reagieren.

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

Gibt es eine Möglichkeit, die systemstandardmäßige Zeitüberschreitungsdauer zu ändern?

  • Meinen Sie damit, dass das Timeout von connect() zu lange dauert oder Sie bereits verbunden sind und eine lange Zeit durchlaufen, in der nichts zu lesen ist?

    – Ente

    15. November 2010 um 6:40 Uhr

  • @Duck: Mein Problem ist, dass das Timeout von connect() zu lange dauert. Jede Verbindung in meinem Programm ist vorübergehend; Die Verbindung soll sofort getrennt werden, nachdem ein Handshaking-Verfahren zur Statusprüfung durchgeführt wurde. In meinem Fall muss die TCP_KEEP_ALIVE-Dauer nicht individuell angepasst werden.

    – RichardLiu

    15. November 2010 um 6:59 Uhr

Tobys Benutzeravatar
Tobi

Sie können die Socket-Optionen SO_RCVTIMEO und SO_SNDTIMEO verwenden, um Zeitüberschreitungen für alle Socket-Operationen wie folgt festzulegen:

    struct timeval timeout;      
    timeout.tv_sec = 10;
    timeout.tv_usec = 0;
    
    if (setsockopt (sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout,
                sizeof timeout) < 0)
        error("setsockopt failed\n");

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

Bearbeiten: von dem setsockopt Manpage:

SO_SNDTIMEO ist eine Option, um einen Timeout-Wert für Ausgabevorgänge festzulegen. Es akzeptiert einen struct timeval-Parameter mit der Anzahl von Sekunden und Mikrosekunden, die verwendet werden, um die Wartezeiten für den Abschluss von Ausgabeoperationen zu begrenzen. Wenn eine Sendeoperation so lange blockiert hat, kehrt sie mit einer Teilzählung zurück oder mit dem Fehler EWOULDBLOCK, 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 die Grenze für Ausgabeabschnitte gilt, deren Größe von der Niedrigwassermarke bis zur Hochwassermarke für die Ausgabe reicht.

SO_RCVTIMEO ist eine Option, um einen Timeout-Wert für Eingabeoperationen festzulegen. Es akzeptiert einen struct timeval-Parameter mit der Anzahl von Sekunden und Mikrosekunden, die verwendet werden, um die Wartezeiten für den Abschluss von Eingabeoperationen zu begrenzen. In der aktuellen Implementierung wird dieser Zeitgeber jedes Mal neu gestartet, wenn zusätzliche Daten durch das Protokoll empfangen werden, und somit ist die Grenze tatsächlich ein Inaktivitätszeitgeber. Wenn eine Empfangsoperation so lange blockiert wurde, ohne weitere Daten zu erhalten, kehrt sie mit einer kurzen Zählung zurück oder mit dem Fehler EWOULDBLOCK, wenn keine Daten empfangen wurden. Der Parameter struct timeval muss ein positives Zeitintervall darstellen; andernfalls kehrt setsockopt() mit dem Fehler EDOM zurück.

  • Sind Sie sicher, dass dies für connect() funktioniert? Ich glaube nicht.

    – Ente

    15. November 2010 um 16:05 Uhr

  • Demzufolge pubs.opengroup.org/onlinepubs/009695399/functions/connect.html, “Wenn die Verbindung nicht sofort hergestellt werden kann und O_NONBLOCK nicht für den Dateideskriptor für den Socket gesetzt ist, soll connect() bis zu einem unbestimmten Timeout-Intervall blockieren, bis die Verbindung hergestellt ist.” Es sieht so aus, als ob diese Timeout-Intervalle nur für Lese- oder Schreibvorgänge gelten.

    – Robert

    8. November 2011 um 23:56 Uhr

  • 1. Dies funktioniert nicht für connect() oder allgemein für alle Socket-Operationen’. Es funktioniert jeweils zum Lesen und Schreiben. 2. Ich kann nicht erkennen, wie diese Antwort im nicht blockierenden Modus möglicherweise korrekt sein kann, wonach das OP fragt. Die richtige Antwort ist, ein select()-Timeout zu verwenden und zu verfolgen, welche Sockets zu welchen Zeiten Verbindungen initiiert hatten.

    – Benutzer207421

    17. Juni 2013 um 10:18 Uhr

  • Dies tut arbeiten für connect(). Sehen SO_RCVTIMEO und SO_SNDTIMEO in socket(7): Linux-Manpagedie besagt: “Wenn keine Daten übertragen wurden und das Timeout erreicht wurde, wird -1 mit gesetzter Fehlernummer zurückgegeben EAGAIN oder EWOULDBLOCK, oder EINPROGRESS (zum connect(2)) so als ob der Socket als nicht blockierend angegeben wurde”

    – Craig McQueen

    24. Mai 2016 um 5:11 Uhr


  • nur unter Linux 4.13 SO_SNDTIMEO setzt a connect() Auszeit, SO_RCVTIMEO scheint ignoriert zu werden.

    – Nahuel Greco

    6. April 2018 um 17:29 Uhr

Benutzeravatar von wgr
wgr

Ich bin mir nicht sicher, ob ich das Problem vollständig verstehe, aber ich denke, es hängt mit dem zusammen, das ich hatte. Ich verwende Qt mit TCP-Socket-Kommunikation, alle nicht blockierend, sowohl Windows als auch Linux.

wollte eine schnelle Benachrichtigung erhalten, wenn ein bereits verbundener Client ausgefallen oder vollständig verschwunden ist, und nicht die standardmäßigen 900+ Sekunden warten, bis das Trennungssignal ausgelöst wurde. Der Trick, um dies zum Laufen zu bringen, bestand darin, die TCP_USER_TIMEOUT-Socket-Option des SOL_TCP-Layers auf den erforderlichen Wert in Millisekunden zu setzen.

Dies ist eine vergleichsweise neue Option, siehe bitte https://www.rfc-editor.org/rfc/rfc5482aber anscheinend funktioniert es gut, habe es mit WinXP, Win7/x64 und Kubuntu 12.04/x64 versucht, meine Wahl von 10 s war etwas länger, aber viel besser als alles andere, was ich zuvor versucht habe 😉

Das einzige Problem, auf das ich gestoßen bin, war, die richtigen Includes zu finden, da dies anscheinend (noch) nicht zu den Standard-Socket-Includes hinzugefügt wurde, also habe ich sie schließlich 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

Das Setzen dieser Socket-Option funktioniert nur, wenn der Client bereits verbunden ist, die Codezeilen sehen so aus:

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

und der Fehler einer anfänglichen Verbindung wird von einem Timer abgefangen, der beim Aufrufen von connect() gestartet wird, da dafür kein Signal von Qt vorhanden ist, wird das Verbindungssignal nicht ausgelöst, da keine Verbindung besteht, und das Trennsignal wird es tun auch nicht erhoben werden, da noch keine Verbindung bestanden hat..

  • Diese Antwort hat mir geholfen, nachdem ich es mit den KEEPALIVE-Einstellungen nicht geschafft hatte. Vielen Dank!

    – stlm58

    13. April 2015 um 9:09 Uhr

  • Vielen Dank! Dies half mir, die 15-minütige Verzögerung bei der Trennung zu bewältigen, die durch den TCP-Retransmit-Timer verursacht wurde.

    – gebrochener Fuß

    9. Dezember 2015 um 0:18 Uhr

Benutzeravatar von Zan Lynx
Zan Luchs

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

Führen Sie eine sortierte Liste oder besser noch einen Prioritätsspeicher, wie Heath vorschlägt, von Timeout-Ereignissen. Verwenden Sie in Ihren Auswahl- oder Abfrageaufrufen den Zeitüberschreitungswert oben in der Zeitüberschreitungsliste. Wenn diese Zeitüberschreitung eintrifft, führen Sie die mit dieser Zeitüberschreitung verbundene Aktion aus.

Diese Aktion könnte das Schließen eines Sockets sein, der noch nicht verbunden ist.

  • Hmm … das wäre eine gute Idee, aber es würde einige Mühe kosten, es umzusetzen. Ich hatte auf etwas wie den Funktionsaufruf setsockopt() gehofft, der die Zeitüberschreitungsdauer der Verbindung individuell festlegen könnte. Übrigens, was würde mit select() passieren, wenn ich einen Socket mit ausstehendem Verbindungsstatus in einem anderen Thread schließe? Wird es eine Thread-Jagd-Situation verursachen?

    – RichardLiu

    15. November 2010 um 7:07 Uhr


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

    – SquareRootOfTwentyThree

    18. Oktober 2012 um 6:28 Uhr

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

    – Jeremy Friesner

    31. Januar 2013 um 16:26 Uhr

  • Dies ist eine großartige Möglichkeit, +1. Ich mache das gerne mit einem Priority-Heap — spart ein wenig O()-Zeit gegenüber einer vollständig sortierten Liste, wenn wir immer nur das Element mit der höchsten Priorität lesen.

    – Heide Hunnicutt

    2. März 2013 um 0:24 Uhr


Benutzeravatar von Couannette
Couannette

connect Timeout muss mit einem nicht blockierenden Socket behandelt werden (GNU LibC Dokumentation an connect). Du erhältst connect sofort zurückgeben und dann verwenden select mit einer Zeitüberschreitung warten, bis die Verbindung hergestellt ist.

Dies wird auch hier erklärt: Operation läuft jetzt Fehler bei Verbindung (Funktion) Fehler.

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;
    }
}

1416100cookie-checkWie setze ich das Socket-Timeout in C, wenn mehrere Verbindungen hergestellt werden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy