C: Socket-Verbindungszeitüberschreitung

Lesezeit: 14 Minuten

Ich habe ein einfaches Programm, um zu überprüfen, ob ein Port geöffnet ist, aber ich möchte die Timeout-Länge für die Socket-Verbindung verkürzen, da die Standardeinstellung viel zu lang ist. Ich bin mir nicht sicher, wie ich das machen soll. Hier ist der Code:

#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char **argv) {
    u_short port;                /* user specified port number */
    char addr[1023];             /* will be a copy of the address entered by u */
    struct sockaddr_in address;  /* the libc network address data structure */
    short int sock = -1;         /* file descriptor for the network socket */

    if (argc != 3) {
        fprintf(stderr, "Usage %s <port_num> <address>", argv[0]);
        return EXIT_FAILURE;
    }

    address.sin_addr.s_addr = inet_addr(argv[2]); /* assign the address */
    address.sin_port = htons(atoi(argv[2]));            /* translate int2port num */

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (connect(sock,(struct sockaddr *)&address,sizeof(address)) == 0) {
        printf("%i is open\n", port);
    }  
    close(sock);
    return 0;
}

  • Sie haben in Ihrer Antwort “fcntl (sock, F_SETFL, O_NONBLOCK)” hinzugefügt. Beachten Sie, dass danach der nächste gelesene Socket auch nicht blockierend wird!

    Benutzer2791114

    18. September 2013 um 10:44 Uhr

  • Interessanterweise wird es in erwähnt diese Manpage Aber in dem, das ich auf Ubuntu installiert habe, müssen Sie den Witz haben, die Informationen darüber zu lesen EINPROGRESS Fehler zu verstehen, dass der Socket nicht blockierend sein kann, um eine asynchrone Verbindung herzustellen.

    – Alexis Wilke

    11. August um 17:19 Uhr

Stellen Sie die Buchse auf nicht blockierend ein und verwenden Sie sie select() (was einen Timeout-Parameter benötigt). Wenn ein nicht blockierender Socket versucht, eine Verbindung herzustellen, dann select() zeigt an, dass der Socket beschreibbar ist, wenn die connect() beendet (entweder erfolgreich oder erfolglos). Sie verwenden dann getsockopt() um das Ergebnis zu bestimmen connect():

int main(int argc, char **argv) {
    u_short port;                /* user specified port number */
    char *addr;                  /* will be a pointer to the address */
    struct sockaddr_in address;  /* the libc network address data structure */
    short int sock = -1;         /* file descriptor for the network socket */
    fd_set fdset;
    struct timeval tv;

    if (argc != 3) {
        fprintf(stderr, "Usage %s <port_num> <address>\n", argv[0]);
        return EXIT_FAILURE;
    }

    port = atoi(argv[1]);
    addr = argv[2];

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr(addr); /* assign the address */
    address.sin_port = htons(port);            /* translate int2port num */

    sock = socket(AF_INET, SOCK_STREAM, 0);
    fcntl(sock, F_SETFL, O_NONBLOCK);

    connect(sock, (struct sockaddr *)&address, sizeof(address));

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

    if (select(sock + 1, NULL, &fdset, NULL, &tv) == 1)
    {
        int so_error;
        socklen_t len = sizeof so_error;

        getsockopt(sock, SOL_SOCKET, SO_ERROR, &so_error, &len);

        if (so_error == 0) {
            printf("%s:%d is open\n", addr, port);
        }
    }

    close(sock);
    return 0;
}

  • Dies funktioniert in * nix, aber nicht in Windows. In Windows bestimmen Sie, ob der Socket verbunden ist, indem Sie sich den Rückgabewert von “select” in Ihrem obigen Code ansehen. In Windows gibt select 1 zurück, wenn die Verbindung hergestellt wurde, oder 0, wenn die Verbindung nicht erfolgreich war. Wenn Sie sich so_error ansehen, gibt Windows immer 0 zurück, auch wenn die Verbindung fehlgeschlagen ist. Das sind Fenster für Sie, wie sie sagen.

    – deltamind106

    8. Mai 2015 um 13:32 Uhr

  • Was ist, wenn ich den nicht blockierenden Modus nicht für andere Socket-Operationen (wie Lesen, Schreiben) verwenden möchte? Kann ich löschen O_NONBLOCK Flag, nachdem Socket verbunden ist? Wenn es möglich ist, ist es sicher?

    – anton_rh

    7. Juli 2016 um 12:37 Uhr

  • @anton_rh: Ja, es ist sicher zu löschen O_NONBLOCK und den Socket wieder in den Sperrmodus versetzen.

    – Café

    7. Juli 2016 um 12:52 Uhr

  • Es ist auch viel besser, poll als select zu verwenden. Niemand sollte mehr “select” in neuer Software verwenden, da es kaputt geht, wenn Sie die Anzahl der offenen Dateideskriptoren pro Prozess hinter eine sehr begrenzte Zahl erhöhen. Es wird auch empfohlen, SOCK_NONBLOCK direkt im Socket-Aufruf zu verwenden (evtl. auch SOCK_CLOEXEC verwenden).

    – Lothar

    9. Juni 2017 um 21:21 Uhr

  • Mir ist klar, dass dieser Thread sehr alt ist, aber ich habe eine Frage zu Ihrer Lösung: Mir ist aufgefallen, dass Sie den Rückgabewert Ihres connect()-Aufrufs nicht überprüfen. Ich habe versucht, Ihr Beispiel zu replizieren, und ich erhalte einen Fehler, bei dem connect() errno 119 „Verbindung bereits im Gange“ zurückgegeben hat. Dies gilt nur, wenn ich den Aufruf von fcntl() verwende; Sonst bekomme ich keinen Fehler. Also … ist das erwartetes Verhalten? Vielen Dank.

    – Zimmer

    5. November 2021 um 19:10 Uhr

