zoukankan      html  css  js  c++  java
  • socket基础函数(2)

    http://www.cnblogs.com/RascallySnake/archive/2013/07/11/3185071.html
     
    一、select 

    winsock中 #include <winsock.h>

    原型

    int   select( 
    int   nfds ,
    fd_set*   readfds ,
    fd_set*   writefds ,
    fd_set*   exceptfds ,
    const struct timeval*   timeout 
    );

    nfds:本参数忽略,仅起到兼容作用。
        readfds:(可选)指针,指向一组等待可读性检查的套接口。
        writefds:(可选)指针,指向一组等待可写性检查的套接口。
        exceptfds:(可选)指针,指向一组等待错误检查的套接口。
        timeout:select()最多等待时间,对阻塞操作则为NULL。

    注释:
        本函数用于确定一个或多个套接口的状态。对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息。

        用fd_set结构来表示一组等待检查的套接口。 在调用返回时,这个结构存有满足一定条件的套接口组的子集,并且select()返回满足条件的套接口的数目。

        有一组宏可用于对fd_set的操作,这些宏与Berkeley Unix软件中的兼容,但内部的表达是完全不同的。

    参数:

       readfds:标识等待可读性检查的套接口。如果该套接口正处于监听listen()状态,则若有连接请求到达,该套接口便被标识为可读,这样一个accept()调用保证可以无阻塞完成。

    对其他套接口而言,可读性意味着有排队数据供读取。

    或者对于SOCK_STREAM类型套接口来说,相对于该套接口的虚套接口已关闭,于是recv()或recvfrom()操作均能无阻塞完成。

    如果虚电路被“优雅地”中止,则recv()不读取数据立即返回;如果虚电路被强制复位,则recv()将以WSAECONNRESET错误立即返回。如果SO_OOBINLINE选项被设置,则将检查带外数据是否存在(参见setsockopt())。
        writefds:标识等待可写性检查的套接口。如果一个套接口正在connect()连接(非阻塞),可写性意味着连接顺利建立。如果套接口并未处于connect()调用中,可写性意味着send()和sendto()调用将无阻塞完成。〔但并未指出这个保证在多长时间内有效,特别是在多线程环境中〕。
        exceptfds:标识等待带外数据存在性或意味错误条件检查的套接口。请注意如果设置了SO_OOBINLINE选项为假FALSE,则只能用这种方法来检查带外数据的存在与否。对于SO_STREAM类型套接口,远端造成的连接中止和KEEPALIVE错误都将被作为意味出错。如果套接口正在进行连接connect()(非阻塞方式),则连接试图的失败将会表现在exceptfds参数中。
        如果对readfds、writefds或exceptfds中任一个组类不感兴趣,可将它置为空NULL。
        在winsock.h头文件中共定义了四个宏来操作描述字集。FD_SETSIZE变量用于确定一个集合中最多有多少描述字(FD_SETSIZE缺省值为64,可在包含winsock.h前用#define FD_SETSIZE来改变该值)。对于内部表示,fd_set被表示成一个套接口的队列,最后一个有效元素的后续元素为INVAL_SOCKET。宏为:
        FD_CLR(s,*set):从集合set中删除描述字s。
        FD_ISSET(s,*set):若s为集合中一员,非零;否则为零。
        FD_SET(s,*set):向集合添加描述字s。
        FD_ZERO(*set):将set初始化为空集NULL。
        timeout: 控制select()完成的时间。若timeout参数为空指针,则select()将一直阻塞到有一个描述字满足条件。否则的话,timeout指向一个timeval结构,其中指定了select()调用在返回前等待多长时间。如果timeval为{0,0},则select()立即返回,这可用于探询所选套接口的状态。如果处于这种状态,则select()调用可认为是非阻塞的,且一切适用于非阻塞调用的假设都适用于它。举例来说,阻塞钩子函数不应被调用,且WINDOWS套接口实现不应yield。

    设置了timeout的值之后呢,select在没有文件描述符监视可用的情况下,会等待这个timeout的时间,时间到了select返回0

    如果timeout超时之前有文件描述符可用,则返回可用的数量,这时候的timeout则会依然计数,因此如果想要每次都超时一定的时间那么在slelect返回>0的值之后要重新装填timeout的值一次。以保证超时时间没有变化。

    如果tv_sec和tv_usec都是0,那么就是超时时间为0,那么select就会立刻返回了。

    如果timeout这里是个NULL,那么超时就未被启用,会一直阻塞在监视文件描述符的地方。

    struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

    &timeout=NULL是传进一个空指针,表示永远等待 
    timeout=0是传进一个timeout,其值为0,系统检查所有的fdset,然后立即返回 
    你传进一个0然后不断的查询,就和传进一个NULL效果一样了 
    不过就是巨耗cpu 


    返回值:
        select()调用返回处于就绪状态并且已经包含在fd_set结构中的描述字总数;如果超时则返回0;否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。

    错误代码:
        WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。
        WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。
        WSAEINVAL:超时时间值非法。
        WSAEINTR:通过一个WSACancelBlockingCall()来取消一个(阻塞的)调用。
        WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。
        WSAENOTSOCK:描述字集合中包含有非套接口的元素。

    select()函数主要是建立在fd_set类型的基础上的。fd_set(它比较重要所以先介绍一下)是一组文件描述字(fd)的集合,它用一位来表示一个fd(下面会仔细介绍),对于fd_set类型通过下面四个宏来操作: 

    fd_set set;

    FD_ZERO(&set);      

    FD_SET(fd, &set);   

    FD_CLR(fd, &set);   

    FD_ISSET(fd, &set);        

    过去,一个fd_set通常只能包含<32的fd(文件描述字),因为fd_set其实只用了一个32位矢量来表示fd;现在,UNIX系统通常会在头文件<sys/select.h>中定义常量FD_SETSIZE,它是数据类型fd_set的描述字数量,其值通常是1024,这样就能表示<1024的fd。根据fd_set的位矢量实现,我们可以重新理解操作fd_set的四个宏: 

    fd_set set;

    FD_ZERO(&set);     

    FD_SET(0, &set);   

    FD_CLR(4, &set);     

    FD_ISSET(5, &set);   

    ―――――――――――――――――――――――――――――――――――――――

    注意fd的最大值必须<FD_SETSIZE。

    ――――――――――――――――――――――――――――――――――――――― 

    select函数的接口比较简单:

    int select(int nfds, fd_set *readset, fd_set *writeset,

    fd_set* exceptset, struct tim *timeout); 

    功能:

    测试指定的fd可读?可写?有异常条件待处理?     

    参数:

    nfds: 需要检查的文件描述字个数(即检查到fd_set的第几位),数值应该比三组fd_set中所含的最大fd

           值更大,一般设为三组fd_set中所含的最大fd值加1(如在readset,writeset,exceptset中所含最大

           的fd为5,则nfds=6,因为fd是从0开始的)。设这个值是为提高效率,使函数不必检查fd_set的所

           有1024位。

    readset:  用来检查可读性的一组文件描述字。

    writeset: 用来检查可写性的一组文件描述字。

    exceptset:用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)

    timeout:有三种可能:

             1:timeout=NULL(阻塞:直到有一个fd位被置为1函数才返回)

             2:timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数

                均返回)

             3. timeout所指向的结构,时间设为0(非阻塞:函数检查完每个fd后立即返回) 

    返回值:     

    返回对应位仍然为1的fd的总数。 

    Remarks:

    三组fd_set均将某些fd位置0,只有那些可读,可写以及有异常条件待处理的fd位仍然为1。

    使用select函数的过程一般是:

    先调用宏FD_ZERO将指定的fd_set清零,然后调用宏FD_SET将需要测试的fd加入fd_set,接着调用函数  

    select测试fd_set中的所有fd,最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1。 

    以下是一个测试单个文件描述字可读性的例子:

    int isready(int fd)

    {

        int rc;

        fd_set fds;

        struct tim tv;    

        FD_ZERO(&fds);

        FD_SET(fd,&fds);

        tv.tv_sec = tv.tv_usec = 0;    

        rc = select(fd+1, &fds, NULL, NULL, &tv);

        if (rc < 0)   //error

            return -1;    

        return FD_ISSET(fd,&fds) ? 1 : 0;

     }

    下面还有一个复杂一些的应用:

    //这段代码将指定测试Socket的描述字的可读可写性,因为Socket使用的也是fd

    uint32 SocketWait(TSocket *s,bool rd,bool wr,uint32 timems)    

    {

         fd_set rfds,wfds;

     #ifdef _WIN32

         TIM tv;

     #else

         struct tim tv;

     #endif    

         FD_ZERO(&rfds);

         FD_ZERO(&wfds); 

         if (rd)   //TRUE

         FD_SET(*s,&rfds);   //添加要测试的描述字 

         if (wr)     //FALSE

           FD_SET(*s,&wfds); 

         tv.tv_sec=timems/1000;     //second

         tv.tv_usec=timems00;     //ms 

         for (;;) //如果errno==EINTR,反复测试缓冲区的可读性

              switch(select((*s)+1,&rfds,&wfds,NULL,

                  (timems==TIME_INFINITE?NULL:&tv)))  //测试在规定的时间内套接口接收缓冲区中是否有数据可读

             {                                              //0--超时,-1--出错

             case 0:    

                  return 0; 

             case (-1):   

                  if (SocketError()==EINTR)

                       break;              

                  return 0; //有错但不是EINTR 

              default:

                  if (FD_ISSET(*s,&rfds)) //如果s是fds中的一员返回非0,否则返回0

                       return 1;

                  if (FD_ISSET(*s,&wfds))

                       return 2;

                  return 0;

             };

    }

    Q&A:

    和select模型紧密结合的四个宏:

    FD_CLR(int fd, fd_set *set);
    FD_ISSET(int fd, fd_set *set);
    FD_SET(int fd, fd_set *set);
    FD_ZERO(fd_set *set);


    理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
    (1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。
    (2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
    (3)若再加入fd=2,fd=1,则set变为0001,0011
    (4)执行select(6,&set,0,0,0)阻塞等待
    (5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空

    基于上面的讨论,可以轻松得出select模型的特点:
    (1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。对调整fd_set的大小可参考http://www.cppblog.com/CppExplore/archive/2008/03/21/45061.html中的模型2,可以有效突破select可监控的文件描述符上限。
    (2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。
    (3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有事件发生)。

    另外,如果select调用中设置了等待时间,那么每次调用时都需要重新对这个时间赋值么?就像对fd_set处理一样。
    例如:
    fd_set readfd;
    struct timval tv;
    while(1) {
      FD_ZERO(&readfd);
      FD_SET(fd, &readfd);
      tv.tv_sec = 2;
      tv.tv_usec = 0;
      select(maxfd+1, &readfd, NULL, NULL, &tv);
      ......;
    }

    如上代码,对fd_set需要每次调用都要重新设置,那么对tv来说是否也是一样呢?能不能把对tv的赋值放在while外面?

    答案是不行,如果将时间的初始化放在外边,时间初始化为2秒,假设在1秒后发上了事件,则select将会返回并将tv的时间变成上次阻塞的剩余时间,即1秒,然后再进行监视套接字。这是因为linux系统对select()的实现中会修改参数tv为剩余时间。所以对于select函数中的最后一个参数,需要在循环中设置,每次循环要重新设置。如果设在循环外面,当循环执行起来后,每次循环select都会修改tv的值,tv的值越来越小,导致最后会产生select函数这tv时间内收不到有效时间,而返回-1,造成错误。

    socket 可读:

    1. 接收缓冲区有数据,一定可读
    2. 对方正常关闭socket,也是可读
    3. 对于侦听socket,有新链接到达也可读

  • 相关阅读:
    eclipse中的debug模式的使用
    Hibernate快速入门
    鸟哥的 Linux 私房菜Shell Scripts篇(四)
    Vim命令图解及快捷键讲解
    SpringBoot文档
    鸟哥的 Linux 私房菜Shell Scripts篇(三)
    鸟哥的 Linux 私房菜Shell Scripts篇(二)
    (二)IDEA使用,快捷键
    (一)IDEA使用,基础配置
    idea-git同步服务器代码后撤销操作
  • 原文地址:https://www.cnblogs.com/jacklikedogs/p/3976204.html
Copyright © 2011-2022 走看看