Non blocking connect

Keywords: socket Linux Windows network

In the socket blocking mode, the connect function will not return until there is a clear result (or the connection succeeds or the connection fails). If the server address is "far away" and the connection speed is slow, the connect function may cause the program to block in the connect process Functions work for a long time (such as two or three seconds). Although this generally does not affect programs that rely on network communication, in actual projects, we tend to use the so-called asynchronous connect technology, or non blocking connect. This process generally has the following steps:

1. Create a socket and set the socket to non blocking mode;
2. Call the connect function. At this time, no matter whether the connect function is connected successfully or not, it will return immediately. If - 1 is returned, it does not necessarily mean the connection is wrong. If the error code is EINPROGRESS at this time, it means the connection is being attempted;
3. Then call the select function to determine whether the socket is writable within the specified time. If it is writable, it means the connection is successful. Otherwise, it means the connection is failed.

Write the code according to the above process as follows:

/**
 * Asynchronous connect, nonblocking_connect.cpp
 * zhao m 2020.3.4
 */
#include <sys/types.h> 
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT     3000
#define SEND_DATA       "helloworld"

int main(int argc, char* argv[])
{
    //1. Create a socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1)
    {
        std::cout << "create client socket error." << std::endl;
        return -1;
    }
	
	//Set clientfd to non blocking mode	
	int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
	int newSocketFlag = oldSocketFlag | O_NONBLOCK;
	if (fcntl(clientfd, F_SETFL,  newSocketFlag) == -1)
	{
		close(clientfd);
		std::cout << "set socket to nonblock error." << std::endl;
		return -1;
	}

    //2. Connect to the server
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
    serveraddr.sin_port = htons(SERVER_PORT);
	for (;;)
	{
		int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
		if (ret == 0)
		{
			std::cout << "connect to server successfully." << std::endl;
			close(clientfd);
			return 0;
		} 
		else if (ret == -1) 
		{
			if (errno == EINTR)
			{
				//Connect action interrupted by signal, retry connect
				std::cout << "connecting interruptted by signal, try again." << std::endl;
				continue;
			} else if (errno == EINPROGRESS)
			{
				//Connection is trying
				break;
			} else {
				//There was a real mistake,
				close(clientfd);
				return -1;
			}
		}
	}
	
	fd_set writeset;
    FD_ZERO(&writeset);
    FD_SET(clientfd, &writeset);
	//TV sec and TV USEC can be used to control the timeout with smaller precision
    struct timeval tv;
    tv.tv_sec = 3;  
    tv.tv_usec = 0;
    if (select(clientfd + 1, NULL, &writeset, NULL, &tv) == 1)
    {
		std::cout << "[select] connect to server successfully." << std::endl;
	} else {
		std::cout << "[select] connect to server error." << std::endl;
	}

	//5. Close socket
	close(clientfd);

    return 0;
}

In order to distinguish whether the connection is successful when the connect function is called or whether the connection is successful through the select function, we add the "[Select]" tag to the output of the latter to show the difference.

Let's start a server program with nc command:

nc -v -l 0.0.0.0 3000

Then compile the client program and execute:

[root@localhost testsocket]# g++ -g -o nonblocking_connect nonblocking_connect.cpp 
[root@localhost testsocket]# ./nonblocking_connect 
[select] connect to server successfully.

We shut down the server program and restart the client. At this time, the connection fails. The output of the program is as follows:

[root@localhost testsocket]# ./nonblocking_connect 
[select] connect to server successfully.

Strange? Why can't the connection get the same output? Is there a problem with the procedure? This is because:

  • On Windows system, before a socket is connected, we use the select function to check whether it is writable and get the correct result (not writable). After the connection is successful, it will become writable. Therefore, there is no problem when the asynchronous connect writing process described above is used on Windows system.

  • Before the connection of a socket is not established on the Linux system, use the select function to check whether it is writable. You will also get writable results, so the above process is not applicable to the Linux system. The correct way is to use select to detect not only the writable but also the error of socket at this time after connect. The error code is used to detect whether the connection is connected. If the error code is 0, it means the connection is connected. Otherwise, it means the connection is not connected. The complete code is as follows:

/**
 * Linux Linux > nonblocking > connect.cpp
 * zhangyl 2018.12.17
 */
#include <sys/types.h> 
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT     3000
#define SEND_DATA       "helloworld"

int main(int argc, char* argv[])
{
    //1. Create a socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1)
    {
        std::cout << "create client socket error." << std::endl;
        return -1;
    }
	
	//Set clientfd to non blocking mode,
	int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
	int newSocketFlag = oldSocketFlag | O_NONBLOCK;
	if (fcntl(clientfd, F_SETFL,  newSocketFlag) == -1)
	{
		close(clientfd);
		std::cout << "set socket to nonblock error." << std::endl;
		return -1;
	}

    //2. Connect to the server
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
    serveraddr.sin_port = htons(SERVER_PORT);
	for (;;)
	{
		int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
		if (ret == 0)
		{
			std::cout << "connect to server successfully." << std::endl;
			close(clientfd);
			return 0;
		} 
		else if (ret == -1) 
		{
			if (errno == EINTR)
			{
				//Connect action interrupted by signal, retry connect
				std::cout << "connecting interruptted by signal, try again." << std::endl;
				continue;
			} else if (errno == EINPROGRESS)
			{
				//Connection is trying
				break;
			} else {
				//There was a real mistake,
				close(clientfd);
				return -1;
			}
		}
	}
	
	fd_set writeset;
    FD_ZERO(&writeset);
    FD_SET(clientfd, &writeset);
	//TV sec and TV USEC can be used to control the timeout with smaller precision
    struct timeval tv;
    tv.tv_sec = 3;  
    tv.tv_usec = 0;
    if (select(clientfd + 1, NULL, &writeset, NULL, &tv) != 1)
    {
		std::cout << "[select] connect to server error." << std::endl;
		close(clientfd);
		return -1;
	}
	
	int err;
    socklen_t len = static_cast<socklen_t>(sizeof err);
    if (::getsockopt(clientfd, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
	{
        close(clientfd);
		return -1;
	}
        
    if (err == 0)
        std::cout << "connect to server successfully." << std::endl;
    else
    	std::cout << "connect to server error." << std::endl;
    
	//5. Close socket
	close(clientfd);

    return 0;
}
78 original articles published, praised 0, 913 visitors
Private letter follow

Posted by romic on Tue, 03 Mar 2020 23:43:35 -0800