Dieser Artikel könnte helfen:

Verbinden mit timeout (oder eine andere Verwendung für select() )

Sieht so aus, als hätten Sie den Socket in den nicht blockierenden Modus versetzt, bis Sie eine Verbindung hergestellt haben, und ihn dann wieder in den blockierenden Modus versetzt, sobald die Verbindung hergestellt ist.

void connect_w_to(void) { 
  int res; 
  struct sockaddr_in addr; 
  long arg; 
  fd_set myset; 
  struct timeval tv; 
  int valopt; 
  socklen_t lon; 

  // Create socket 
  soc = socket(AF_INET, SOCK_STREAM, 0); 
  if (soc < 0) { 
     fprintf(stderr, "Error creating socket (%d %s)\n", errno, strerror(errno)); 
     exit(0); 
  } 

  addr.sin_family = AF_INET; 
  addr.sin_port = htons(2000); 
  addr.sin_addr.s_addr = inet_addr("192.168.0.1"); 

  // Set non-blocking 
  if( (arg = fcntl(soc, F_GETFL, NULL)) < 0) { 
     fprintf(stderr, "Error fcntl(..., F_GETFL) (%s)\n", strerror(errno)); 
     exit(0); 
  } 
  arg |= O_NONBLOCK; 
  if( fcntl(soc, F_SETFL, arg) < 0) { 
     fprintf(stderr, "Error fcntl(..., F_SETFL) (%s)\n", strerror(errno)); 
     exit(0); 
  } 
  // Trying to connect with timeout 
  res = connect(soc, (struct sockaddr *)&addr, sizeof(addr)); 
  if (res < 0) { 
     if (errno == EINPROGRESS) { 
        fprintf(stderr, "EINPROGRESS in connect() - selecting\n"); 
        do { 
           tv.tv_sec = 15; 
           tv.tv_usec = 0; 
           FD_ZERO(&myset); 
           FD_SET(soc, &myset); 
           res = select(soc+1, NULL, &myset, NULL, &tv); 
           if (res < 0 && errno != EINTR) { 
              fprintf(stderr, "Error connecting %d - %s\n", errno, strerror(errno)); 
              exit(0); 
           } 
           else if (res > 0) { 
              // Socket selected for write 
              lon = sizeof(int); 
              if (getsockopt(soc, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &lon) < 0) { 
                 fprintf(stderr, "Error in getsockopt() %d - %s\n", errno, strerror(errno)); 
                 exit(0); 
              } 
              // Check the value returned... 
              if (valopt) { 
                 fprintf(stderr, "Error in delayed connection() %d - %s\n", valopt, strerror(valopt) 
); 
                 exit(0); 
              } 
              break; 
           } 
           else { 
              fprintf(stderr, "Timeout in select() - Cancelling!\n"); 
              exit(0); 
           } 
        } while (1); 
     } 
     else { 
        fprintf(stderr, "Error connecting %d - %s\n", errno, strerror(errno)); 
        exit(0); 
     } 
  } 
  // Set to blocking mode again... 
  if( (arg = fcntl(soc, F_GETFL, NULL)) < 0) { 
     fprintf(stderr, "Error fcntl(..., F_GETFL) (%s)\n", strerror(errno)); 
     exit(0); 
  } 
  arg &= (~O_NONBLOCK); 
  if( fcntl(soc, F_SETFL, arg) < 0) { 
     fprintf(stderr, "Error fcntl(..., F_SETFL) (%s)\n", strerror(errno)); 
     exit(0); 
  } 
  // I hope that is all 
}

  • Dies ist eine sehr gute Antwort. Warum haben Sie es zum Community-Wiki gemacht? Sie sollten sich einen Ruf verdienen, wenn Sie die Ressource vorschlagen.

    – Tim Post

    8. April 2010 um 5:19 Uhr

  • Das verlinkte Forum scheint seine Software geändert zu haben, also ist der Link jetzt tot.

    – Jorenko

    8. Juni 2011 um 19:14 Uhr

  • Hinweis: Obwohl diese Antwort als richtig angesehen wird, erklärt die folgende Lösung von caf nicht nur die Strategie, sondern bietet auch funktionierenden Code.

    – ecume des jours

    2. Februar 2016 um 0:00 Uhr

  • Diese Lösung (und anscheinend die meisten anderen) hat darin einen Fehler signal/poll mit EINTR unterbrochen wird, beginnt der Timeout wieder bei 0, obwohl schon einige Zeit verstrichen ist. Sehen Sie sich meine Lösung an, die dies anspricht.

    – Jay Sullivan

    22. Mai 2020 um 17:20 Uhr

  • Was ist der Sinn der “do while”-Schleife hier? Es scheint, dass diese Schleife nur einmal ausgeführt werden kann.

    – josh798

    23. Februar 2021 um 16:37 Uhr

