非阻塞connect的三个用途:
可以把三路握手叠加在其他处理上; 可以使用这个技术同时建立多个连接; 可以给select指定一个时间限制,使得能够缩短connect的超时。
当一个非阻塞的TCP套接字上调用connect时,connec将立即返回一个EINPROGRESS错误,不过已经发起的TCP三路握手继续进行。接着使用select/poll/epoll等检测这个连接或成功或失败的已建立条件。
尽管套接字连接是非阻塞的,如果连接到的服务器在同一个主机上,那么当调用connect时,连接通常立刻建立。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#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 serveraddr;
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
inet_pton(AF_INET, ip, &serveraddr.sin_addr);
int sockfd = socket( AF_INET, SOCK_STREAM, 0);
assert( sockfd >= 0 );
int fdopt = setnonblocking(sockfd);
ret = connect(sockfd, ( struct sockaddr *)&serveraddr, sizeof(serveraddr));
if( ret == 0 ) //立即建立了连接
{
printf("connect with server immediately\n");
fcntl(sockfd, F_SETFL, fdopt);
return sockfd;
}
else if( errno != EINPROGRESS) // 返回错误不是EINPROGRESS,则无法建立连接
{
printf("unblock connect not support\n");
return -1;
}
//没有立即建立连接,需要对套机字监听是否可写
fd_set writefds;
struct timeval timeout;
FD_ZERO( &writefds );
FD_SET( sockfd, &writefds);
timeout.tv_sec = time;
timeout.tv_usec = 0;
ret = select( sockfd+1, NULL, &writefds, NULL, &timeout );
if( ret <= 0) //超时或者select调用失败
{
printf("connection time out\n");
close( sockfd );
return -1;
}
if( !FD_ISSET( sockfd, &writefds ) ) //套接字不可写,则建立链接失败
{
printf("no events on sockfd found\n");
close( sockfd );
return -1;
}
//调用getsockopt检查套接字上是否存在待处理错误
//如果错误码是0则表示连接建立成功,否则连接建立失败
int error = 0;
socklen_t length = sizeof( error );
if( getsockopt( sockfd, SOL_SOCKET, SO_ERROR, &error, &length) < 0 )
{
printf("get sockfd 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;
}
printf("connection ready after select with the socket: %d\n", sockfd );
fcntl( sockfd, F_SETFL, fdopt );
return sockfd;
}
int main(int argc, char **argv)
{
if( argc <= 2 )
{
printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
return -1;
}
const char *ip = argv[1];
int port = atoi( argv[2] );
int sockfd = unblock_connect( ip, port, 10 );
if( sockfd < 0 )
{
return 1;
}
return 0;
}