I/O多路转接模型 [select] [poll] [epoll]

目录

select:

函数解析:

参数类型分析:

fd_set操作函数: 

 监控流程:

优缺点:

poll:

函数解析: 

 事件结构体解析:

 优缺点:

epoll:

函数解析:

原理:

epoll的特点:

LT和ET模式:

参考:


多路复用又称多路转接;可以监控多个文件描述符,当监控的文件描述符有事件(可读、可写、异常)产生的时候,就会通知调用者;

这样可以避免,进程对没有就绪的文件描述符进行操作,从而陷入阻塞的情况

select:

 select就是多路复用的一种实现;它主要将用户关心的文件描述符拷贝到内核,由内核进行监控,内核监控到有某些文件描述符有时间产生的时候就会返回对应的文件描述符,用户对返回的文件描述符进行操作;

函数解析:

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

参数解析:
nfds:可以提高监控效率;可以设置为最大监控文件描述符+1
readfds:读事件集合
writedfs:写事件集合
exceptfds:异常事件集合
timeout:结构体,用于设定超时时间
         timeout = NULL               阻塞监控
         timeout 中所代表时间为0       非阻塞监控
         timeout 中所代表的时间大于0    带有超时时间的监控    
返回值:
     大于0:就绪的文件描述符个数
     等于0: 监控超时
     小于0:监控出现错误

参数类型分析:

fd_set:

typedef struct
{
 #ifdef __USE_XOPEN
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
 # define __FDS_BITS(set) ((set)->fds_bits)
 #else
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
 # define __FDS_BITS(set) ((set)->__fds_bits)
 #endif
} fd_set;

//结构体fd_set里面放有一个__fd_mask类型的数组,数组大小为__FD_SETSIZE / __NFDBITS;

typedef long int __fd_mask;
#define __NFDBITS       (8 * (int) sizeof (__fd_mask))
#define __FD_SETSIZE  1024

 通过上面的源码分析:fd_set里面是一个数组   long  fds_bits[1024/64] ;

而这个数组是通过位图的形式进行使用的,这也说明了,我们为什么要传入三个要监控的事件集合;因此,在Linux下,通过源码分析的到nfds最大为1024

 struct  timeval:

struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };

 注意:返回给我们的是仅有事件发生的文件描述符,没有事件发生的文件描述符会从事件集合中抹除

fd_set操作函数: 

void FD_CLR(int fd, fd_set *set);      //从事件集合当中删除某一个文件描述符 

int  FD_ISSET(int fd, fd_set *set);    
//判断fd描述符,是否在set集合当中     
//0:表示没有在集合当中    非0:表示在集合当中


void FD_SET(int fd, fd_set *set);  //添加fd,到集合set当中
void FD_ZERO(fd_set *set);        //清空

 监控流程:

I/O多路转接模型 [select] [poll] [epoll]_第1张图片

当内核返回就绪的文件描述符的时候,用户为了获得就绪的文件描述符,需要遍历事件集合;当再次监控的时候,需要重新设置事件集合

优缺点:

1.优点:

          遵循posix标准,可以跨平台使用

          超时时间可以精确到微妙

2.缺点:

          select是需要轮询遍历的,监控的效率随着文件描述符的增多而下降

          select的监控的最大文件描述符是有限制的

           select监控事件集合的时候,需要拷贝到内核当中,同样,如果返回就绪的文件描述符的时候,同样需要拷贝;效率受到影响

           select返回的事件集合中只有就绪的文件描述符,当再次监控的时候,需要重新设置事件集合

           select返回的就绪事件集合中,需要轮询遍历才能知道是哪些文件描述符就绪了 

poll:

 poll同样也是多路复用的一种实现形式。与select不同的是poll仅限于在linux下使用,跨平台性不如select好;同样的poll和select一样也是采用轮询遍历的方式,所以效率方面几乎没有提升;

但是他改进了select的一个大缺点,不在限制监控的文件描述符数量;每个文件描述符对应一个事件结构;

仅需要设置一下:监控的文件描述符,监控该文件描述符的哪个事件(可读、可写还是异常)

函数解析: 

#include 
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数解析:
fds:要监控的文件描述符事件结构体数组
nfds:fds中元素的个数
timeout:超时时间,单位为毫秒
     timeout<0   非阻塞监控
     timeout=0   阻塞监控
     timeout>0   带有超时时间的监控
返回值:
  小于0:监控出错了
  等于0:监控超时
  大于0:返回就绪的时间个数

 事件结构体解析:

