I/O 多路复用之 Select & Epoll

来源:互联网 发布:js调用微信分享sdk 编辑:程序博客网 时间:2024/05/22 17:31

本文将简要介绍 select 、epoll 接口,并从接口的设计、调用方式分析两者的差异,最后总结两者功能的差异。当然,为什么么会有这些差异还得去研究相关接口的内核实现细节。

1. Select


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

select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述副就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以 通过遍历fdset,来找到就绪的描述符。

select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select的一 个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但 是这样也会造成效率的降低。

2. Epoll


epoll 使用起来也很清晰,

  1. 首先调用 epoll_create 建立一个epoll 对象,返回文件 fd
  2. epoll_ctl 可以操作上面建立的 epoll 对象进行,增添需要被监听新建立的 socket ,或从监听列表中删除某个 socket
  3. epoll_wait 函数传递一个 struct epoll_event 结构参数,当在监控的所有句柄中有事件发生时,就返回数据已经准备就绪的 socket 文件描述符的数量
  4. 利用返回得到的准备就绪socket 的 num (数量)轮询上面得到的 struct epoll_event, 根据描述符的类型和事件类型分别进行处理

从上面的调用方式就可以看到epoll比select/poll的优越之处:epoll 只对活跃的 socket 进行事件处理,并且通过 add、delete 的方式来增减 socket,省去了不必要的重复拷贝。

epoll_wait范围之后应该是一个循环,遍利所有的事件。

几乎所有的epoll程序都使用下面的框架:

for( ; ; )    {        nfds = epoll_wait(epfd,events,20,500);        for(i=0;i<nfds;++i)        {            if(events[i].data.fd==listenfd) //有新的连接            {                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接                ev.data.fd=connfd;                ev.events=EPOLLIN|EPOLLET;                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中            }            else if( events[i].events&EPOLLIN ) //接收到数据,读socket            {                n = read(sockfd, line, MAXLINE)) < 0    //读                ev.data.ptr = md;     //md为自定义类型,添加数据                ev.events=EPOLLOUT|EPOLLET;                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓            }            else if(events[i].events&EPOLLOUT) //有数据待发送,写socket            {                struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取数据                sockfd = md->fd;                send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //发送数据                ev.data.fd=sockfd;                ev.events=EPOLLIN|EPOLLET;                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据            }            else            {                //其他的处理            }        }    }

3. Different


从 select 的调用方式就可以看出,每次调用都要传递需要监控的所有 FD_SET ,这意味着需要将用户态的描述符集 copy 到内核。而且 select 只是在有事件发生时才被唤醒,并没有给出哪些文件描述符是准备就绪的,必须得通过一次 O(n) 的线性扫描。

所以 select 有如下几点缺陷:

  1. 每次调用 select 都需要把fd集合从用户态拷贝到内核态
  2. 拷贝结束后 select 都需要在内核遍历传递进来的所有fd
  3. select支持的文件描述符数量太小了,默认是1024

epoll 能解决上面三点缺陷

  1. 省去不必要的重复拷贝:epoll 通过内核与用户空间mmap同一块内存,保证了每个fd在整个过程中只会拷贝一次
  2. 效率:epoll 只会对”活跃”的socket进行操作—这是因为在内核中 epoll 是根据每个fd上面的 callback 函数实现的。只有”活跃”的socket才会主动的去调用 callback函数,其他idle状态socket则不会
  3. epoll 没有最大文件符限制,它所支持的FD上限是最大可以打开文件的数目

4. Reference


Linux IO模式及 select、poll、epoll详解

IO多路复用之epoll总结

epoll 详解

0 0