多路复用——select&poll&epoll

select()

系统调用 select()函数,提供轮循等待的方式从多个文件描述符中获取状态变化后的状态
程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变

 int select(int nfds, //socket+1
            fd_set *readfds, //可读
            fd_set *writefds,//可写
            fd_set *exceptfds, //异常
            struct timeval *timeout);//阻塞超时时限

第一个参数是限制要检测的文件描述符的范围的,一般都是socket+1;
第二,三,四个参数,可以看到都是fd_set*类型的,这是文件描述符集合类型(多个文件描述符在一起)
fd_set:其实这个结构就是一个整数数组, 更严格的说, 是一个 “位图”,使用位图中对应的位来表示要监视的文件描述符.

  • readfds:包含所有可能因状态变成可读而触发select()函数返回的文件描述符
  • writefds:包含所有可能因状态变成可写而触发select()函数返回的文件描述符
  • exceptfds:包含所有可能因状态发生特殊异常(如带外数据到来)而触发select()函数返回的文件描述符

具体操作如下:

FD_SET(fd,fdsetp)   //将fd添加到fdsetp中
FD_CLR(fd,fdsetp)  //从fdsetp中删除fd
FD_ISSET(fd,fdsetp) //检测fdsetp中的fd是否出现异常
FD_ZERO(fdsetp)      //初始化fdsetp为空

最后一个参数timeout,表示阻塞超时时限
这里写图片描述
当超时或者其中一个或者多个文件描述符发生变化时,此函数都将返回。
在实际应用中,可以针对select的返回值进行选择性的处理

返回值:

  1. <0 :函数执行错误,将返回-1
  2. ==0 :超时返回,就是说在timeout所描述的时间范围内没有任何文件描述符有需要的操作,则返回0,并且将该时间结构体清空为0
  3. >0 :正常,因一个或者多个文件描述符需要处理而返回,其返回值是产生异常的文件描述符数,并在相应文件描述符集合中清除不需要处理的文件描述符
//如果想检测某个socket是否有数据可读

fd_set rdfds;       //创建一个fd_set集合用来保存要检测的socket
int ret;           //返回值
FD_ZERO(&rdfds);  //用select函数之前先把集合清零
FD_SET(socket,&rdfds);//把要检测的文件描述符socket加入到集合中
struct timeval tv; //设置超时时间
tv.tv_sec=1;
tv.tv_usec=500;

//检测上面设置到集合rdfds里的文件描述符是否有可读消息
ret=select(socket+1,&rdfds,NULL,NULL,&tv);
if(ret<0)
   perror("select");
 else if(ret==0)
   printf("超时\n");
 else
 { /*说明了等待时间还没到之前的设定,socket的状态发生了变化*/
   printf("ret=%d\n",ret);
   /*ret记录了发生状态变化的文件描述符的数目,由于当前只监视了socket一个文件描述符,
   所以这里的ret=1;如果同时有多个文件描述符发生变化,这里返回的就是文件描述符的总和*/

   if(FD_ISSET(socket,&rdfds))/*先检测一下是否真的变成可读,有没有出现异常*/
   {  
       recv(...);//读取socket文件描述符里的数据
   }
 }

select缺点

  • 每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便.
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
  • 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
  • select支持的文件描述符数量太小

poll()

poll函数可以实现比select函数更强大的功能
函数poll()与select()函数完成类似的功能,都是用来等待一组文件描述符集合。
函数声明如下:
这里写图片描述

int poll(struct pollfd *fds,  
         nfds_t nfds,
         int timeout);

这个函数对文件描述符集合的描述使用了一个结构体数组
第一个参数是结构体数组,第二个参数是数组的大小
结构体声明如下:
这里写图片描述
优点:采用上面这种结构体,使得请求的事件和返回的事件都得到了记录,从而使应用程序更确切的明确需要完成那些操作

所请求或返回事件类型如下:
- POLLIN:有数据可读
- POLLPRI:
- POLLOUT:
- POLLRDHUP:
- POLLERR:
- POLLHUP:
- POLLNVAL:
缺点


epoll()

man手册说:epoll是为了处理大批量句柄而作了改进的poll
那“句柄”又是什么?

先来看看epoll的三个相关函数接口
1、创建一个句柄

int epoll_create(int size);  

注意:用完之后调用close(),对句柄进行关闭

2、控制一个事件(fd):注册/修改/删除

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//epfd:epoll_fd,epoll_creat()的返回值,标志这这个句柄
//op:option选择,

 - EPOLL_CTL_ADD:注册fd到epfd句柄中
 - EPOLL_CTL_MOD:修改已经注册的fd
 - EPOLL_CTL_DEL:从epfd中删除一个fd

//fd:需要监听的fd文件描述符
//event:对fd需要监听什么类型的事

 - EPOLLIN:可读
 - EPOLLOUT:可写

3、收集监测的事件(fd)中已经发生的事件

epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
//event:创建好的epoll_event结构体数组,用来放收集到的发生的事件(fd),
//maxevents:这个句柄的大小,maxevents不大于epoll_creat()时的size
理解epoll的原理机制

epoll_creat()创建,创建一个epoll对象,每一个epoll对象都会有一个eventpoll结构体

struct eventpoll{      
   ....      
   /*红⿊黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/      
   struct rb_root  rbr;      
   /*双链表中则存放着将要通过epoll_wait返回给⽤用户的满足条件的事件*/      
   struct list_head rdlist;      
   ....  
   }; 

这里写图片描述
红黑树中的事件(fd),通过一个回调函数,将发生的事件(fd)添加到继续队列中
当调用epoll_wait()查看是否有事件发生时,只要遍历就绪队列

epoll的优点:


epoll有2种工作方式-水平触发(LT)和边缘触发(ET)

猜你喜欢

转载自blog.csdn.net/lindaxym/article/details/79865935