webentwicklung-frage-antwort-db.com.de

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

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

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

Ich habe verschiedene Themen im Forum überprüft. Einige hatten vorgeschlagen, dass man beim Funktionsaufruf select () das Alarmsignal () verwenden oder eine Zeitüberschreitung 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 nicht zwischen allen anderen Verbindungen unterscheiden.

Gibt es eine Möglichkeit, die Standard-Zeitüberschreitungsdauer des Systems zu ändern?

61
RichardLiu

Mit den Socket-Optionen SO_RCVTIMEO und SO_SNDTIMEO können Sie Zeitüberschreitungen für alle Socket-Vorgänge festlegen, z.

    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");

Bearbeiten: aus dem setsockoptManpage :

SO_SNDTIMEO ist eine Option zum Festlegen eines Zeitlimits für Ausgabeoperationen. Es akzeptiert einen Struktur-Zeitwert-Parameter mit der Anzahl der Sekunden und Mikrosekunden, die verwendet werden, um das Warten auf den Abschluss von Ausgabeoperationen zu begrenzen. Wenn ein Sendevorgang für diese Zeit blockiert wurde, wird er 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 bedeutet, dass die Grenze für Ausgabeabschnitte gilt, deren Größe von der Niedrigwasser- bis zur Hochwasser-Marke für die Ausgabe reicht.

SO_RCVTIMEO ist eine Option zum Festlegen eines Zeitlimits für Eingabevorgänge. Es akzeptiert einen Struktur-Zeitwert-Parameter mit der Anzahl der Sekunden und Mikrosekunden, die verwendet werden, um das Warten auf den Abschluss von Eingabeoperationen zu begrenzen. In der aktuellen Implementierung wird dieser Zeitgeber jedes Mal neu gestartet, wenn zusätzliche Daten vom Protokoll empfangen werden, und daher ist das Limit tatsächlich ein Inaktivitätszeitgeber. Wenn eine Empfangsoperation so lange blockiert wurde, ohne dass zusätzliche Daten empfangen wurden, wird sie mit einer kurzen Zählung 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.

110
Toby

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

ich wollte eine schnelle Benachrichtigung erhalten, wenn ein bereits verbundener Client ausfällt oder vollständig verschwindet, und nicht die Standardzeit von mehr als 900 Sekunden warten, bis das Trennungssignal ausgelöst wird. Der Trick, um dies zum Laufen zu bringen, bestand darin, die Socket-Option TCP_USER_TIMEOUT des SOL_TCP-Layers 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, habe es mit WinXP, Win7/x64 und Kubuntu 12.04/x64 versucht. meine wahl von 10 s erwies sich als etwas länger, aber viel besser als alles, 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. Schließlich habe 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

das Setzen dieser Socket-Option funktioniert nur, wenn der Client bereits verbunden ist. Die Codezeilen sehen folgendermaßen 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 Zeitgeber abgefangen, der beim Aufrufen von connect () gestartet wird, da hierfür kein Signal von Qt vorhanden ist, das Verbindungssignal nicht angehoben wird, da keine Verbindung vorhanden ist, und das Trennungssignal auch nicht angesprochen werden, da noch keine Verbindung besteht ..

13
wgr

Können Sie kein eigenes Timeout-System implementieren?

Führen Sie eine sortierte Liste oder besser gesagt einen Prioritätshaufen von Timeout-Ereignissen, wie Heath vorschlägt. Verwenden Sie in Ihren Auswahl- oder Abrufanrufen den Zeitüberschreitungswert oben in der Zeitüberschreitungsliste. Wenn dieses Zeitlimit erreicht ist, führen Sie die mit diesem Zeitlimit verknüpfte Aktion aus.

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

9
Zan Lynx

connect Timeout muss mit einem nicht blockierenden Socket behandelt werden (GNU LibC Dokumentation on connect). Sie erhalten connect, um sofort zurückzukehren, und verwenden dann select, um mit einer Zeitüberschreitung zu warten, bis die Verbindung hergestellt ist.

Dies wird auch hier erklärt: Operation wird gerade ausgeführt, Fehler beim Verbinden (Funktions) 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;
    }
}
4
Couannette