非阻塞socket调用connect, epoll和select检查连

系统 2063 0

非阻塞socket调用connect, epoll和select检查连接情况示例 - 语行 - 博客园

非阻塞socket调用connect, epoll和select检查连接情况示例

我们知道,linux下socket编程有常见的几个系统调用:

对于服务器来说, 有socket(), bind(),listen(), accept(),read(),write()

对于客户端来说,有socket(),connect()

这里主要要讲的是客户端这边的connect函数。

对于客户端来说,需要打开一个套接字,然后与对端服务器连接,例如:

            
               1
            
            
              int
            
             main(
            
              int
            
             argc, 
            
              char
            
             **
            
              argv) 

            
            
               2
            
            
              {

            
            
               3
            
            
              struct
            
            
               sockaddr_in s_addr;

            
            
               4
            
                     memset(&s_addr, 
            
              0
            
            , 
            
              sizeof
            
            
              (s_addr));

            
            
               5
            
                     s_addr.sin_family =
            
               AF_INET;

            
            
               6
            
                     s_addr.sin_addr.s_addr = inet_addr(
            
              "
            
            
              remote host
            
            
              "
            
            
              );

            
            
               7
            
                     s_addr.sin_port =
            
               htons(remote port);

            
            
               8
            
                     socklen_t addr_len = 
            
              sizeof
            
            (
            
              struct
            
            
               sockaddr);

            
            
               9
            
            
              int
            
             c_fd = socket(AF_INET, SOCK_STREAM, 
            
              0
            
            
              );

            
            
              10
            
            
              int
            
             ret = connect(c_fd, (
            
              struct
            
             sockaddr*)&
            
              s_addr, addr_len);                                 

            
            
              11
            
            
                      ......

            
            
              12
            
             }
          

 

当connect上对端服务器之后,就可以使用该套接字发送数据了。

我们知道,如果socket为TCP套接字, 则connect函数会激发TCP的三次握手过程,而三次握手是需要一些时间的, 内核中对 connect的 超时限制是 75 秒,就是说如果超过75秒则connect会由于超时而返回失败。但是如果对端服务器由于某些问题无法连接,那么每一个客户端发起的connect都会要等待75才会返回,因为socket默认是阻塞的。对于一些线上服务来说,假设某些对端服务器出问题了,在这种情况下就有可能引发严重的后果。 或者在有些时候,我们不希望在调用connect的时候阻塞住,有一些额外的任务需要处理;

这种场景下,我们就可以将socket设置为非阻塞,如下代码:

            
              int
            
             flags = fcntl(c_fd, F_GETFL, 
            
              0
            
            
              );

            
            
              if
            
            (flags < 
            
              0
            
            
              ) {
    
            
            
              return
            
            
              0
            
            
              ;      
}
fcntl(c_fd, F_SETFL, flags 
            
            | O_NONBLOCK);
          

当我们将socket设置为NONBLOCK后,在调用connect的时候,如果操作不能马上完成,那connect便会立即返回,此时connect有可能返回-1, 此时需要根据相应的错误码errno,来判断连接是否在继续进行。

当errno=EINPROGRESS时,这种情况是正常的,此时连接在继续进行, 但是仍未完成;同时TCP的三路握手操作继续进行; 后续只要用select/epoll去注册对应的事件并设置超时时间来判断连接否是连接成功就可以了。

            
              int
            
             ret = connect(c_fd, (
            
              struct
            
             sockaddr*)&
            
              s_addr, addr_len);

            
            
              while
            
            (ret < 
            
              0
            
            
              ) {
    
            
            
              if
            
            ( errno ==
            
               EINPROGRESS ) {
         
            
            
              break
            
            
              ;
    }  
            
            
              else
            
            
               {
         perror(
            
            
              "
            
            
              connect fail'\n
            
            
              "
            
            
              );
         
            
            
              return
            
            
              0
            
            
              ;
    }
}
            
          

这个地方,我们很可能会判断如果ret小于0,就直接判断连接失败而返回了,没有根据errno去判断EINPROGRESS这个错误码。这里也是昨天在写份程序的时候遇到的一个坑。

使用非阻塞 connect 需要注意的问题是:
1. 很可能 调用 connect 时会立即建立连接(比如,客户端和服务端在同一台机子上),必须处理这种情况。
2. Posix 定义了两条与 select 和 非阻塞 connect 相关的规定:
1)连接成功建立时,socket 描述字变为可写。(连接建立时,写缓冲区空闲,所以可写)
2)连接建立失败时,socket 描述字既可读又可写。 (由于有未决的错误,从而可读又可写)

不过我同时用epoll也做了实验(connect一个无效端口,errno=110, errmsg=connect refused),当连接失败的时候,会触发epoll的EPOLLERR与EPOLLIN,不会触发EPOLLOUT。

