Linux 网络编程详解十一

/**
 * read_timeout - 读超时检测函数,不含读操作
 * @fd:文件描述符
 * @wait_seconds:等待超时秒数,如果为0表示不检测超时
 * 成功返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
 * */
int read_timeout(int fd, unsigned int wait_seconds)
{
    int ret = 0;
    if (wait_seconds > 0)
    {
        //定义文件描述符集合
        fd_set readfds;
        //清空文件描述符
        FD_ZERO(&readfds);
        //将当前文件描述符添加集合中
        FD_SET(fd, &readfds);
        //定义时间变量
        struct timeval timeout;
        timeout.tv_sec = wait_seconds;
        timeout.tv_usec = 0;
        do
        {
            ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
        } while (ret == -1 && errno == EINTR);
        //ret==-1时,返回的ret正好就是-1
        if (ret == 0)
        {
            errno = ETIMEDOUT;
            ret = -1;
        } else if (ret == 1)
        {
            ret = 0;
        }
    }
    return ret;
}
/**
 * write_timeout - 写超时检测函数,不含写操作
 * @fd:文件描述符
 * @wait_seconds:等待超时秒数,如果为0表示不检测超时
 * 成功返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
 * */
int write_timeout(int fd, unsigned int wait_seconds)
{
    int ret = 0;
    if (wait_seconds > 0)
    {
        //定义文件描述符集合
        fd_set writefds;
        //清空集合
        FD_ZERO(&writefds);
        //添加文件描述符
        FD_SET(fd, &writefds);
        //定义时间变量
        struct timeval timeout;
        timeout.tv_sec = wait_seconds;
        timeout.tv_usec = 0;
        do
        {
            ret = select(fd + 1, NULL, &writefds, NULL, &timeout);
        } while (ret == -1 && errno == EINTR);
        if (ret == 0)
        {
            errno = ETIMEDOUT;
            ret = -1;
        } else if (ret == 1)
        {
            ret = 0;
        }
    }
    return ret;
}
/**
 * accept_timeout - 带超时accept (方法中已执行accept)
 * @fd:文件描述符
 * @addr:地址结构体指针
 * @wait_seconds:等待超时秒数,如果为0表示不检测超时
 * 成功返回已连接的套接字,失败返回-1,超时返回-1并且errno = ETIMEDOUT
 * */
int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
    int ret = 0;
    if (wait_seconds > 0)
    {
        /*
         * 说明:accept和connect都会阻塞进程,accept的本质是从listen的队列中读一个连接,是一个读事件
         * 三次握手机制是由TCP/IP协议实现的,并不是由connect函数实现的,connect函数只是发起一个连接,
         * connect并非读写事件,所以只能设置connect非阻塞,而用select监测写事件(读事件必须由对方先发送报文,时间太长了)
         * 所以accept可以由select管理
         * 强调:服务端套接字是被动套接字,实际上只有读事件
         * */
        fd_set readfds;
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);
        struct timeval timeout;
        timeout.tv_sec = wait_seconds;
        timeout.tv_usec = 0;
        do
        {
            ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
        } while (ret == -1 && errno == EINTR);
        if (ret == -1)
        {
            return ret;
        } else if (ret == 0)
        {
            ret = -1;
            errno = ETIMEDOUT;
       return ret; }
//成功无需处理,直接往下执行 } //一旦检测出select有事件发生,表示有三次握手成功的客户端连接到来了 //此时调用accept不会被阻塞 if (addr != NULL) { socklen_t len = sizeof(struct sockaddr_in); ret = accept(fd, (struct sockaddr *) addr, &len); } else { ret = accept(fd, NULL, NULL); } return ret; }
/**
 * activate_nonblock - 设置套接字非阻塞
 * @fd:文件描述符
 * 成功返回0,失败返回-1
 * */
int activate_nonblock(int fd)
{
    int ret = 0;
    int flags = fcntl(fd, F_GETFL);
    if (flags == -1)
        return -1;
    flags = flags | O_NONBLOCK;
    ret = fcntl(fd, F_SETFL, flags);
    if (ret == -1)
        return -1;
    return ret;
}

/**
 * deactivate_nonblock - 设置套接字阻塞
 * @fd:文件描述符
 * 成功返回0,失败返回-1
 * */
int deactivate_nonblock(int fd)
{
    int ret = 0;
    int flags = fcntl(fd, F_GETFL);
    if (flags == -1)
        return -1;
    flags = flags & (~O_NONBLOCK);
    ret = fcntl(fd, F_SETFL, flags);
    if (ret == -1)
        return -1;
    return ret;
}

/**
 * connect_timeout - 带超时的connect(方法中已执行connect)
 * @fd:文件描述符
 * @addr:地址结构体指针
 * @wait_seconds:等待超时秒数,如果为0表示不检测超时
 * 成功返回0.失败返回-1,超时返回-1并且errno = ETIMEDOUT
 * */
int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
    int ret = 0;
    //connect()函数是连接服务器,本来connect会阻塞,但是设置未阻塞之后,
    //客户端仍然会三次握手机制,如果三次握手失败,那么客户端一定无法向文件描述符中写入数据
    //如果连接成功,那么客户端就可以向文件描述符写入数据了,
    //所以交给select监管的文件描述符如果可以写,说明连接成功,不可以写说明连接失败

    //设置当前文件描述符未阻塞--设置非阻塞之后,
    //connect在网络中非常耗时,所以需要设置成非阻塞,如果有读事件,说明可能连接成功
    //这样有利于做超时限制
    if (wait_seconds > 0)
    {
        if (activate_nonblock(fd) == -1)
            return -1;
    }
    ret = connect(fd, (struct sockaddr *) addr, sizeof(struct sockaddr));
    if (ret == -1 && errno == EINPROGRESS)
    {
        fd_set writefds;
        FD_ZERO(&writefds);
        FD_SET(fd, &writefds);
        struct timeval timeout;
        timeout.tv_sec = wait_seconds;
        timeout.tv_usec = 0;
        do
        {
            ret = select(fd + 1, NULL, &writefds, NULL, &timeout);
        } while (ret == -1 && errno == EINTR);
        //ret==-1 不需要处理,正好给ret赋值
        //select()报错,但是此时不能退出当前connect_timeout()函数
        //因为还需要取消文件描述符的非阻塞
        if (ret == 0)
        {
            errno = ETIMEDOUT;
            ret = -1;
        } else if (ret == 1)
        {
            //ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,
            //此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。
            int err = 0;
            socklen_t len = sizeof(err);
            ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len);
            if (ret == 0 && err != 0)
            {
                errno = err;
                ret = -1;
            }
            //说明套接字没有发生错误,成功
        }
    }
    if (wait_seconds > 0)
    {
        if (deactivate_nonblock(fd) == -1)
            return -1;
    }
    return ret;
}
原文地址:https://www.cnblogs.com/zhanggaofeng/p/6160490.html