在使用tcp的connect调用时,默认是使用阻塞方式,当服务器当前不可用时,connect会等待(内部在重试?)直到超时时间到达,而这个超时时间是系统内核规定的,不能使用setSocketOpt来设置。
在碰到服务器不可用,上层逻辑进行重试时,如果超时时间过长,会产生卡死的感觉,用户体验也不佳,所以需要控制connect的超时时间。
参考网络上的资料,这里使用select。实现方式是:将socket设置为非阻塞方式,使用select来轮询socket,在select里指定超时时间,根据socket来判断连接状态。最后恢复socket的阻塞方式。
代码如下(linux):
int connect_with_timeout(int socket, const struct sockaddr *address, socklen_t address_len, int time_out) { int flag, old_flag; old_flag = flag = fcntl(socket, F_GETFL, 0); flag |= O_NONBLOCK; fcntl(socket, F_SETFL, flag); int ret = -1; ret = ::connect(socket, (struct sockaddr*)address, address_len); if (ret != 0) { if (errno != EINPROGRESS) { LOG("connect failed,err(%d)", errno); } else { struct timeval tm; tm.tv_sec = time_out; tm.tv_usec = 0; fd_set set,rset; FD_ZERO(&set); FD_ZERO(&rset); FD_SET(socket, &set); FD_SET(socket, &rset); int res; res = ::select(socket+1, &rset, &set, NULL, &tm); if (res < 0) { LOG("select:network error in connect.errno:%d", errno); } else if(res == 0) { LOG("select:connect timeout.errno:%d", errno); } else if (res == 1) { if (FD_ISSET(socket, &set)) { LOG("select success"); ret = 0; } } else { LOG("other error when select: %s", strerror(errno)); } } } fcntl(socket, F_SETFL, old_flag); return ret; }
::connect在非阻塞模式下会立即返回,如果没有其他错误,返回值等于0。
当::connect不能立即建立连接时,会返回EINPROGRESS,表示正在连接的过程中,这时可以使用select去轮询套接口,而select超时时间由参数指定 。
select返回值小于0,表明connect出错;等于0,表明connect超时;等于1,并且套接口的状态是可写,则表明connect已经成功建立。
最后恢复socket的阻塞属性。
参考:
http://olive101.blog.163.com/blog/static/2051263201011221915696/