C: socket connection timeout

CNetwork Programming

C Problem Overview


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

C Solutions


Solution 1 - C

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

Solution 2 - C

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 
}

Solution 3 - C

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.

Solution 4 - C

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

Solution 5 - C

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.

Solution 6 - C

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

Solution 7 - C

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.

Solution 8 - C

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionThe.Anti.9View Question on Stackoverflow
Solution 1 - CcafView Answer on Stackoverflow
Solution 2 - CJackView Answer on Stackoverflow
Solution 3 - CschieferstapelView Answer on Stackoverflow
Solution 4 - CJay SullivanView Answer on Stackoverflow
Solution 5 - CNahuel GrecoView Answer on Stackoverflow
Solution 6 - CdAm2KView Answer on Stackoverflow
Solution 7 - CKevin W MatthewsView Answer on Stackoverflow
Solution 8 - Cm0hithreddyView Answer on Stackoverflow