65

I have a simple program to check if a port is open, but I want to shorten the timeout length on the socket connection because the default is far too long. I'm not sure how to do this though. Here's the 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;
}
The.Anti.9
  • 43,474
  • 48
  • 123
  • 161
  • You added in your answer "fcntl(sock, F_SETFL, O_NONBLOCK)" Note that after this the next socket read becomes also nonblocking! –  Sep 18 '13 at 10:44
  • Interestingly enough, it is mentioned in [this man page](https://linux.die.net/man/3/connect) but in the one I have installed on Ubuntu, you have to have the wit of reading the info about the `EINPROGRESS` error to understand that the socket can be non-blocking to do an asynchronous connection. – Alexis Wilke Aug 11 '22 at 17:19

8 Answers8

74

Set the socket non-blocking, and use select() (which takes a timeout parameter). If a non-blocking socket is trying to connect, then select() will indicate that the socket is writeable when the connect() finishes (either successfully or unsuccessfully). You then use getsockopt() to determine the outcome of the 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;
}
caf
  • 233,326
  • 40
  • 323
  • 462
  • 9
    This works in *nix, but does not work in Windows. In Windows, you determine if the socket is connected by looking at the return value of "select" in your code above. In windows, select returns 1 if the connection completed, or it returns 0 if the connection was not successful. If you look at so_error, then Windows always returns 0, even when the connection failed. That's windows for you, as they say. – deltamind106 May 08 '15 at 13:32
  • 1
    What if I don't want to use non-blocking mode for other socket operations (like read, write)? Can I clear `O_NONBLOCK` flag after socket is connected? If it is possible, is It safe? – anton_rh Jul 07 '16 at 12:37
  • 1
    @anton_rh: Yes, it is safe to clear `O_NONBLOCK` and put the socket back into blocking mode. – caf Jul 07 '16 at 12:52
  • 1
    It is also much better to use poll then select. Nobody should use "select" in new software anymore because it is broken when you increase the number of open file descriptors per process behind a very limited number. Also using SOCK_NONBLOCK directly in the socket call is recommended (maybe also use SOCK_CLOEXEC) – Lothar Jun 09 '17 at 21:21
  • I realize this thread is very old, but I do have a question about your solution: I notice that you don't check the return value of your connect() call. I've tried replicating your example, and I get an error connect() returned errno 119 "Connection already in progress". This is only true when I use the fcntl() call; I don't get an error otherwise. So...is this expected behavior? Thank you. – mzimmers Nov 05 '21 at 19:10
  • @mzimmers: It's expected that it would return non-zero with `errno` set to `EINPROGRESS`. You could check for other errors there if you like, but any other error should also be retrieved by the `getsockopt()` call later anyway. – caf Nov 07 '21 at 23:48
  • 1
    @deltamind106 maybe something changed, or maybe you tried in different situations, but in the basic case where `select` returns 0 because of a timeout because the remote end simply doesn't exist, `so_error` in the code above gets set to 10061 a.k.a. `WSAECONNREFUSED` a.k.a 'No connection could be made because the target machine actively refused it.'. In other words: works fine for me on Windows (10). – stijn Nov 16 '22 at 15:04
  • @deltamind106 That is not correct. `select()` always returns the number of sockets that are ready for whatever operation they were selected for. Same in Unix, Linux, Windows, AIX, HP/UX, MacOS, Solaris, ... anything you name. – user207421 Apr 25 '23 at 08:09
55

This article might help:

Connect with timeout (or another use for select() )

Looks like you put the socket into non-blocking mode until you've connected, and then put it back into blocking mode once the connection's established.

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 
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Jack
  • 1
  • 3
  • 2
  • Nice. This is a better suggestion than mine. – asveikau Apr 08 '10 at 05:17
  • 1
    This is a very good answer, why did you make it community wiki? You should earn some reputation for suggesting the resource. – Tim Post Apr 08 '10 at 05:19
  • 3
    The forum linked to seems to have changed their software, so the link is dead now. – Jorenko Jun 08 '11 at 19:14
  • 2
    Note: although this answer is deemed the right one, caf's solution below not only explains the strategy but provides working code. – ecume des jours Feb 02 '16 at 00:00
  • 3
    This solution (and it appears most others) has a flaw in that, when `signal`/`poll` is interrupted with EINTR, it starts the timeout back at 0, even though some time has already elapsed. See my solution which addresses this. – Jay Sullivan May 22 '20 at 17:20
  • Connection timeout using select() like this does not work when fd > 1024 on most of the linux or unix hosts. – Satish Burnwal Jun 17 '20 at 19:37
  • 2
    What is the point of the "do while" loop here? It seems this loop could only ever possibly run once. – josh798 Feb 23 '21 at 16:37
  • Does not handle signals appropriately. The code is way to short to handle all the select() corner cases. Note that select() may block on non-blocking sockets, and it may fall through on blocking sockets, and it may spuriously return without fds getting writable. The semantics of the returned timeout is undefined and thus cannot be relied upon. Using select() is difficult. The code above may in fact block indefinitely since the timeout is restarted with each signal, as Jay already said. – Johannes Overmann Mar 08 '21 at 15:13
