文档章节

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

thanatos_y
 thanatos_y
发布于 2016/04/15 10:23
字数 2104
阅读 11
收藏 0
点赞 1
评论 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
博文 90
码字总数 315059
作品 0
成都
程序员
0-Linux 网络编程学习笔记导航

学习交流群: Linux 学习交流群 610441700 说明:本系列文章并不能取代 《UNP》这本旷世之作,文章中难免有错误与不足之处,希望读者们遇到有疑问的地方可以加群互相交流,共同进步。写这一系...

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

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

yaocoder
2014/09/21
0
0
Linux IO模型漫谈(5)- IO复用模型之select

首先需要了解的是select函数: select函数 #include #include int select (int maxfd , fdset *readset ,fdset writeset, fd_set exceptionset , const struct timeval * timeout); 返回:就绪......

晨曦之光
2012/06/07
368
0
gRPC学习笔记——(一)学习铺垫

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

志明丶
2017/11/29
0
0
libevent源码深度剖析

原文地址:http://blog.csdn.net/sparkliang/article/details/4957667 libevent源码深度剖析一 ——序幕 张亮 1 前言 Libevent是一个轻量级的开源高性能网络库,使用者众多,研究者更甚,相关...

晨曦之光
2012/03/09
145
0
Golang 协程工作池 - 豆豆Pool

豆豆Pool — GOLANG简单的工作池 本repo是一个简单的golang工作池,仅供交流和学习使用。golang工作池的作用是可以限制goroutine的启动数量。 use //NewTask是放到工作池当中运行的函数。使用...

ppmoon
07/11
0
0
所看书籍记录

《程序员教程(第三版)》 《深入理解计算机系统》 《程序员的自我修养--链接、装载与库》(两遍) 《编译原理(龙书)》 《现代操作系统(第三版)》 《图解网络硬件》 《图解TCP/IP》 《数据...

thanatos_y
2016/03/14
62
0
François Chollet 谈深度学习的局限性和未来 - 下篇

雷锋网 AI 科技评论按:本篇是 Keras 作者 François Chollet 撰写的一篇博客,文中作者结合自己丰富的开发经验分享一些自己对深度学习未来发展方向的洞见。另外本篇也是一个关于深度学习局限...

隔壁王大喵
04/23
0
0
深度 | AI 芯片之智能边缘计算的崛起

  AI 科技评论按:本文作者为线性资本黄松延,原文首发于微信公众号:线性资本(ID: LinearVenture),AI 科技评论获其授权转载。   黄松延,浙江大学人工智能博士,前华为数据科学家,对...

AI科技评论
2017/12/26
0
0
敏捷开发“松结对编程”系列之十二:L型代码结构(质量篇之一)

有没有一种管理方法,无需额外的测试活动,就能大幅度提高产品质量?L型代码结构就是其中一种候选方案。 缺陷的来源 要减少缺陷,就要先弄清楚到底缺陷是从哪里来的?就我自己的经验而言,大...

wbf961127
2017/11/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

TextView设置行间距、字体间距

一、设置行间距 1、设置行间距:android:lineSpacingExtra,取值范围:正数、负数和0,正数表示增加相应的大小,负数表示减少相应的大小,0表示无变化 2、设置行间距的倍数:android:lineSpa...

王先森oO
5分钟前
0
0
适配器模式

适配器模式(Adapter):将一个类的接口转换成客户端希望的另外一个接口,适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 适配器用于连接两种不同种类的对象,使其毫...

阿元
5分钟前
0
0
CoreText进阶(四)-文字行数限制和显示更多

CoreText进阶(四)-文字行数限制和显示更多 用例和效果 Demo:CoreTextDemo 效果图: 默认的截断标识和自定义的截断标识符效果图  点击查看更多之后的效果图  为了可以设置显示的行数以...

aron1992
7分钟前
0
0
nginx的五种负载算法

nginx的五种负载算法 2017年04月26日 15:01:11 阅读数:1297 1.round robin(默认) 轮询方式,依次将请求分配到各个后台服务器中,默认的负载均衡方式。 适用于后台机器性能一致的情况。 挂...

linjin200
9分钟前
0
0
Android RecyclerView快速上手

RecyclerView mainMenu = findViewById(R.id.fragmentMain); mainMenu.setLayoutManager(new GridLayoutManager(getActivity(),4)); mainMenu.setAdapter(new MainAdapter......

燕归南
11分钟前
0
0
RabbitMQ实战:理解消息通信 

应用RabbitMQ的5种队列 一、简单队列 P:消息的生产者 C:消息的消费者 红色:队列 简单队列的生产者和消费者关系一对一 但有时我们的需求,需要一个生产者,对应多个消费者,那就可以采用第...

spinachgit
12分钟前
0
0
Linux的使用技巧:到底要不要会用?[图]

Linux的使用技巧:到底要不要会用?[图] 最近有个项目接近了尾声,要进入到调试测试阶段。这是一个使用Springboot框架为后台程序,mpvue构建的小程序项目。服务器我最终仍旧选择了Linux操作系...

原创小博客
14分钟前
0
0
记elasticdump 备份数据导出导入

版本: elasticsearch 5.5.2 elasticdump 2.2 系统 CentOS7.3 因项目需求 从生产导出一份索引到测试 帮助文档 https://github.com/taskrabbit/elasticsearch-dump?utm_source=dbweekly&utm_m......

雁南飞丶
15分钟前
0
0
saltstack配置目录管理

1.服务端配置 -接着编辑之前的 top.sls 文件 #vim /srv/salt/top.sls //修改为如下 base: 'slaver.test.com': - filedir -新建 filedir.sls 文件 # vim /srv/salt/filedir.sls file-dir: fi......

硅谷课堂
15分钟前
0
0
python日期时间

日期和时间 Python内建的datetime模块提供了datetime、date和time类型。datetime类型结合了date和time,是最常使用的: In [102]: from datetime import datetime, date, timeIn [103]:...

火力全開
22分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部