网络大杂烩首页

网络大杂烩

  • 网站首页
  • HTML/CSS
  • javascript
  • seo优化
  • PS美工
  • 系统安全
  • 漏洞预警
  • 编程学习
  • 原创专区
  • 【网络大杂烩】是一家综合技术门户,为您提供网站建设,编程开发,安全维护,漏洞预警等技术文章

    技术文章MAP

    文章

    linux系统问题之不阻塞情况下报错:einprogress的解决办法

    日期:2019/12/9 14:30:00来源分类:系统安全

    Linux 非阻塞connect,错误码:EINPROGRESS  非阻塞情况下connect产生EINPROGRESS错误

            这是正确的。和windows不同的是,这里显示EINPROGRESS,windows应该是EWOULDBLOCK。感觉linux的这种表述更准确一些,确实是EINPROGRESS (正在处理),而EWOULDBLOCK应该是在网卡或者系统繁忙、无法及时处理数据。

    调用connect连接一般的超时时间是75s, 但是在程序中我们一般不希望等这么长时间采取采取动作。 可以在调用connect之前设置套接字非阻塞,然后调用connect,此时connect会立刻返回, 如果连接成功则直接返回0(成功), 如果没有连接成功,也会立即返回并且会设置errno为EINPROCESS,这并不是一个致命错误,仅仅是告知你已经在连接了,你只要判断是它就继续执行后面的逻辑就行了,比如select.通过select设置超时来达到为connect设定超时的目的. 下面的代码显示这个过程。

    bool timeout_connect(const string& _host, uint16_t _port, uint32t _timeout, int32_t& _sockfd)

    {
    #define CLOSE_SOCK_AND_RETURN_FALSE(so)    close(so); return false;

        if (_sockfd != -1) return true;
        
        int sockfd;
        struct sockaddr_in serv_addr;

        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(_port);
        serv_addr.sin_addr.s_addr = inet_addr(_host.c_str());

        if ((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)
        {
            return false;
        }
        // get origin sockfd flag
        int flags = fcntl(sockfd, F_GETFL);
        if (flags == -1)
        {
            CLOSE_SOCK_AND_RETURN_FALSE(sockfd);
        }
        
        // set sockfd to non-block mode
        int retcode = fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);
        if (retcode == -1)
        {
            CLOSE_SOCK_AND_RETURN_FALSE(sockfd);
        }

        if (::connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
        {
            if (errno == EINPROGRESS) // EINPROGRESS means connection is in progress, normally the socket connecting timeout is 75s. after the socket fd is ready to read.

                                                                // means the connecting of the socket fd is established.

            {
                int err;
                int len = sizeof(int);
                fd_set wds;
                struct timeval tm;
                tm.tv_sec = _timeout;
                tm.tv_usec = 0;
                FD_ZERO(&wds);
                FD_SET(sockfd, &wds);
                if (select(sockfd + 1, NULL, &wds, NULL, &tm) > 0)  // ">0" means sockfd ready to read, "=0" means timeout cause retrun, "<0" means error.
                {
                    retcode = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, (socklen_t *)&len);
                    if (retcode == -1 || err != 0)
                    {
                        CLOSE_SOCK_AND_RETURN_FALSE(sockfd);
                    }
                }
                else
                {
                    CLOSE_SOCK_AND_RETURN_FALSE(sockfd);
                }
            }
            else
            {
                CLOSE_SOCK_AND_RETURN_FALSE(sockfd);
            }
        }
        retcode = fcntl(sockfd, F_SETFL, flags);  // trun back the mode of sockfd to block.
        if (retcode == -1)
        {
            CLOSE_SOCK_AND_RETURN_FALSE(sockfd);
        }

        struct timeval readtimeout;
        readtimeout.tv_sec = _timeout;
        readtimeout.tv_usec = 0;
        retcode = setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (void *)&readtimeout, sizeof(readtimeout)); // set socket read timeout.
        if (retcode == -1)
        {
            CLOSE_SOCK_AND_RETURN_FALSE(sockfd);
        }

    #undef CLOSE_SOCK_AND_RETURN_FALSE
        _sockfd = sockfd;
        return true;
    }

    通过SO_RCVTIMEO 设置连接超时


    SO_RCVTIMEO和SO_SNDTIMEO套接口选项可以给套接口的读和写,来设置超时时间,

    一、在unix网络编程中,说是他们只能用于读和写,而像 accept和connect都不能用他们来设置.

    可是我在阅读内核源码的过程中看到,在linux中,accept和connect可以分别用 SO_RCVTIMEO和SO_SNDTIMEO套接口来设置超时,这里他们的超时时间也就是sock的sk_rcvtimeo和sk_sndtimeo 域.accept和connect的相关代码我前面都介绍过了,这里再提一下.其中accept的相关部分在inet_csk_accept中,会调用 sock_rcvtimeo来取得超时时间(如果是非阻塞则忽略超时间).而connect的相关代码在inet_stream_connect中通过调用sock_sndtimeo来取得超时时间(如果非阻塞则忽略超时时间).

    SO_RCVTIMEO和SO_SNDTIMEO ,它们分别用来设置socket接收数据超时时间和发送数据超时时间。
    因此,这两个选项仅对与数据收发相关的系统调用有效,这些系统调用包括:send, sendmsg, recv, recvmsg, accept, connect 。
    这两个选项设置后,若超时, 返回-1,并设置errno为EAGAIN或EWOULDBLOCK.
    其中connect超时的话,也是返回-1, 但errno设置为EINPROGRESS

      #include <stdio.h>  
        #include <stdlib.h>  
        #include <unistd.h>  
        #include <string.h>  
        #include <errno.h>  
        #include <assert.h>  
        #include <fcntl.h>  
        #include <sys/types.h>  
        #include <sys/socket.h>  
        #include <netinet/in.h>  
        #include <arpa/inet.h>  
          
        //超时连接  
        int timeout_connect(const char *ip, int port, int time);  
          
        int main(int argc, char **argv)  
        {  
            if (argc != 3) {  
                fprintf(stderr, "Usage: %s ip port\n", argv[0]);  
                return 1;  
            }  
              
            const char *ip = argv[1];  
            int port = atoi(argv[2]);  
              
            int sockfd = timeout_connect(ip, port, 10);  
            if (sockfd < 0)  
                return 1;  
              
              
            return 0;  
        }  
          
        int timeout_connect(const char *ip, int port, int time)  
        {  
            int ret = 0;  
            int error;  
              
            struct sockaddr_in address;  
            bzero(&address, sizeof(address));  
            address.sin_family = AF_INET;  
            address.sin_port = htons(port);  
            inet_pton(AF_INET, ip, &address.sin_addr);  
              
            int sockfd = socket(PF_INET, SOCK_STREAM, 0);  
            if (sockfd == -1)  
                return -1;  
              
            //超时时间  
            struct timeval timeout;  
            timeout.tv_sec = time;  
            timeout.tv_usec = 0;  
              
            socklen_t len = sizeof(timeout);  
            ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len);  
            if (ret == -1) {  
                error = errno;  
                while ((close(sockfd) == -1) && (errno == EINTR));  
                errno = error;  
                return -1;  
            }  
              
            ret = connect(sockfd, (struct sockaddr*)&address, sizeof(address));  
            if (ret == -1) {  
                if (errno == EINPROGRESS) {  
                    printf("connecting timeout\n");  
                    return -1;  
                }  
                  
                printf("error occur when connecting to server\n");  
                return -1;  
            }  
              
            char buffer[1024];  
            memset(buffer, '\0', 1024);  
            ret = recv(sockfd, buffer, 1024, 0);  
              
            printf("recv %d bytes, buf: %s\n", ret, buffer);  
              
            return sockfd;  
        } 

    当我们以非阻塞的方式来进行连接的时候,返回的结果如果是 -1,这并不代表这次连接发生了错误,如果它的返回结果是 EINPROGRESS,那么就代表连接还在进行中。 后面可以通过poll或者select来判断socket是否可写,如果可以写,说明连接完成了

    随机推荐

    • 该分类还没有添加任何内容!
    • 该分类还没有添加任何内容!

    Copyright 2005-2019 【网络大杂烩】 版权所有 黑ICP备16886888号

    声明:本站所有文章来自互联网 如有异议 请联系本站管理员