29

The answers about using select()/poll() are right and code should be written this way to be portable.

However, since you're on Linux, you can do this:

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

See man 7 tcp and man setsockopt.

I used this to speed up the connect-timeout in a program I needed to patch quickly. Hacking it to timeout via select()/poll() was not an option.

schieferstapel
  • 1,025
  • 10
  • 15
29

Here is a modern connect_with_timeout implementation, using poll, with proper error and signal handling:

#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;
}
Jay Sullivan
  • 17,332
  • 11
  • 62
  • 86
  • 3
    +1000. Wow. I have to say this is the most underrated answer currently on StackOverflow! – kanso Nov 28 '20 at 18:54
  • 1
    +1 This is absolutely beautiful. Simple, extremely effective, completely self-contained, documented, and gets away from using select. – Alex Baum Dec 30 '20 at 21:10
  • among other answers this is the one i choose. non blocking + poll. – philippe lhardy Feb 14 '21 at 08:58
  • Nice! As always, if the solution using select() is less than 20 lines long it is usually broken. select() has difficult semantics. – Johannes Overmann Mar 08 '21 at 15:08
  • 3
    This is excellent, thanks. I'm still left wondering why there isn't a `connect` function which allows me to offer a timeout value as a parameter. Seems like a common need. As it stands, it appears that if I want something other than the default timeout, I have to replace my one `connect` call with a huge amount of code. – lurker Jul 16 '21 at 17:55
  • 1
    Not portable, will not work on Windows – p0358 Sep 19 '22 at 00:01
  • Whats the point of do..while(0) in the outermost loop ? What is the author trying to achieve here ? – moi Sep 23 '22 at 03:29
  • @moi `break` in it is the same as `goto end` where `end` is the next line after the `while (0)` – RandomB Oct 04 '22 at 12:24
9

On Linux you can also use:

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(...)

Don't forget to clear SO_SNDTIMEO after connect() if you don't need it.

Nahuel Greco
  • 1,080
  • 14
  • 12
5

This one has parametrized ip, port, timeout in seconds, handle connection errors and give you connection time in milliseconds:

#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;
}
dAm2K
  • 9,923
  • 5
  • 44
  • 47
5

The two socket options SO_RCVTIMEO and SO_SNDTIMEO have no effect on connect. Below is a link to the screenshot which includes this explanation, here I am just briefing it. The apt way of implementing timeouts with connect are using signal or select or poll.

Signals

connect can be interrupted by a self generated signal SIGALRM by using syscall (wrapper) alarm. But, a signal disposition should be installed for the same signal otherwise the program would be terminated. The code goes like this...

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

Select or Poll

As already some users provided nice explanation on how to use select to achieve connect timeout, it would not be necessary for me to reiterate the same. poll can be used in the same way. However, there are few mistakes that are common in all of the answers, which I would like to address.

  • Even though socket is non-blocking, if the server to which we are connecting is on the same local machine, connect may return with success. So it is advised to check the return value of connect before calling select.

  • Berkeley-derived implementations (and POSIX) have the following rules for non-blocking sockets and connect.

    1. When the connection completes successfully, the descriptor becomes writable (p. 531 of TCPv2).

    2. When the connection establishment encounters an error, the descriptor becomes both readable and writable (p. 530 of TCPv2).

So the code should handle these cases, here I just code the necessary modifications.

/* 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 from Unix Network Programming Volume 1

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
m0hithreddy
  • 1,752
  • 1
  • 10
  • 17
  • 1
    The answer incorrectly state that SNDTIMEO does not apply to 'connect'. This is not true - the "man 7 socket" page clealy state that using SO_SNDTIMEO will result in EINPROGRESS for connect. – dash-o Mar 08 '23 at 09:39
4

Is there anything wrong with Nahuel Greco's solution aside from the compilation error?

If I change one line

// Compilation error
setsockopt(fd, SO_SNDTIMEO, &timeout, sizeof(timeout));

to

// Fixed?
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));

then it seems to work as advertised - socket() returns a timeout error.

Resulting code:

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(...)

I'm not versed enough to know the tradeoffs are between a send timeout and a non-blocking socket, but I'm curious to learn.

Kevin W Matthews
  • 722
  • 6
  • 19
  • 2
    if you plan to use the socket in blocking mode, you will make fewer syscalls by implementing the SO_SNDTIMEO solution rather than putting the socket into non-blocking mode, connect and then putting back in blocking mode. Seems a little faster. – Nahuel Greco Nov 24 '18 at 15:09