• I/O多路转接之poll 函数


    poll

    一、poll()函数:

    这个函数是某些Unix系统提供的用于执行与select()函数同等功能的函数,自认为poll和select大同小异,下面是这个函数的声明:

    #include <poll.h>
    
    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    

    参数:

    1.第一个参数:一个结构数组,struct pollfd:

            fds:是一个struct pollfd结构类型的数组,每个数组元素都是一个pollfd结构,用于指定测试某个给定描述字fd的条件。存放需要检测其状态的Socket描述符;每当调用这个函数之后,系统不会清空这个数组,操作起来比较方便;特别是对于socket连接比较多的情况下,在一定程度上可以提高处理的效率;这一点与select()函数不同,调用select()函数之后,select()函数会清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加入到待检测的集合中;因此,select()函数适合于只检测一个socket描述符的情况,而poll()函数适合于大量socket描述符的情况

    结构如下:

     struct pollfd{
      int fd;          //文件描述符
      short events;    //请求的事件
      short revents;   //返回的事件
      };

      events和revents是通过对代表各种事件的标志进行逻辑或运算构建而成的。events包括要监视的事件(就是我需要关注的时间,是读?是写?还是出错?)poll用已经发生的事件填充revents。poll函数通过在revents中设置标志肌肤POLLHUP、POLLERR和POLLNVAL来反映相关条件的存在。不需要在events中对于这些标志符相关的比特位进行设置。如果fd小于0, 则events字段被忽略,而revents被置为0.标准中没有说明如何处理文件结束。文件结束可以通过revents的标识符POLLHUN或返回0字节的常规读操作来传达。即使POLLIN或POLLRDNORM指出还有数据要读,POLLHUP也可能会被设置。因此,应该在错误检验之前处理正常的读操作。

    poll函数的事件标志符值:

    常量 说明
    POLLIN 普通或优先级带数据可读
    POLLRDNORM 普通数据可读
    POLLRDBAND 优先级带数据可读
    POLLPRI 高优先级数据可读
    POLLOUT 普通数据可写
    POLLWRNORM 普通数据可写
    POLLWRBAND 优先级带数据可写
    POLLERR 发生错误
    POLLHUP 发生挂起
    POLLNVAL 描述字不是一个打开的文件

      注意:

            1)后三个只能作为描述字的返回结果存储在revents中,而不能作为测试条件用于events中。

      2)第二个参数nfds:要监视的描述符的数目。

      3)最后一个参数timeout:是一个用毫秒表示的时间,是指定poll在返回前没有接收事件时应该等待的时间。如果  它的值为-1,poll就永远都不会超时。如果整数值为32个比特,那么最大的超时周期大约是30分钟。 

    timeout值 说明
    INFTIM 永远等待
    0 立即返回,不阻塞进程
    >0
    等待指定数目的毫秒数


    如果是对一个描述符上的多个事件感兴趣的话,可以把这些常量标记之间进行按位或运算就可以了;

    比如:

    对socket描述符fd上的读、写、异常事件感兴趣,就可以这样做:

    struct pollfd  fds;
    
    fds[index].events=POLLIN | POLLOUT | POLLERR;

    当 poll()函数返回时,要判断所检测的socket描述符上发生的事件,可以这样做:

    struct pollfd  fds;
    
    //检测可读TCP连接请求:
    if((fds[nIndex].revents & POLLIN) == POLLIN)
    {
    <span style="white-space:pre">	</span>//接收数据,调用accept()接收连接请求
    }
    
    //检测可写:
    if((fds[nIndex].revents & POLLOUT) == POLLOUT)
    {
    <span style="white-space:pre">	</span>//发送数据
    }
    
    //检测异常:
    if((fds[nIndex].revents & POLLERR) == POLLERR)
    {
    <span style="white-space:pre">	</span>//异常处理
    }
    
    二、实例TCP服务器的服务器程序

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <netdb.h> 
    #include <string.h>
    #include <errno.h>
    #include <poll.h>   //for poll
    
    #define LISTENQ 1024
    #define MAXLINE 1024
    #define OPEN_MAX 50000
    
    #ifndef INFTIM 
    #define INFTIM -1 
    #endif             
    
    int start_up(char* ip,int port)  //创建一个套接字,绑定,检测服务器
    {
      //sock
      //1.创建套接字
      int sock=socket(AF_INET,SOCK_STREAM,0);   
      if(sock<0)
      {
          perror("sock");
          exit(0);
      }
      
      //2.填充本地 sockaddr_in 结构体(设置本地的IP地址和端口)
      struct sockaddr_in local;       
      local.sin_port=htons(port);
      local.sin_family=AF_INET;
      local.sin_addr.s_addr=inet_addr(ip);
    
      //3.bind()绑定
      if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0) 
      {
          perror("bind");
          exit(1);
      }
      //4.listen()监听 检测服务器
      if(listen(sock,back_log)<0)
      {
          perror("sock");
          exit(1);
      }
      return sock;    //这样的套接字返回
    }
    	
    int main(int argc, char *argv[])
    {
      int i, maxi, connfd, sockfd;
      int nready;
      ssize_t n;
      socklen_t clilen;
    
      struct sockaddr_in servaddr;
        socklen_t len=sizeof(servaddr); 
    
       char buf[BUFSIZ];
        struct pollfd client[OPEN_MAX]; // 用于poll函数第一个参数的数组,存放每次的文件描述符个数
      if( argc != 3 )
        {
           printf("Please input %s <hostname>
    ", argv[0]);
    	 exit(2);
        }
    
            int listenfd=start_up(argv[1],argv[2]);      //创建一个绑定了本地 ip 和端口号的套接字描述符
    		
          client[0].fd = listenfd;         //将数组中的第一个元素设置成监听描述字
          client[0].events = POLLIN;       //将测试条件设置成普通或优先级带数据可读(感兴趣的事件读、写、出错),此处书中为POLLRDNORM,*/
    		client[0].revents = 0;           //真正发生的事件
    
          for(i = 1;i < OPEN_MAX; ++i)     //数组中的其它元素将暂时设置成不可用
    		{
    			client[i].fd = -1;
    		}
            
          maxi = 0;
           while(1)
          {
             nready = poll(client, maxi+1,INFTIM);          //将进程阻塞在poll上
             if( client[0].revents & POLLIN)                //先测试监听描述字
             {
    				connfd = accept(listenfd,(struct sockaddr*)&servaddr, &clilen);
    
                  for(i = 1; i < OPEN_MAX; ++i)
    				{
    					if( client[i].fd < 0 )
                      {
                          client[i].fd = connfd;      //将新连接加入到测试数组中
                         client[i].events = POLLIN;  //POLLRDNORM; 测试条件普通数据可读
                          break;
                      }
                  	if( i == OPEN_MAX )
                   	{
                     	 	printf("too many clients"); //连接的客户端太多了,都达到最大值了
                       	exit(1);
                  	}
    
                  	if( i > maxi )
                      	maxi = i;        //maxi记录的是数组元素的个数
    
                  	if( --nready <= 0 )
                     	continue;            //如果没有可读的描述符了,就重新监听连接
    				}
              }
    
              for(i = 1; i <= maxi; i++)  //测试除监听描述字以后的其它连接描述字
              {
    				if( (sockfd = client[i].fd) < 0) //如果当前描述字不可用,就测试下一个
                      continue;
    
                  if(client[i].revents & (POLLIN | POLLERR))//如果当前描述字返回的是普通数据可读或出错条件
                  {
                     if( (n = read(sockfd, buf, MAXLINE)) < 0) //从套接口中读数据
                     {
                          if( errno == ECONNRESET) //如果连接断开,就关闭连接,并设当前描述符不可用
                          {
                             close(sockfd);
                             client[i].fd = -1;
                          }
                          else
                              perror("read error");
                      }
                    else if(n == 0) //如果数据读取完毕,关闭连接,设置当前描述符不可用
                      {
                          close(sockfd);
                          client[i].fd = -1;
                      }
                      else
                          write(sockfd, buf, n); //打印数据
                   if(--nready <= 0)
                       break;
                  }
              }
         }
         exit(0);
     }
    赐教!

  • 相关阅读:
    下班的时候收到两封Mail
    图解SSIS监视文件夹并自动导入数据
    用SqlDependency类为SQL2005查询提供更改通知
    一句话理解ref object的意义
    开源Mono框架将C#编程带到iPhone、Android和Wii
    div css 图片和文字上下居中对齐
    js 表单验证思路
    jquery 表单验证插件
    PHP开源AJAX框架
    Hello China V1.5 源码下载地址
  • 原文地址:https://www.cnblogs.com/melons/p/5791789.html
Copyright © 2020-2023  润新知