为了将代码移植到iPhone等运行了iOS的设备上,我们不得不百般地顺从iOS系统的特殊性,Linux上普通的文件描述符(套接字)超时时间设置在iOS上无效,前面那篇文章“实现超时返回的gethostbyname函数”曾试过采用时钟与信号实现超时返回,但因为某些尚未查明的原因,加了信号与时钟以后,程序会莫名地崩溃,这促使我们使用最保险的select函数来实现超时返回。
原理很简单,先将connect要调用的文件描述符(套接字)设置为非阻塞模式,这样connect将不会阻塞而立刻返回,如果马上连接成功则返回0(这种情况几乎不会出现,除非网络状况超好),否则都返回-1,这时我们检查全局错误码errno,如果“errno == EINPROGRESS”则说明connect正在尝试连接,尚未返回结果,该情况下,我们将connect要调用的文件描述符(套接字)交给select函数去维护,在select指定的时间内,倘若connect连接成功了,select可以通过判断绑定的文件描述符(套接字)集合中是否可读或可写来确定connect是否连接成功了。倘若connect在指定的时间内仍未返回结果,则视为连接失败。如果errno是其他值则说明connect连接失败,利用perror函数打印出错误原因即可。不论connect是否成功,在函数返回之前最好将connect调用的文件描述符(套接字)设回阻塞模式。
int timeConnect( int sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout)
{
int no = 0, yes = 1;
int disconnected, fd_num;
struct timeval select_timeval;
fd_set readfds, writefds;
if (sockfd < 0 || NULL == addr || addrlen <= 0 || timeout < 0) return -1;
//Set Non-blocking
if (ioctl(sockfd, FIONBIO, &yes) < 0)
{
printf("ioctl: %s [%s:%d]\n", strerror(errno), __FILE__, __LINE__);
return -1;
}
disconnected = connect(sockfd, addr, addrlen);
if (disconnected)
{
if(errno != EINPROGRESS)
{
ioctl(sockfd, FIONBIO, &no);
return -1;
}
else
{
select_timeval.tv_sec = timeout;
select_timeval.tv_usec = 0;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_SET(sockfd,&readfds);
FD_SET(sockfd,&writefds);
fd_num = select(sockfd+1,&readfds,&writefds,NULL,&select_timeval);
if(fd_num < 0)
{
printf( "select: %s [%s:%d]\n", strerror(errno), __FILE__, __LINE__);
ioctl(sockfd, FIONBIO, &no);
return -1;
}
else if(0 == fd_num)
{
printf("connect time out\n");
ioctl(sockfd, FIONBIO, &no);
return -1;
}
}
}
return 0;
}
显然,上面的timeConnect函数比connect函数多一个timeout参数,它实现一个超时时间为timeout秒的connect函数,在指定时间内返回0表示连接成功,返回-1则表示连接失败或者连接超时。
除非注明,文章均为CppLive 编程在线原创,转载请注明出处,谢谢。



