网络编程学习——I/O复用(三)
网络编程学习——I/O复用(三)
thanatos_y 发表于2年前
网络编程学习——I/O复用(三)
  • 发表于 2年前
  • 阅读 9
  • 收藏 0
  • 点赞 1
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

摘要: 通过《UNIX网络编程卷1:套接字联网API》学习网络编程

1.10 poll函数

  poll提供的功能与select类似,不过在处理流设备时,它能够提供额外的信息。

/* Poll the file descriptors described by the NFDS structures starting at
   FDS.  If TIMEOUT is nonzero and not -1, allow TIMEOUT milliseconds for
   an event to occur; if TIMEOUT is -1, block until an event occurs.
   Returns the number of file descriptors with events, zero if timed out,
   or -1 for errors.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int poll (struct pollfd *__fds, nfds_t __nfds, int __timeout);

  第一个参数是指向一个结构数组第一个元素的指针。每个数组元素都是一个pollfd,用于指定测试某个给定描述符fd的条件。

/* Data structure describing a polling request.  */
struct pollfd
  {
    int fd;            /* File descriptor to poll.  */
    short int events;        /* Types of events poller cares about.  */
    short int revents;        /* Types of events that actually occurred.  */
  };

  要测试的条件由event成员指定,函数在相应的revents成员中返回该描述符的状态。(每个描述符都有两个变量,一个为调用值,另一个为返回结果,从而避免使用值-结果参数。回想select函数的中间三个参数都使值-结果参数)这两个成员中的每一个都由指定某个特定条件的一位或多位构成。图1-19列出了用于指定events标志以及测试revents标志的一些常值。

图1-19 poll函数的输入events和返回revents

  我们将该图分为三个部分:第一部分是处理输入的四个常值,第二部分是处理输出的三个常值,第三部分是处理错误的三个常值。其中第三部分的三个常值不能在events中设置,但是当相应条件存在时就在revents中返回。

  poll识别三类数据:普通(normal)、优先级带(priority band)和高优先级(high priority)。这些术语均出自基于流的实现。

  就TCP和UDP套接字而言,以下条件引起poll返回特定的revent。不幸的是POSIX在其poll的定义中留了许多空洞(也就是说有多种方法可返回相同的条件)。

  • 所有正规TCP数据和所有UDP数据都被认为是普通数据。

  • TCP的带外数据被认为是优先级带数据。

  • 当TCP连接的读半部关闭时(譬如收到了一个来自对端的FIN),也被认为是普通数据,随后的读操作将返回0。

  • TCP连接存在错误即可认为是普通数据,也可认为是错误(POLLERR)。无论哪种情况,随后的读操作将返回-1,并把errno设置成合适的值。这可用于处理诸如接收到RST或发生超时等条件。

  • 在监听套接字上有新的连接可用既可认为是普通数据,也可认为是优先级数据。大多数实现视之为普通数据。

  • 非阻塞式connect的完成被认为是使相应套接字可写。

  结构数组中元素的个数是由__nfds参数指定。

/* Type used for the number of file descriptors.  */
typedef unsigned long int nfds_t;

  timeout参数指定poll函数返回前等待多长时间。它是一个指定应等待毫秒数的正值。图1-20给出了它的可能取值。

图1-20 poll的timeout参数值

  INFTIM常值被定义为一个负值。如果系统不能提供毫秒级精度的定时器,该值就向上舍入到最接近的支持值。

  当发生错误时,poll函数的返回值为-1,若定时器到时之前没有任何描述符就绪,则返回0,否则返回就绪描述符的个数,即revents成员值非0的描述符个数。

  如果我们不再关心某个特定描述符,那么可以把与它对应的pollfd结构的fd成员设置成一个负值。poll函数将忽略这样的pollfd结构的events成员,返回时将它的revents成员的值置为0。

 

1.11 TCP回射服务器程序(再修订版)

  我们用poll代替select重写TCP回射服务器程序。在使用select早先那个版本中,我们必须分配一个client数组以及一个名为rset的描述符集。改用poll后,我们只需分配一个pollfd结构的数组来维护客户信息,而不必分配另一个数组。我们用前面处理client数组相同的方法处理该数组的fd成员:值-1表示所在项未用,否则即为描述符值。我们直到传递给poll的pollfd结构数组中的任何fd成员为负值的项都被poll忽略。

  下面是服务器程序。

