linux Socket : 实现非阻塞的connect

非阻塞模式有3种用途
        1.三次握手同时做其他的处理。connect要花一个往返时间完成,从几毫秒的局域网到几百毫秒或几秒的广域网。这段时间可能有一些其他的处理要执行,比如数据准备,预处理等。
        2.用这种技术建立多个连接。这在web浏览器中很普遍.
        3.由于程序用select等待连接完成,可以设置一个select等待时间限制,从而缩短connect超时时间。多数实现中,connect的超时时 间在75秒到几分钟之间。有时程序希望在等待一定时间内结束,使用非阻塞connect可以防止阻塞75秒,在多线程网络编程中,尤其必要。   例如有一个通过建立线程与其他主机进行socket通信的应用程序,如果建立的线程使用阻塞connect与远程通信,当有几百个线程并发的时候,由于网 络延迟而全部阻塞,阻塞的线程不会释放系统的资源,同一时刻阻塞线程超过一定数量时候,系统就不再允许建立新的线程(每个进程由于进程空间的原因能产生的 线程有限),如果使用非阻塞的connect,连接失败使用select等待很短时间,如果还没有连接后,线程立刻结束释放资源,防止大量线程阻塞而使程 序崩溃。
目前connect非阻塞编程的普遍思路是:
在一个TCP套接口设置为非阻塞后,调用connect,connect会在系统提供的errno变量中返回一个EINRPOCESS错误,此时TCP的三路握手继续进行。之后可以用select函数检查这个连接是否建立成功。

 1 int ConnectNonb(int sockfd, struct sockaddr *saptr, socklen_t salen, int nsec)
 2 {
 3     int flags, n, error;
 4     socklen_t len;
 5     fd_set rset, wset;
 6     struct timeval tval;
 7  
 8     flags = fcntl(sockfd, F_GETFL, 0);
 9     fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
10     errno = 0;
11  
12     if ((n = connect(sockfd, saptr, salen)) < 0)
13     {
14         if (errno != EINPROGRESS)
15         {
16             return EXIT_FAILURE;
17         }
18     }
19     // 可以做任何其它的操作
20     if (n == 0)
21     {
22         goto done; // 一般是同一台主机调用,会返回 0
23     }
24     FD_ZERO(&rset);
25     FD_SET(sockfd, &rset);
26     wset = rset; // 这里会做 block copy
27     tval.tv_sec = nsec;
28     tval.tv_usec = 0;
29  
30     // 如果nsec 为0,将使用缺省的超时时间,即其结构指针为 NULL
31     // 如果tval结构中的时间为0,表示不做任何等待,立刻返回
32     if ((n = select(sockfd + 1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0)
33     {
34         close(sockfd);
35         errno = 10;
36         return EXIT_FAILURE;
37     }
38     if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset))
39     {
40         len = sizeof(error);// 如果连接成功,此调用返回 0
41         if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
42             return EXIT_FAILURE;
43     }
44     else
45     {
46         printf("select error: sockfd not set");
47     }
48     done: fcntl(sockfd, F_SETFL, flags); // 恢复socket 属性
49     if (error)
50     {
51         close(sockfd);
52         errno = error;
53         return EXIT_FAILURE;
54     }
55  
56     return EXIT_SUCCESS;
57 }

   1.首先填写套接字结构,包括远程的ip,通信端口

   2.建立socket套接字

   3.将socket建立为非阻塞,此时socket被设置为非阻塞模式
  flags = fcntl(sockfd,F_GETFL,0);//获取建立的sockfd的当前状态(非阻塞)
  fcntl(sockfd,F_SETFL,flags|O_NONBLOCK);//将当前sockfd设置为非阻塞

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

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

     6. 用select查看接收描述符,如果可读,就读出数据,程序结束。在接收数据的时候注意要先对先前的rset重新赋值为描述符,因为select会对 rset清零,当调用select后,如果socket没有变为可读,则rset在select会被置零。所以如果在程序中使用了rset,最好在使用时 候重新对rset赋值。

原文地址:https://www.cnblogs.com/huazhen/p/3421741.html