Bootstrap

网络编程(3)——poll/epoll模型

poll

了解poll函数

介于select和epoll之间,用得少,只是简单了解

  int poll(struct pllfd* fds, nfds_t nfds,int timeout)
  函数说明:跟select相似,监控多路IO但是不能跨平台,委托内核监控可读可写,以及监控异常事件
  参数选择:
      fds:传入传出参数,实际上是一个结构体数组
      {
        int fds.fd:要监控的文件描述符
        short fds.event://要监控的事件:读,写,异常
            POLLIN->读事件
            POLLOUT->写事件      
        short fds.revents:返回的事件,告诉用户哪些文件描述符发生变化
      }
      nfds:数组实际的有效个数,内核监控的范围,具体最大值+1
      timeout:超时时间,单位毫秒
        -1:永阻塞,直到事件发生
        0:立刻返回
        >0:直到监控时间发生或者超时
  返回值:
    》0:发生变化的文件描述符的个数
    =0:没有文件描述符发生变化
    =-1:异常发生

开发流程

   建立socket
   设置端口复用
   绑定
   定义结构体
     {
     //定义结构体数组
     struct pollfd clients[1024] 
     clients[0].fd=lfd;//lfd是新建的socket
     int maxi=0;
     clients[0].events=POLLIN;
     //初始化
     for(i=1;i<1024;i++){
      clients[i]=-1;
     }
   //委托内核监控  
      while(1)
      {
          int nready=poll(client,maxi+1,-1);
          if(nready<0){
             if(errno=EINTER){
             continue;
             }
             break;
          }
          else
          {
          //有客户端连接请求到来
              if(client[0].revents==POLLIN&&client[0].fd==lfd)
              {
                  cfd=accept(lfd,NULL,NULL);
                  //寻找一个可用位置
                  for(int i=0;i<1024;i++)
                  {
                     if(clients[i].fd==-1)
                     {
                        clients[i].fd=cfd;
                        clients[i].events=POLLIN;
                        break;
                     }
                     if(i==1024){
                        //拒绝服务
                        close(cfd);
                        continue;
                     }
                  }
                  if(maxi<i){
                    maxi=i;//修改数组下标最大值
                  }
                  if(--nready==0)
                  {
                      continue;
                  }
              }
          //有客户端发送数据
          if(client[i])
            for(i=0;i<=maxi;i++){
                if(client[i].fd==-1){
                  continue//中有文件描述符不再被内核监控,的直接从头跑
                }
                //有数据发过来
                if(client[i].revents==POLLIN){
                    n=read(client[i].fd,buf,sizeof(buf));
                    if(n<=0)
                    {
                        close(client[i].fd);
                        client[i].revents=-1;/不再监控了
                        continue;
                    }
                    else{
                        write(client[i].fd,buf,n)
                    }
                }
            }        
          }
      }
      close(lfd)
    }
说明:返回的时候,events和fd不会变化,输出使revents,输入和输出分离,请求和返回分离
      如果fd成员赋值为-1,poll不会监控
      poll相对于select没有本质上的变化,但是可以突破1024的限制

epoll

函数说明

头文件:#include <sys/epoll.h>
创建

int epoll_create(int size); 
     函数说明:创建一棵epoll树,返回一个树根节点,就是一个文件描述符,标识树根
     参数:被忽略了,传一个大于0的就行,实际是表明组曾epoll数的节点个数,但是实际会不断扩充
     返回值:返回一个文件描述符,标识树的树根

