为了将代码移植到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 编程在线原创,转载请注明出处,谢谢。