当用select检测连接时,socket既可读又可写,只能在可读的集合通过getsockopt获取错误码。

当用epoll检测连接时,socket既可读又可写,只能在EPOLLERR中通过getsockopt获取错误码。

完整代码如下:

            
              /*
            
            
               
 * File:   main.cpp
 * Created on March 7, 2013, 5:54 PM
 
            
            
              */
            
            
              

#include 
            
            <cstdlib>
            
              
#include 
            
            <
            
              string
            
            >
            
              
#include 
            
            <iostream>
            
              

#include 
            
            <sys/epoll.h>
            
              
#include 
            
            <sys/socket.h>
            
              
#include 
            
            <sys/types.h>
            
              
#include 
            
            <sys/
            
              select
            
            .h>
            
              
#include 
            
            <error.h>
            
              
#include 
            
            <errno.h>
            
              
#include 
            
            <fcntl.h>
            
              
#include 
            
            <netinet/
            
              in
            
            .h>
            
              
#include 
            
            <arpa/inet.h>



            
              using
            
            
              namespace
            
            
               std;


            
            
              struct
            
            
               so {
    
            
            
              int
            
            
               fd;
    
            
            
              string
            
            
               val;
};


            
            
              int
            
             select_version(
            
              int
            
             *
            
              fd) {
    
            
            
              int
            
             c_fd = *
            
              fd;
    fd_set rset, wset;
    
            
            
              struct
            
            
               timeval tval;
    FD_ZERO(
            
            &
            
              rset);
    FD_SET(c_fd, 
            
            &
            
              rset);
    wset 
            
            =
            
               rset;
    tval.tv_sec 
            
            = 
            
              0
            
            
              ;
    tval.tv_usec 
            
            = 
            
              300
            
             * 
            
              1000
            
            ; 
            
              //
            
            
              300毫秒
            
            
              int
            
            
               ready_n;
    
            
            
              if
            
             ((ready_n = 
            
              select
            
            (c_fd + 
            
              1
            
            , &rset, &wset, NULL, &tval)) == 
            
              0
            
            
              ) {
        close(c_fd); 
            
            
              /*
            
            
               timeout 
            
            
              */
            
            
              
        errno 
            
            =
            
               ETIMEDOUT;
        perror(
            
            
              "
            
            
              select timeout.\n
            
            
              "
            
            
              );
        
            
            
              return
            
             (-
            
              1
            
            
              );
    }
    
            
            
              if
            
             (FD_ISSET(c_fd, &
            
              rset)) {
        
            
            
              int
            
            
               error;
        socklen_t len 
            
            = 
            
              sizeof
            
            
               (error);
        
            
            
              if
            
             (getsockopt(c_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 
            
              0
            
            
              ) {
            cout 
            
            << 
            
              "
            
            
              getsockopt error.
            
            
              "
            
             <<
            
               endl;
            
            
            
              return
            
             -
            
              1
            
            
              ;
        }
        cout 
            
            << 
            
              "
            
            
              in fire.
            
            
              "
            
             << error <<
            
               endl;
    }
    
            
            
              if
            
             (FD_ISSET(c_fd, &
            
              wset)) {
        
            
            
              int
            
            
               error;
        socklen_t len 
            
            = 
            
              sizeof
            
            
               (error);
        
            
            
              if
            
             (getsockopt(c_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 
            
              0
            
            
              ) {
            cout 
            
            << 
            
              "
            
            
              getsockopt error.
            
            
              "
            
             <<
            
               endl;
            
            
            
              return
            
             -
            
              1
            
            
              ;
        }
        cout 
            
            << 
            
              "
            
            
              out fire.
            
            
              "
            
             << error <<
            
               endl;
    }
    
            
            
              return
            
            
              0
            
            
              ;
}


            
            
              int
            
             epoll_version(
            
              int
            
             *
            
              fd) {
    
            
            
              int
            
             c_fd = *
            
              fd;
    
            
            
              int
            
             ep = epoll_create(
            
              1024
            
            
              );
    
            
            
              struct
            
             epoll_event 
            
              event
            
            
              ;
    
            
            
              event
            
            .events = (uint32_t) (EPOLLIN | EPOLLOUT |
            
               EPOLLET);
    
            
            
              struct
            
            
               so _data;
    _data.fd 
            
            =
            
               c_fd;
    _data.val 
            
            = 
            
              "
            
            
              test
            
            
              "
            
            
              ;
    
            
            
              event
            
            .data.ptr = (
            
              void
            
            *) &
            
              _data;
    epoll_ctl(ep, EPOLL_CTL_ADD, c_fd, 
            
            &
            
              event
            
            
              );
    
            
            
              struct
            
             epoll_event eventArr[
            
              1000
            
            
              ];
    
            
            
              int
            
            
               status, err;
    socklen_t len;
    err 
            
            = 
            
              0
            
            
              ;
    len 
            
            = 
            
              sizeof
            
            
               (err);
    
            
            
              int
            
             n = epoll_wait(ep, eventArr, 
            
              20
            
            , 
            
              300
            
            
              );
    
            
            
              for
            
             (
            
              int
            
             i = 
            
              0
            
            ; i < n; i++
            
              ) {
        epoll_event ev 
            
            =
            
               eventArr[i];
        
            
            
              int
            
             events =
            
               ev.events;
        
            
            
              if
            
             (events &
            
               EPOLLERR) {
            
            
            
              struct
            
             so* so_data = (
            
              struct
            
             so*
            
              ) ev.data.ptr;
            cout 
            
            << so_data->val << 
            
              "
            
            
              ,err event fire.
            
            
              "
            
             <<
            
               endl;
            status 
            
            = getsockopt(c_fd, SOL_SOCKET, SO_ERROR, &err, &
            
              len);
            cout 
            
            << status << 
            
              "
            
            
              ,
            
            
              "
            
             << err <<
            
               endl;
        }
        
            
            
              if
            
             (events &
            
               EPOLLIN) {
            
            
            
              struct
            
             so* so_data = (
            
              struct
            
             so*
            
              ) ev.data.ptr;
            cout 
            
            << so_data->val << 
            
              "
            
            
              ,in event fire.
            
            
              "
            
             <<
            
               endl;
            status 
            
            = getsockopt(c_fd, SOL_SOCKET, SO_ERROR, &err, &
            
              len);
            cout 
            
            << status << 
            
              "
            
            
              ,
            
            
              "
            
             << err <<
            
               endl;
        }
        
            
            
              if
            
             (events &
            
               EPOLLOUT) {
            
            
            
              struct
            
             so* so_data1 = (
            
              struct
            
             so*
            
              ) ev.data.ptr;
            cout 
            
            << so_data1->val << 
            
              "
            
            
              ,out event fire.
            
            
              "
            
             <<
            
               endl;
        }
    }

}


            
            
              int
            
             main(
            
              int
            
             argc, 
            
              char
            
            **
            
               argv) {
    
            
            
              string
            
             ip = 
            
              "
            
            
              127.0.0.1
            
            
              "
            
            
              ;
    
            
            
              int
            
             port = 
            
              25698
            
            
              ;
    
            
            
              int
            
            
               c_fd, flags, ret;
    
            
            
              struct
            
            
               sockaddr_in s_addr;
    memset(
            
            &s_addr, 
            
              0
            
            , 
            
              sizeof
            
            
               (s_addr));
    s_addr.sin_family 
            
            =
            
               AF_INET;
    s_addr.sin_port 
            
            =
            
               htons(port);
    s_addr.sin_addr.s_addr 
            
            =
            
               inet_addr(ip.c_str());

    
            
            
              if
            
             ((c_fd = socket(AF_INET, SOCK_STREAM, 
            
              0
            
            )) < 
            
              0
            
            
              ) {
        perror(
            
            
              "
            
            
              create socket fail.\n
            
            
              "
            
            
              );
        exit(
            
            
              0
            
            
              );
    }
    flags 
            
            = fcntl(c_fd, F_GETFL, 
            
              0
            
            
              );
    
            
            
              if
            
             (flags < 
            
              0
            
            
              ) {
        perror(
            
            
              "
            
            
              get socket flags fail.\n
            
            
              "
            
            
              );
        
            
            
              return
            
             -
            
              1
            
            
              ;
    }

    
            
            
              if
            
             (fcntl(c_fd, F_SETFL, flags | O_NONBLOCK) < 
            
              0
            
            
              ) {
        perror(
            
            
              "
            
            
              set socket O_NONBLOCK fail.\n
            
            
              "
            
            
              );
        
            
            
              return
            
             -
            
              1
            
            
              ;
    }
    ret 
            
            = connect(c_fd, (
            
              struct
            
             sockaddr*) &s_addr, 
            
              sizeof
            
             (
            
              struct
            
            
               sockaddr));
    
            
            
              while
            
             (ret < 
            
              0
            
            
              ) {
        
            
            
              if
            
             (errno ==
            
               EINPROGRESS) {
            
            
            
              break
            
            
              ;
        } 
            
            
              else
            
            
               {
            perror(
            
            
              "
            
            
              connect remote server fail.\n
            
            
              "
            
            
              );
            printf(
            
            
              "
            
            
              %d\n
            
            
              "
            
            
              , errno);
            exit(
            
            
              0
            
            
              );
        }
    }
    
            
            
              //
            
            
              select_version(&c_fd);
            
            
    epoll_version(&
            
              c_fd);
    
            
            
              return
            
            
              0
            
            
              ;
}
            
          

 

ps:文中如有不妥的地方,望各位博友指正。

 

非阻塞socket调用connect, epoll和select检查连接情况示例


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论