Die Antworten zur Verwendung select()/poll() sind richtig und Code sollte auf diese Weise geschrieben werden, um portabel zu sein.

Da Sie sich jedoch unter Linux befinden, können Sie Folgendes tun:

int synRetries = 2; // Send a total of 3 SYN packets => Timeout ~7s
setsockopt(fd, IPPROTO_TCP, TCP_SYNCNT, &synRetries, sizeof(synRetries));

Sehen man 7 tcp und man setsockopt.

Ich habe dies verwendet, um das Verbindungs-Timeout in einem Programm zu beschleunigen, das ich schnell patchen musste. Hacken Sie es zum Timeout über select()/poll() war keine Option.

  • Nett! Einfach und nützlich! Include zum Hinzufügen: #include

    – Johannes Übermann

    8. März 2021 um 16:04 Uhr

  • synRetries = 1 führt zu ~3s Timeout (empirisch) und synRetries = 0 leider zu ~127s Timeout (nicht zu ~1s wie man erwarten würde). synRetries = 2 ~7s Timeout bestätigt.

    – Johannes Übermann

    8. März 2021 um 16:54 Uhr


Benutzeravatar von Jay Sullivan
Jay Sullivan

Hier ist ein modernes connect_with_timeout Umsetzung, Verwendung pollmit richtiger Fehler- und Signalbehandlung:

#include <sys/socket.h>
#include <fcntl.h>
#include <poll.h>
#include <time.h>