int main()
{
  int i, maxi, listenfd, sockfd, connectfd;
  int nready;
  ssize_t n;
  char buf[ MAX_MESG_SIZE ];
  socklen_t clilen;
  // 我们声明在pollfd结构数组中存在OPEN_MAX个元素。确定一个进程任何时刻能够打开的最大描述符数目并不容易
  // 方法之一是以参数_SC_OPEN_MAX调用POSIX的sysconf函数,然后动态分配一个合适大小的数组。然而sysconf的可能
  // 返回之一是“ indeterminate ”(不确定),意味着我们仍然不得不猜测一个值
  struct pollfd client[ OPEN_MAX ];
  struct sockaddr_in cliaddr, servaddr;

  bzero( &servaddr, sizeof( servaddr ) );
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons( SERV_PORT );
  servaddr.sin_addr.s_addr = htonl( INADDR_ANY );
  
  // 创建一个TCP套接字
  if( ( listenfd = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
  {
    printf( " socket error!\n " );
    return -1;
  }
  
  // 在待绑定到TCP套接字的网际套接字地址结构中填入通配地址(INADDR_ANY)和服务器的众所周知端口(SERV_PORT,
  // 这里定义为5566)。绑定通配地址是在告知系统:要是系统是多宿主机,我们将接受目的地址为任何本地接口的连接。
  // 我们选择TCP端口号应该比1023大(我们不需要一个保留端口),比5000大(以免与许多源自Berkeley的实现分配临
  // 时端口的范围冲突),比49152小(以免与临时端口号的“正确”范围冲突),而且不应该与任何已注册的端口冲突。
  // listen把该套接字转换成一个监听套接字。 
  if( ( bind( listenfd, ( struct sockaddr* ) &servaddr, sizeof(servaddr) ) ) < 0 )
  {
    printf( " bind error!\n " );
    return -1;
  }
  
  if( listen( listenfd, LISTENQ ) < 0 )
  {
    printf( " listen error!\n " );
    return -1;
  }
  
  // 我们把client数组的第一项用于监听套接字,并把其余各项的描述符成员置为-1.我们还给第一项设置POLLRDNORM事件,这样
  // 当有新的连接准备好被接收时poll将通知我们。maxi变量含有client数组当前正在使用的最大下标值
  client[ 0 ].fd = listenfd;
  client[ 0 ].events = POLLRDNORM;
  for( i = 1; i < OPEN_MAX; i++ )
      client[ i ].fd = -1; // -1 indicates available entry
  maxi = 0; // max index into client[] array

  signal( SIGCHLD, sig_chld );

  for( ; ; )
    {
      // 我们调用poll以等待新的连接或者现有连接上有数据可读。当一个新的连接被接受后,我们在client数组中查找第一个描述符成员为负
      // 的可用项。注意,我们从下标1开始搜索,因为client[ 0 ]固定用于监听套接字。找到一个可用项之后,我们把新连接的描述符保存到
      // 其中,并设置POLLRDNORM事件。
      nready = poll( client, maxi + 1, -1 );

      if( client[ 0 ].revents & POLLRDNORM )  // new client connection
      {
          clilen = sizeof( cliaddr );
          connectfd = accept( listenfd, ( struct sockaddr* ) &servaddr, &clilen );

          for( i = 1; i < OPEN_MAX; i++ )
          {
              if( client[ i ].fd < 0  )
              {
                  client[ i ].fd = connectfd;  // save descriptor
                  break;
              }
              if( i == OPEN_MAX )
              {
                  printf( " too many clients " );
                  exit( 1 );
              }

              client[ i ].events = POLLRDNORM;
              if( i > maxi )
                  maxi = i;  // max index in client[] array

              if( --nready <= 0 )
                  continue;  // no more readable descriptors
          }
      }

      // 我们检查的两个返回事件是POLLRDNORM和POLLERR。其中我们并没有在event成员中设置第二个事件,因为它在条件成立时总是
      // 返回。我们检查POLLERR的原因在于:有些实现在一个连接上接收到RST时返回的是POLLERR事件,而其他实现返回的只是
      // POLLRDNORM事件。不论哪种情形,我们都掉用read,当有错误发生时,read将返回这个错误。当一个现有连接由它的客户终止时,
      // 我们就把它的fd成员置为1.
      for( i = 1; i <= maxi; i++ ) //check all clients for data
      {
          if( ( sockfd = client[ i ].fd ) < 0 )
              continue;
          if( client[ i ].revents & ( POLLRDNORM | POLLERR ) )
          {
              if( ( n = read( sockfd, buf, MAX_MESG_SIZE ) ) < 0 )
              {
                  if( errno == ECONNRESET ) // connection reset by client
                  {
                      close( sockfd );
                      client[ i ].fd = -1;
                  }
                  else
                  {
                      printf( " read error " );
                      exit( 1 );
                  }
              }
              else if( n == 0 ) // connection closed by client
              {
                  close( sockfd );
                  client[ i ].fd = -1;
              }
              else
                  write( sockfd, buf, n );

              if( --nready <= 0 )
                  break;  // no more readble descriptors
          }
      }

    }
}

 

 

 

 

 

 

 

 

 

 

共有 人打赏支持
粉丝 7
博文 90
码字总数 314854
×
thanatos_y
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: