• 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赋值。

  • 相关阅读:
    celery的使用
    DOM操作
    js动画
    列表案例
    背景案例
    背景属性连写
    背景属性
    链接导航案例
    链接伪类
    优先权之权重会叠加
  • 原文地址:https://www.cnblogs.com/huazhen/p/3421741.html
Copyright © 2020-2023  润新知