网络编程之非阻塞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 }

 

posted @ 2016-05-20 13:28  zhangwju  阅读(2761)  评论(0编辑  收藏  举报