增删改

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
      函数说明:能够将一个文件描述符对应的epoll结点进行更改,比如上树或者下树或者修改对应的事件(读或者写)
      参数:
          int epfd,树根结点 
          int op, 标识修改的方式,表名我们想做怎样的操作
                EPOLL_CTL_ADD:注册新的fd到epfd中;
                EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
                EPOLL_CTL_DEL:从epfd中删除一个fd;
          int fd, 目标文件描述符
          struct epoll_event *event
                struct epoll_event {
                  __uint32_t events;  /* Epoll events */
                        EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
                        EPOLLOUT:表示对应的文件描述符可以写;
                        EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
                        EPOLLERR:表示对应的文件描述符发生错误;
                        EPOLLHUP:表示对应的文件描述符被挂断;
                        EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
                        EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
                  epoll_data_t data;  /* User data variable */要操作的文件描述符
                        //这个字段是一个联合体,联合体在同一时间只有一个成员变量,联合体占用的内存大小为所有成员中占用字节最大的成员决定,在此处,就是类型为uint_64_t 的u64。
                        //这里只会用到fd。
                        typedef union epoll_data {  
                            void *ptr;  
                            int fd;  
                            uint32_t u32;  
                            uint64_t u64;  
                        } epoll_data_t;
                };

委托内核监控

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
      函数说明:委托内核监控的事件结点,返回的数组中的事件结点的只读不改,
      		是当时上epoll树的时候设置的值,其实就两种结点,一种监听结点,一种通信结点
      参数:
          int epfd, //树根结点
          struct epoll_event * events,//传出参数,结构体数组 
          int maxevents, //数组大小
          int timeout//监控时间,-1标识一直阻塞,0不阻塞,》0标识阻塞时长

epoll开发流程

{
  1.创建socket
  2.设置端口复用,
  3.绑定
  4.监听
  5.int epfd=epoll-creat(1)//创建树根
  6·上树
    struct epoll_event ev;
    ev.events=EPOLLIN;//表示对应的文件描述符可以读
    ev.data.fd=lfd;
    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
  7.进入while循环等待事件发生
    while(1)
    {
      struct epoll_event events[1024];
      nready=epoll(epfd,events,1024,-1)
      if(nready<0)
      {
         if(errno=EINTER)
         {
           continue;
         }
         break;
      }
    8。有事件发生,并且知道多少个事件发生变化
      for(i=0;i<nready;i++)
      {
        //有两类事件,连接请求或者数据发送
        sockfd=events[i].data.fd;
        //判断是否是链接
        if(sockfd==lfd)
        {
          cfd=accept(lfd,NULL,NULL);//得到新的文件描述符、
          //新的文件描述符也要上树,读事件
          ev.data.fd=cfd;
          ev.events=EPOLLIN;
          epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
        }
        else//有数据发过来
        {
          n=read(sockfd,buf,sizeof(buf));
          if(n<=0)//关闭链接或者读异常
          {
            //关闭连接
            close(so0ckfd);
            //下树,下数的时候最后一个参数可以设置为为NULL
            epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL);
            continue;
          }
          else
          {
            write(sockfd,buf,n);
          }
        }
      }
    }
    close(lfd);
}

触发模式

ET:edge triger
每次到来数据就触发一次,就算没读完也不会再通知了
LT:level triger
只要缓冲区中有数据就一直通知,默认情况下是水平触发,没读完就会一直读一直读直到读到最后的回车符(包括回车符)
如果想改为边缘触发模式,那么只需要在通信文件描述符cfd的上树事件改为
ev.events=EPOLLIN | EPOLLET;

思考

在ET模式下如一次读完数据
答:循环读,单数如果没有数据你还要读,读到的不是0,而是-1,而且会在read那里阻塞,那么会在read那里阻塞,这样就需要对cfd文件描述符设置非阻塞
在通信文件描述符上树之后,加入以下代码

       int flag=fcntl(cfd,F_GETFL,0);
       flag |=O_NONBLOCK;
       fcntl(cfd,F_SETFL,flag);
       同时对读到的加入判断,是否等于-1单独拎出来说
       if(n==-1)
       {
         break;//不需要关闭链接,只是读完了数据并不是异常
       }
       if(n==0||(n<0&&n!=-1))//真的出现了异常而且异常不是读取完毕,或者是对方关闭了链接(0)
       {
           //关闭连接
           close(so0ckfd);
           //下树,下数的时候最后一个参数可以设置为为NULL
           epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL);
           break;
       }

反应堆

一个for循环,里面没运行一次就更改函数指针指向的地址,有点多态那味儿了~

;