文档章节

网络编程学习——I/O复用(三)

thanatos_y
 thanatos_y
发布于 2016/04/15 10:23
字数 2104
阅读 12
收藏 0

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
          }
      }

    }
}

 

 

 

 

 

 

 

 

 

 

© 著作权归作者所有

共有 人打赏支持
thanatos_y
粉丝 7
博文 112
码字总数 315059
作品 0
成都
程序员
私信 提问
0-Linux 网络编程修炼指南——内功心法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/q1007729991/article/details/69091877 学习交流群: Linux 学习交流群 610441700 说明:本系列文章并不能取代...

--Allen--
2017/04/04
0
0
我的网络开发之旅——socket编程

上一篇文章《TCP/IP协议分析》讲述了自己是如何和网络领域的开发扯上关系的。正如从招聘网站上抽出的几个关键词“TCP/IP, Socket, 多线程”可见,协议分析并不是网络开发的主流,通常我们所说...

yaocoder
2014/09/21
0
0
新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析

1、引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。 本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件、...

JackJiang2011
11/05
0
0
gRPC学习笔记——(一)学习铺垫

(如有不全,烦请指出,后续不断跟进修正) 一、什么是RPC?为什么要学习RPC?有没有RPC的代替品? ——今后所有的学习笔记都将以此三问起头。关于编程,个人觉得由此三问,可助于编码人更加...

志明丶
2017/11/29
0
0
解读I/O多路复用技术

前言 当我们要编写一个echo服务器程序的时候,需要对用户从标准输入键入的交互命令做出响应。在这种情况下,服务器必须响应两个相互独立的I/O事件:1)网络客户端发起网络连接请求,2)用户在...

新栋BOOK
2017/11/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周三乱弹 —— 你是靠自己努力才失败的

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 小小编辑:推荐歌曲 《Raveena》- Raveena 《Raveena》- Raveena 手机党少年们想听歌,请使劲儿戳(这里) 11月18日,俞敏洪在某论坛演讲中称...

小小编辑
55分钟前
313
7
firewalld

1. firewalld 是什么 CentOS中默认是有 firewalld, iptables, etablesd firewalld 是 CentOS7/RadHat7 中默认的防火墙管理工具. firewalld 工具用来管理里netfilter, 不过底层还是调用的还是...

Fc丶
今天
4
0
Java 源代码和 C 源代码的运行区别

与其他程序的执行方式和编译方式不同。 Java 源代码需要进行编译成字节码后在 Java 虚拟机上运行,这样 Java 程序能够保持独立性和跨平台功特性。 请参考下图。 https://www.cwiki.us/pages...

honeymose
今天
6
0
Apache限定目录解析PHP,限制user_agent,PHP相关的配置

Apache限定目录解析PHP 配置前访问upload/index.php [root@test-a ~]# curl -x192.168.77.139:80 'www.test.com/upload/index.php'This is upload diretory 配置,/usr/local/apache2.4/......

野雪球
今天
6
0
java.util.Concurrent.Exchanger源码

类图 源码: package java.util.concurrent;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.atomic.AtomicReference;import java.util.concurrent......

狼王黄师傅
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部