socket使用非阻塞connect

在使用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/

posted @ 2015-08-13 09:53  onemuji  阅读(4378)  评论(0编辑  收藏  举报