Non blocking socket linux
~/cpp$ ./connect 192.168.1.234 1234 kkkk
block mode: ubuntu 14.04 : time used:21.0.001053s
The connect timeout is about 21 seconds!
Note: If connect 127.x.x.x xxx kkkk will return immediately because the network card at the beginning of 127 is the network card itself. You can ping it and find that it is all connected and equal to127.0.0.1
#include #include #include #include #include #include #include #include #include #include #define BUFFER_SIZE 512 int setnonblocking( int fd ) < int old_option = fcntl( fd, F_GETFL ); int new_option = old_option | O_NONBLOCK; fcntl( fd, F_SETFL, new_option ); return old_option; >int main( int argc, char* argv[] ) < if( argc const char* ip = argv[1]; int port = atoi( argv[2] ); struct sockaddr_in server_address; bzero( &server_address, sizeof( server_address ) ); server_address.sin_family = AF_INET; inet_pton( AF_INET, ip, &server_address.sin_addr ); server_address.sin_port = htons( port ); int sock = socket( PF_INET, SOCK_STREAM, 0 ); assert( sock >= 0 ); int sendbuf = atoi( argv[3] ); int len = sizeof( sendbuf ); setsockopt( sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, sizeof( sendbuf ) ); getsockopt( sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, ( socklen_t* )&len ); printf( "the tcp send buffer size after setting is %d\n", sendbuf ); int old_option = fcntl( sock, F_GETFL ); printf("noblock: %d\n", old_option & O_NONBLOCK); //0-->block mode //int oldopt = setnonblocking(sock); set nonblock mode! struct timeval tv1, tv2; gettimeofday(&tv1, NULL); int ret = connect( sock, ( struct sockaddr* )&server_address, sizeof( server_address ) ); printf("connect ret code is: %d\n", ret); if ( ret == 0 ) < // printf("call getsockname . \n"); struct sockaddr_in local_address; socklen_t length; int ret = getpeername(sock, ( struct sockaddr* )&local_address, &length); assert(ret == 0); char local[INET_ADDRSTRLEN ]; printf( "local with ip: %s and port: %d\n", inet_ntop( AF_INET, &local_address.sin_addr, local, INET_ADDRSTRLEN ), ntohs( local_address.sin_port ) ); // char buffer[ BUFFER_SIZE ]; memset( buffer, 'a', BUFFER_SIZE ); send( sock, buffer, BUFFER_SIZE, 0 ); >else if (ret == -1) < gettimeofday(&tv2, NULL); suseconds_t msec = tv2.tv_usec - tv1.tv_usec; time_t sec = tv2.tv_sec - tv1.tv_sec; printf("time used:%d.%fs\n", sec, (double)msec / 1000000 ); printf("connect failed. \n"); if (errno == EINPROGRESS) < printf("unblock mode ret code. \n"); >> else < printf("ret code is: %d\n", ret); >printf("after connected!\n"); close( sock ); return 0; >
#include #include #include #include #include #include #include #include #include #include #include #include #include #define BUFFER_SIZE 1023 int setnonblocking( int fd ) < int old_option = fcntl( fd, F_GETFL ); int new_option = old_option | O_NONBLOCK; fcntl( fd, F_SETFL, new_option ); return old_option; >int unblock_connect( const char* ip, int port, int time ) < int ret = 0; struct sockaddr_in address; bzero( &address, sizeof( address ) ); address.sin_family = AF_INET; inet_pton( AF_INET, ip, &address.sin_addr ); address.sin_port = htons( port ); int sockfd = socket( PF_INET, SOCK_STREAM, 0 ); int fdopt = setnonblocking( sockfd ); ret = connect( sockfd, ( struct sockaddr* )&address, sizeof( address ) ); printf("connect ret code = %d\n", ret); if ( ret == 0 ) < printf( "connect with server immediately\n" ); fcntl( sockfd, F_SETFL, fdopt ); //set old optional back return sockfd; >//unblock mode --> connect return immediately! ret = -1 & errno=EINPROGRESS else if ( errno != EINPROGRESS ) < printf("ret = %d\n", ret); printf( "unblock connect failed!\n" ); return -1; >else if (errno == EINPROGRESS) < printf( "unblock mode socket is connecting. \n" ); >//use select to check write event, if the socket is writable, then //connect is complete successfully! fd_set readfds; fd_set writefds; struct timeval timeout; FD_ZERO( &readfds ); FD_SET( sockfd, &writefds ); timeout.tv_sec = time; //timeout is 10 minutes timeout.tv_usec = 0; ret = select( sockfd + 1, NULL, &writefds, NULL, &timeout ); if ( ret if ( ! FD_ISSET( sockfd, &writefds ) ) < printf( "no events on sockfd found\n" ); close( sockfd ); return -1; >int error = 0; socklen_t length = sizeof( error ); if( getsockopt( sockfd, SOL_SOCKET, SO_ERROR, &error, &length ) < 0 ) < printf( "get socket option failed\n" ); close( sockfd ); return -1; >if( error != 0 ) < printf( "connection failed after select with the error: %d \n", error ); close( sockfd ); return -1; >//connection successful! printf( "connection ready after select with the socket: %d \n", sockfd ); fcntl( sockfd, F_SETFL, fdopt ); //set old optional back return sockfd; > int main( int argc, char* argv[] ) < if( argc const char* ip = argv[1]; int port = atoi( argv[2] ); int sockfd = unblock_connect( ip, port, 10 ); if ( sockfd < 0 ) < printf("sockfd error! return -1\n"); return 1; >//shutdown( sockfd, SHUT_WR ); //disable read and write printf( "send data out\n" ); send( sockfd, "abc", 3, 0 ); shutdown( sockfd, SHUT_WR ); //disable read and write close(sockfd); return 0; >
After the socket is established, the default connect() function is in the blocking connection state. In most implementations, the timeout period of connect is between 75s and several minutes. If you want to shorten the timeout period, there are two ways to solve the problem: Method one, connect the socket The handle is set to a non-blocking state. Method two is to use a signal processing function to set blocking timeout control.
Call connect after a TCP socket is set to non-blocking, connect will immediately return an EINPROGRESS error, indicating that the connection operation is in progress, but it has not yet been completed; at the same time, the TCP three-way handshake operation continues; after this, we can call select to check whether the link is established successfully; non-blocking connect has three uses:
1. We can do some other processing during the three-way handshake. The connect operation takes a round trip time to complete, and it can be anywhere, from a few milliseconds of the local area network to a few hundred milliseconds or a few seconds of the wide area network. Here During a period of time, we may have some other processing we want to perform;
2. This technology can be used to establish multiple connections at the same time. It is very common in web browsers;
3. Since we use select to wait for the connection to complete, we can set a time limit for select to shorten the timeout of connect. In most implementations, the timeout of connect is between 75 seconds and several minutes. Sometimes The application wants a shorter timeout period, using non-blocking connect is one way;
Although non-blocking connect sounds simple, there are still some details to deal with:
1. Even if the socket is non-blocking, if the connected server is on the same host, the connection will usually be established immediately when calling connect to establish a connection. We must handle this situation;
2. The implementation derived from Berkeley (and Posix.1g) has two rules related to select and non-blocking IO:
A: When the connection is established successfully, the socket descriptor becomesWritable;
B: When the connection error occurs, the socket descriptor becomes both readable and writable;
note : When a socket fails, it will be marked as both readable and writable by the select call;
Non-blocking connect has so many benefits, but there are many portability problems when dealing with non-blocking connect;
Steps to handle non-blocking connect:
The first step: create a socket and return the socket descriptor;
Step 2: Call fcntl to set the socket descriptor to non-blocking;
The third step: call connect to start the connection;
Step 4: Determine whether the connection is successfully established;
A: If connect returns 0, it means the connection abbreviation is successful (this may happen when the server and the client are on the same machine);
B: Call select to wait for the connection establishment to complete successfully;
If select returns 0, it means that the connection is timed out; we return a timeout error to the user and close the connection to prevent the three-way handshake operation from continuing;
If select returns a value greater than 0, you need to check whether the socket descriptor is readable or writable; if the socket descriptor is readable or writable, we can call getsockopt to get the pending error on the socket (SO_ERROR ), if the connection is established successfully, this error value will be 0, if an error is encountered when establishing the connection, this value is the errno value corresponding to the connection error (for example: ECONNREFUSED, ETIMEDOUT, etc.).
«Error on reading socket» is the first portability problem encountered; If there is a problem, the implementation of getsockopt derived from Berkeley is to return 0, and the error waiting to be processed is returned in the variable errno; but Solaris will let getsockopt return -1, and errno is set as a pending error; we have to deal with both cases ;
In this way, when dealing with non-blocking connect, there are portability problems in platforms implemented by different sockets. First, it is possible that the connection has been successfully established before calling select, and the other party’s data has arrived. In this case When the connection is successful, the socket will be both readable and writable. This is the same as when the connection fails. At this time, we have to read the error value through getsockopt; this is the second portability problem;
Summary of portability issues :
1. For the error socket descriptor, the return value of getsockopt is derived from Berkeley’s implementation and returns 0, and the error value to be processed is stored in errno; while the implementation from Solaris is to return -1, and the error to be processed is stored in errno; (the return value of calls to getsockopt when the socket descriptor fails is not portable)
2. It is possible that the connection has been established successfully before calling select, and the other party’s data has arrived. In this case, the socket descriptor is both readable and writable; this is the same as when the socket descriptor is wrong. ; (how to judge whether the connection is established successfully, the conditions are not portable)
In this case, when we judge whether the connection is established successfully, the condition is not unique, we There are the following methods to solve this problem :
1. Call getpeername instead of getsockopt. If the call to getpeername fails, getpeername returns ENOTCONN, indicating that the connection establishment failed, and we must call getsockopt with SO_ERROR to get the pending error on the socket descriptor;
2. Call read to read data with a length of 0 bytes. If the read call fails, it means that the connection establishment has failed, and the errno returned by read indicates the reason for the connection failure. If the connection is established successfully, read should return 0;
3. Call connect again. It should fail. If the error errno is EISCONN, it means that the socket has been established, and the first connection is successful; otherwise, the connection has failed;
Interrupted connect :
If connect is called on a blocking socket and it is interrupted before the TCP three-way handshake operation is completed, for example, the captured signal is interrupted, what will happen? Assuming that the connect will not restart automatically, it will return EINTR Then, at this time, we can no longer call connect to wait for the connection to be established. If you call connect again to wait for the connection to be established, connect will return the error value EADDRINUSE. In this case, what should be done is to call select, Just like in non-blocking connect. Then, select returns when the connection is established successfully (making the socket descriptor writable) or when the connection establishment fails (making the socket descriptor both readable and writable);
Method two, define the signal processing function:
- sigset(SIGALRM, u_alarm_handler);
- alarm(2);
- code = connect(socket_fd, (struct sockaddr*)&socket_st, sizeof(struct sockaddr_in));
- alarm(0);
- sigrelse(SIGALRM);
First define an interrupt signal processing function u_alarm_handler for alarm processing after timeout, and then define a 2-second timer to execute connect. When the system connects successfully, the system executes normally; if the connect is unsuccessfully blocked here, it exceeds After the defined 2 seconds, the system will generate a signal to trigger the execution of the u_alarm_handler function. After the u_alarm_handler is executed, the program will continue to execute from the next line of connect.
Among them, the processing function can be defined as follows, or more error handling can be added.