zoukankan      html  css  js  c++  java
  • 网络编程之非阻塞connect编写

    一、connect非阻塞编写

      TCP连接的建立涉及到一个三次握手的过程,且socket中connect函数需要一直等到客户接收到对于自己的SYN的ACK为止才返回,

    这意味着每 个connect函数总会阻塞其调用进程至少一个到服务器的RTT时间,而RTT波动范围很大,从局域网的几个毫秒到几百个毫秒甚至广域网上的几秒。

    这段 时间内,我们可以执行其他处理工作,以便做到并行。在此,需要用到非阻塞connect。

    将一个阻塞connect变为非阻塞大概有如下几步:

    第一步:将套接字设置为非阻塞模式

    设置套接字可以用ioctl()或者fcntl()

    (1)用ioctl设置

    1 /*创建套接字*/
    2 int sockfd =  socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    3     //.................
    4 /*step 1: 将套接字设置为非阻塞*/
    5  flags = 1;
    6  ioctl(sockfd, FIONBIO, &flags);

    (2)用fcntl函数设置

    1  #include <unistd.h>
    2  #include <fcntl.h>
    3 
    4  int fcntl(int fd, int cmd, ... /* arg */ );
    5 
    6   //int fcntl(int fd, int cmd, long arg);

    参数fd

    参数fd代表欲设置的文件描述符

    参数cmd

    参数cmd代表打算操作的指令
    有以下几种情况:
    F_DUPFD用来查找大于或等于参数arg的最小且仍未使用的文件描述符,并且复制参数fd的文件描述符。
    F_GETFD取得close-on-exec旗标。若此旗标的FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会关闭。
    F_SETFD 设置close-on-exec 旗标。该旗标以参数arg 的FD_CLOEXEC位决定。
    F_GETFL 取得文件描述符状态旗标,此旗标为open()的参数flags。
    F_SETFL 设置文件描述符状态旗标,参数arg为新旗标,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响。
    F_GETLK 取得文件锁定的状态。
    F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。
    F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。

    返回值

    fcntl的返回值与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列四个命令有特定返回值:

    F_DUPFD、F_GETFD、F_GETFL、F_GETOWN.

    第一个返回新的文件描述符,接下来的两个返回相应标志,最后一个返回一个正的进程ID或负的进程组ID。

    1 /*创建套接字*/
    2 int sockfd =  socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    3     //.................
    4 /*获取flags标志*/
    5 int flags;
    6 flags = fcntl(sockfd, F_GETFL, 0); 
    7 /*设置套接字为非阻塞*/
    8 fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

     第二步:调用connect连接判断返回值

     1 ret = connect(sockfd, addr, addrlen);
     2 if (ret == 0) {
     3     return TURE;
     4 }
     5 if (ret < 0 && errno == EINPROGRESS) {
     6          
     7     /*如果调用connect函数会之间返回-1(表示出错),且错误为EINPROGRESS,表示连接建立,建立启动但是尚未完成,*/
     8 } 
     9 else
    10     // connect error 

    实现非阻塞 connect ,首先把 sockfd 设置成非阻塞的。这样调用 connect 可以立刻返回,根据返回值和 errno 处理三种情况

    (1) 如果返回 0,表示 connect 成功。

    (2) 如果返回值小于 0, errno 为 EINPROGRESS, 表示连接建立已经启动但是尚未完成。这是期望的结果,不是真正的错误

    (3) 如果返回值小于0,errno 不是 EINPROGRESS,则连接出错了。

    建立connect连接,此时socket设置为非阻塞,connect调用后,无论连接是否建立立即返回-1,同时将errno(包含 errno.h就可以直接使用)设置为EINPROGRESS, 表示此时tcp三次握手仍旧进行,如果errno不是EINPROGRESS,则说明连接错误,程序结束。
    当客户端和服务器端在同一台主机上的时候,connect回马上结束,并返回0;无需等待,所以使用goto函数跳过select等待函数,直接进入连接后的处理部分

    第三步:将套接字添加到select集合中,判断是否可读可写 

    ret = connect(sockfd, addr, addrlen);
    if (ret < 0 && errno == EINPROGRESS) {
             
        /*如果调用connect函数会之间返回-1(表示出错),且错误为EINPROGRESS,表示连接建立,建立启动但是尚未完成;*/
            FD_ZERO(&connectfd); 
            FD_SET(0, &connectfd);
            FD_SET(sockfd, &connectfd);//将套接字加入到集合中
          /*设置超时时间*/
            timeout.tv_sec = 2;
            timeout.tv_usec = 0;
            if (select(sockfd+1, NULL, &connectfd, NULL, &timeout) > 0)
            {
                len = sizeof(uint32);
           /*获取error值,并进行判断*/
                getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, (socklen_t*)&len);
                if (err == 0)
                {
                    ret = TURE;
                }
                else
                    ret = ERR;
            }
            else
                ret = ERR;          
    }

      设置等待时间,使用select函数等待正在后台连接的connect函数,这里需要说明的是使用select监听socket描述符是否可读或者可写,如果只可写,说明连接成功,可以进行下面的操作。如果描述符既可读又可写,分为两种情况,第一种情况是socket连接出现错误,第二种情况是connect连接成 功,socket读缓冲区得到了远程主机发送的数据。需要通过connect连接后返回给errno的值来进行判定,或者通过调用 getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len); 函数返回值来判断是否发生错误.当网络发生错误的时候,getsockopt返回-1,return -1,程序结束。网络正常时候返回0,程序继续执行

    第四步:恢复套接字描述符状态为非阻塞,并返回 

    112 /* 恢复套接字非阻塞*/
    3  flags = 0;
    4 fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    5 
    627 /* 恢复套接字阻塞*/
    8 flags = 0;
    9  ioctl(sockfd, FIONBIO, &flags);

     下面是整个connect流程代码:

    (1) 创建socket,并利用fcntl将其设置为非阻塞

    (2) 调用connect函数,如果返回0,则连接建立;如果返回-1,检查errno ,如果值为 EINPROGRESS,则连接正在建立

    (3) 为了控制连接建立时间,将该socket描述符加入到select的可写集合中,采用select函数设定超时

    (4) 如果规定时间内成功建立,则描述符变为可写;否则,采用getsockopt函数捕获错误信息

    (5) 恢复套接字的文件状态并返回

     1 int Connect(uint32 sockfd, struct sockaddr *addr,  socklen_t addrlen)
     2 {
     3     struct timeval timeout;
     4     uint32 flags, len;
     5     int err, ret;
     6     fd_set connectfd;
     7 
     8     if (NULL == addr) 
     9         return ERR;
    10 
    11     /*step 1: 将套接字设置为非阻塞*/
    12     flags = 1;
    13     ioctl(sockfd, FIONBIO, &flags);
    14 
    15     /*step 2: 调用connect连接*/
    16     if ((ret = connect(sockfd, addr, addrlen)) < 0)
    17     {   
    18         if (errno != EINPROGRESS)
    19             ret =  ERR;
    20     
    21         FD_ZERO(&connectfd); 
    22         FD_SET(0, &connectfd);
    23         FD_SET(sockfd, &connectfd);
    24     
    25         timeout.tv_sec = 2;   
    26         timeout.tv_usec = 0;
    27         if (select(sockfd+1, NULL, &connectfd, NULL, &timeout) > 0)
    28         {   
    29             len = sizeof(uint32);
    30             getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, (socklen_t*)&len);
    31             if (err == 0)  
    32             {   
    33                 ret = TURE;
    34             }   
    35             else
    36                 ret = ERR;
    37         }   
    38         else
    39             ret = ERR;
    40     }   
    41     
    42   /*恢复套接字为阻塞模式*/
    43     flags = 0;
    44     ioctl(sockfd, FIONBIO, &flags);
    45 
    46     return ret;
    47 }
  • 相关阅读:
    leetcode 买卖股票的最佳时机3
    leetcode 买卖股票的最佳时机Ⅱ
    leetcode 最长有效括号
    C++中的%lld和%I64d区别
    Ural 1095 Nikifor 3 思维+同余性质的利用
    博弈基础
    ural 1091. Tmutarakan Exams
    容斥原理
    一些易错的地方
    codeforces911D Inversion Counting 求逆序数+小trick
  • 原文地址:https://www.cnblogs.com/wenqiang/p/5510367.html
Copyright © 2011-2022 走看看