使用select控制非阻塞connect()超时的示例

#include <stdio.h>

#include <sys/time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

static int tac_connect_server(int fd, struct sockaddr *addr, int dstlen, int timeout)
{
int ret = -1, err_num = 0, flags = 0;
struct timeval wr_time = {0,0};
fd_set write_set;
fd_set read_set;
int rc = 0, err_value = 0, err_len = sizeof(err_value);

flags = fcntl(fd, F_GETFL, 0);

/*设置非阻塞模式*/
fcntl(fd, F_SETFL, flags|O_NONBLOCK);
ret = connect(fd, addr, dstlen);
printf("LINE:%d,connect ret:%d ", __LINE__, ret);
if(0 != ret)
{
err_num = errno;
if(EINPROGRESS == err_num)
{
FD_ZERO(&write_set);
FD_ZERO(&read_set);
FD_SET(fd, &write_set);
FD_SET(fd, &read_set);
wr_time.tv_sec = timeout;
ret = select(fd+1, &read_set, &write_set, NULL, &wr_time);
err_num = errno;
printf("LINE:%d,select ret:%d ", __LINE__,ret);
if(0 >= ret)
{
printf("LINE:%d, connect fail,errno[%d]:%s ", __LINE__, err_num, strerror(err_num));
ret = -1;
}
else if(1 == ret && FD_ISSET(fd, &write_set))
{
printf("LINE:%d, connect sucess ", __LINE__);
ret = 0;
}
else if(2 == ret && FD_ISSET(fd, &write_set) && FD_ISSET(fd, &read_set))
{
/*getsockopt是必须的,因为1:在套接字连接成功可读、可写时返回2,并且读写被置位;2:套接字出错时同样会返回2,并且读写被置位。为区分1、2两种情况需调用getsockopt判断套接字是否出错。*/
rc = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err_value, &err_len);
/*同时判断返回值rc和错误码err_value原因:查看资料getsockopt目前有两种实现:1:如果发生错误,getsockopt在err_value中返回错误,但getsockopt本身返回值为0(Berkeley实现);2:getsockopt返回-1,并在err_value中返回错误(Solaris实现)。基于此只需判断err_value,但为安全考虑两个同时判断*/
if(0 == rc && 0 == err_value)
{
printf("LINE:%d, connect sucess ", __LINE__);
ret = 0;
}
else
{
printf("LINE:%d, connect fail,errno[%d]:%s ", __LINE__, err_num, strerror(err_num));
ret = -1;
}
}
else
{
printf("LINE:%d, connect fail,errno[%d]:%s ", __LINE__, err_num, strerror(err_num));
ret = -1;
}
}
else
{
printf("LINE:%d, connect fail,errno[%d]:%s ", __LINE__, err_num, strerror(err_num));
ret = -1;
}
}
else
{
printf("LINE:%d, connect sucess ", __LINE__);
ret = 0;
}
printf("LINE:%d, connect finish:ret:%d, fd:%d ", __LINE__, ret, fd);
fcntl(fd, F_SETFL, flags);
return ret;
}

static int tac_connect(char * dst, int port, int timeout)
{
int sockfd = 0;
struct sockaddr_in s;

printf("dst:%s, port:%d, timeout:%d ", dst, port, timeout);
if (port==0) port=49;

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
//if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
printf("LINE:%d, create socket fail ", __LINE__);
return -1;
}
#if 0
s.sin_addr.s_addr = htonl(INADDR_ANY);
s.sin_family = AF_INET;
s.sin_port = 0;
if (bind(sockfd, (struct sockaddr *)&s, sizeof(s)) < 0)
{
printf("LINE:%d, bind socket fail ", __LINE__);
close(sockfd);
return -1;
}
#endif
if ((s.sin_addr.s_addr = inet_addr(dst)) == 0xffffff)
{
printf("LINE:%d, inet_addr fail ", __LINE__);
close(sockfd);
return -1;
}
s.sin_family = AF_INET;
s.sin_port = htons(port);
if (tac_connect_server(sockfd, (struct sockaddr *)&s, sizeof(s), timeout) < 0) {
printf("LINE:%d, tac_connect_server fail ", __LINE__);
close(sockfd);
return -1;
}
printf("LINE:%d,connect sucess ", __LINE__);
close(sockfd);
return 0;
}

int main(int argc, char** argv)
{
int i=0;

if(argc < 4 )
{
printf("usag:test <dstip> <port> <timeout> ");
return -1;
}
for(i=0; i<4; i++)
{
if(tac_connect(argv[1], atoi(argv[2]), atoi(argv[3])) != 0)
{
printf("============================================ ");
printf("LINE:%d, trytimes:%d ", __LINE__, i);
printf("============================================ ");
continue;
}
else
{
printf("++++++++++++++++++++++++++++++++++++++++++ ");
printf("connect sucess, trytimes:%d ", i);
printf("++++++++++++++++++++++++++++++++++++++++++ ");
break;
}
}

return 0;
}

备注:

getsockopt处也可使用以下方式判断是否真正建立连接成功:
1、调用read,读取长度为0字节的数据.如果read调用失败,则表示连接建立失败,而且read返回的errno指明了连接失败的原因.如果连接建立成功,read应该返回0;
2、再调用一次connect.它应该失败,如果错误errno是EISCONN,就表示套接口已经建立,而且第一次连接是成功的;否则,连接就是失败的;

原文地址:https://www.cnblogs.com/wangliangblog/p/13262344.html