int connect_with_timeout(int sockfd, const struct sockaddr *addr, socklen_t addrlen, unsigned int timeout_ms) {
    int rc = 0;
    // Set O_NONBLOCK
    int sockfd_flags_before;
    if((sockfd_flags_before=fcntl(sockfd,F_GETFL,0)<0)) return -1;
    if(fcntl(sockfd,F_SETFL,sockfd_flags_before | O_NONBLOCK)<0) return -1;
    // Start connecting (asynchronously)
    do {
        if (connect(sockfd, addr, addrlen)<0) {
            // Did connect return an error? If so, we'll fail.
            if ((errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
                rc = -1;
            }
            // Otherwise, we'll wait for it to complete.
            else {
                // Set a deadline timestamp 'timeout' ms from now (needed b/c poll can be interrupted)
                struct timespec now;
                if(clock_gettime(CLOCK_MONOTONIC, &now)<0) { rc=-1; break; }
                struct timespec deadline = { .tv_sec = now.tv_sec,
                                             .tv_nsec = now.tv_nsec + timeout_ms*1000000l};
                // Wait for the connection to complete.
                do {
                    // Calculate how long until the deadline
                    if(clock_gettime(CLOCK_MONOTONIC, &now)<0) { rc=-1; break; }
                    int ms_until_deadline = (int)(  (deadline.tv_sec  - now.tv_sec)*1000l
                                                  + (deadline.tv_nsec - now.tv_nsec)/1000000l);
                    if(ms_until_deadline<0) { rc=0; break; }
                    // Wait for connect to complete (or for the timeout deadline)
                    struct pollfd pfds[] = { { .fd = sockfd, .events = POLLOUT } };
                    rc = poll(pfds, 1, ms_until_deadline);
                    // If poll 'succeeded', make sure it *really* succeeded
                    if(rc>0) {
                        int error = 0; socklen_t len = sizeof(error);
                        int retval = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
                        if(retval==0) errno = error;
                        if(error!=0) rc=-1;
                    }
                }
                // If poll was interrupted, try again.
                while(rc==-1 && errno==EINTR);
                // Did poll timeout? If so, fail.
                if(rc==0) {
                    errno = ETIMEDOUT;
                    rc=-1;
                }
            }
        }
    } while(0);
    // Restore original O_NONBLOCK state
    if(fcntl(sockfd,F_SETFL,sockfd_flags_before)<0) return -1;
    // Success
    return rc;
}

Benutzeravatar von Nahuel Greco
Nahuel Greco

Unter Linux können Sie auch verwenden:

struct timeval timeout;
timeout.tv_sec  = 7;  // after 7 seconds connect() will timeout
timeout.tv_usec = 0;
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
connect(...)

Löschen nicht vergessen SO_SNDTIMEO nach connect() wenn du es nicht brauchst.

  • Ich bevorzuge die obige Form mit dem SOL_SOCKET im Setsockopt.

    – Adrian May

    22. November 2018 um 15:57 Uhr

Benutzeravatar von dAm2K
dAm2K

Dieser hat IP, Port, Timeout in Sekunden parametrisiert, behandelt Verbindungsfehler und gibt Ihnen die Verbindungszeit in Millisekunden:

#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

int main(int argc, char **argv) {
    struct sockaddr_in addr_s;
    char *addr;
    short int fd=-1;
    int port;
    fd_set fdset;
    struct timeval tv;
    int rc;
    int so_error;
    socklen_t len;
    struct timespec tstart={0,0}, tend={0,0};
    int seconds;

    if (argc != 4) {
        fprintf(stderr, "Usage: %s <ip> <port> <timeout_seconds>\n", argv[0]);
        return 1;
    }

    addr = argv[1];
    port = atoi(argv[2]);
    seconds = atoi(argv[3]);

    addr_s.sin_family = AF_INET; // utilizzo IPv4
    addr_s.sin_addr.s_addr = inet_addr(addr);
    addr_s.sin_port = htons(port);

    clock_gettime(CLOCK_MONOTONIC, &tstart);

    fd = socket(AF_INET, SOCK_STREAM, 0);
    fcntl(fd, F_SETFL, O_NONBLOCK); // setup non blocking socket

    // make the connection
    rc = connect(fd, (struct sockaddr *)&addr_s, sizeof(addr_s));
    if ((rc == -1) && (errno != EINPROGRESS)) {
        fprintf(stderr, "Error: %s\n", strerror(errno));
        close(fd);
        return 1;
    }
    if (rc == 0) {
        // connection has succeeded immediately
        clock_gettime(CLOCK_MONOTONIC, &tend);
        printf("socket %s:%d connected. It took %.5f seconds\n",
            addr, port, (((double)tend.tv_sec + 1.0e-9*tend.tv_nsec) - ((double)tstart.tv_sec + 1.0e-9*tstart.tv_nsec)));

        close(fd);
        return 0;
    } /*else {
        // connection attempt is in progress
    } */

    FD_ZERO(&fdset);
    FD_SET(fd, &fdset);
    tv.tv_sec = seconds;
    tv.tv_usec = 0;

    rc = select(fd + 1, NULL, &fdset, NULL, &tv);
    switch(rc) {
    case 1: // data to read
        len = sizeof(so_error);

        getsockopt(fd, SOL_SOCKET, SO_ERROR, &so_error, &len);

        if (so_error == 0) {
            clock_gettime(CLOCK_MONOTONIC, &tend);
            printf("socket %s:%d connected. It took %.5f seconds\n",
                addr, port, (((double)tend.tv_sec + 1.0e-9*tend.tv_nsec) - ((double)tstart.tv_sec + 1.0e-9*tstart.tv_nsec)));
            close(fd);
            return 0;
        } else { // error
            printf("socket %s:%d NOT connected: %s\n", addr, port, strerror(so_error));
        }
        break;
    case 0: //timeout
        fprintf(stderr, "connection timeout trying to connect to %s:%d\n", addr, port);
        break;
    }

    close(fd);
    return 0;
}

  • Ich bevorzuge die obige Form mit dem SOL_SOCKET im Setsockopt.

    – Adrian May

    22. November 2018 um 15:57 Uhr

Benutzeravatar von Alexis Wilke
Alex Wilke

Die beiden Steckdosenoptionen SO_RCVTIMEO und SO_SNDTIMEO haben keinen Einfluss auf connect. Unten ist ein Link zu dem Screenshot, der diese Erklärung enthält, hier stelle ich es nur kurz vor. Die passende Art, Timeouts mit zu implementieren connect benutzen signal oder select or poll.

Signale

connect kann durch ein selbst erzeugtes Signal unterbrochen werden SIGALRM durch Verwendung von Syscall (Wrapper) alarm. Allerdings sollte für das gleiche Signal eine Signaldisposition installiert werden, sonst würde das Programm beendet werden. Der Code geht so…

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<signal.h>
#include<errno.h>

static void signal_handler(int signo)
{
    return; // Do nothing just interrupt.
}

int main()
{
    /* Register signal handler */

    struct sigaction act, oact;

    act.sa_handler = signal_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

#ifdef SA_INTERRUPT
    act.sa_flags |= SA_INTERRUPT;
#endif

    if(sigaction(SIGALRM, &act, &oact) < 0)  // Error registering signal handler.
    {
        fprintf(stderr, "Error registering signal disposition\n");
        exit(1);
    }

    /* Prepare your socket and sockaddr structures */

    int sockfd;
    struct sockaddr* servaddr;

    /* Implementing timeout connect */

    int sec = 30;

    if(alarm(sec) != 0)
        fprintf(stderr, "Already timer was set\n");

    if(connect(sockfd, servaddr, sizeof(struct sockaddr)) < 0)
    {
        if(errno == EINTR)
            fprintf(stderr, "Connect timeout\n");
        else
            fprintf(stderr, "Connect failed\n");

        close(sockfd);
    
        exit(1);
    }

    alarm(0);  /* turn off the alarm */

    sigaction(SIGALRM, &oact, NULL);  /* Restore the default actions of SIGALRM */

    /* Use socket */


    /* End program */

    close(sockfd);
    return 0;
}

Auswählen oder abfragen

Wie bereits einige Benutzer eine nette Erklärung zur Verwendung gegeben haben select erreichen connect timeout, wäre es nicht nötig, dass ich dasselbe noch einmal sage. poll können genauso verwendet werden. Es gibt jedoch einige Fehler, die in allen Antworten üblich sind, auf die ich eingehen möchte.

  • Obwohl Socket nicht blockiert, wenn sich der Server, zu dem wir eine Verbindung herstellen, auf demselben lokalen Computer befindet, connect kann mit Erfolg zurückkehren. Es wird daher empfohlen, den Rückgabewert von zu überprüfen connect vor dem Anruf select.

  • Von Berkeley abgeleitete Implementierungen (und POSIX) haben die folgenden Regeln für nicht blockierende Sockets und connect.

    1. Wenn die Verbindung erfolgreich abgeschlossen wird, wird der Deskriptor beschreibbar (S. 531 von TCPv2).

    2. Wenn beim Verbindungsaufbau ein Fehler auftritt, wird der Deskriptor sowohl lesbar als auch beschreibbar (Seite 530 von TCPv2).

Der Code sollte also diese Fälle behandeln, hier codiere ich nur die notwendigen Änderungen.

/* All the code stays */

/* Modifications at connect */

int conn_ret = connect(sockfd, servaddr, sizeof(struct sockdaddr));

if(conn_ret == 0)
    goto done;

/* Modifications at select */

int sec = 30;
for( ; ; )
{
    struct timeval timeo;
    timeo.tv_sec = sec;
    timeo.tv_usec = 0;

    fd_set wr_set, rd_set;
    FDZERO(&wr_set);
    FD_SET(sockfd, &wr_set);
    rd_set = wr_set;
    int sl_ret = select(sockfd + 1, &rd_set, &wr_set, NULL, &timeo);

    /* All the code stays */
}


done:
    
    /* Use your socket */

Text aus Unix Network Programming Volume 1

1413120cookie-checkC: Socket-Verbindungszeitüberschreitung

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

Privacy policy