struct pollfd {
               int   fd;         /* file descriptor */   
               short events;     /* requested events */
               short revents;    /* returned events */
           };


fd:    监控的文件描述符
events:要监控该文件描述符的哪个事件
     eg:
          POLLIN:可读事件
          POLLOUT:可写事件
  要监控多个事件的时候,可以使用 |  操作符进行连接

revents: 
         当关心的文件描述符产生对应关心的事件时:将发生的事件放到revents当中返回给调用者; 
         revents在每次poll监控的时候,就会被初始化为空

 优缺点:

1.优点:

       采用了事件结构体的方式对文件描述符进行监控,简化了代码的编写

        不再限制监控的文件描述符的个数

        多次监控的时候,用户不需要再次设置监控事件

2.缺点:

       poll监控的时候,也是采用了轮询遍历的方式,随着监控的文件描述符的增多,效率下降

       poll监控事件集合的时候,需要拷贝到内核当中,同样,如果返回就绪的文件描述符的时候,同样需要拷贝;效率比较低

       poll返回就绪的事件时,也需要用户轮询遍历,查询就绪的事件;

       poll仅支持Linux平台

epoll:

函数解析:

1.创建epoll的操作句柄

#include 

int epoll_create(int size)
参数:size从Linux内核2.6.8之后size就没有了任何意义,只需要转入的size>0即可;
     //在一开始size表示epoll监控的事件结构的数量,后来epoll采用扩容的方式,size就没有了意义只需 
     //要大于0即可
返回值:
     -1:创建epoll操作句柄失败
         成功的话将会返回epoll的操作句柄

2.向内核的红黑树中添加,删除或者修改事件结构体


int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
    epfd:epoll的操作句柄
    op:   常见的宏:   EPOLL_CTL_ADD :添加事件结构
                      EPOLL_CTL_DEL :删除事件结构
                      EPOLL_CTL_MOD:修改事件结构
    fd : 要监控的文件描述符
    event:事件结构体


typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

           struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };
//uint32_t events 是该文件描述符监控的事件
                  EPOLLIN:可读事件
                  EPOLLOUT:可写事件
//epoll_data_t data 是一个联合体,联合体的特点是共用该联合体的内存
//也就是说ptr和fd变量,我们可以选择任用中一个,使用ptr的时候,我们可以自己定义一个结构体,该结构体的成员一定要包含fd

3.从双向链表中拷贝就绪的事件结构到events

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
参数:
   epfd:epoll的操作句柄
   events:出参,获取就绪的事件结构,可以传入一个数组
   maxevents: 获取就绪的事件结构的最大数量,是为了防止就绪的事件结构过多,events数组放不下,出现越界的情况
   timeout: 超时时间
    timeout<0 阻塞监控
    timeout=0  非阻塞监控
    timeout>0  带有超时时间的监控,单位为毫秒
返回值:
    大于0 :返回的是就绪的文件描述符的个数
    等于0 :监控超时
    小于0 : 监控出现错误

   

原理:

I/O多路转接模型 [select] [poll] [epoll]_第2张图片

我们使用epoll_create函数创建一个epoll句柄的时候,就会在内核当中创建一个eventpoll结构体;该结构体当中有两个比较重要的数据结构,一颗红黑树,存放着我们需要监控的事件结构;一旦红黑树中有文件描述符就绪,就会拷贝一份对应的事件结构到双向链表当中;也就是说,我们使用epoll_wait获取的就绪事件结构是从双向链表当中获取的,不需要遍历红黑树,这就体现了epoll的高性能;

此外在监控的时候,遍历红黑树的时间复杂度为O(logN) 是要比O(N)小的,这也体现的epoll的高性能

epoll的特点:

1.没有监控的文件描述符的个数限制(即使有内存限制,也一定远远超过1024)

2.内核监控效率高,遍历红黑树时间复杂度为O(logN)

3.返回就绪事件结构的的效率高,从双向链表中获取就绪事件,时间复杂度为O(1),用户直接得到事件结构,无需轮询遍历

4.epoll仅仅限于在linux上使用

5.超时监控的时候,epoll的超时事件单位为毫秒

LT和ET模式:

参考:

三种 I/O 复用(select poll epoll)的优缺点总结 & epoll 为何如此高效_春风来不来的博客-CSDN博客

select、poll、epoll之间的区别(搜狗面试)  

你可能感兴趣的:(Linux,后端)