java epoll实例 java epoll select_文件描述符

本篇文章所讲主要内容:

    1、epoll系统调用介绍

    2、 epoll系统调用的优缺点

一、epoll介绍

        从面试的角度来说,对于现在的大厂面试来说,网络编程中的NIO模块一般是必须问的。目前java项目一般都是部署在Linux平台之上,linux内核提供的epoll系统调用无疑是多路复用最优的实现方式,对NIO的实现提供了基础。

        epoll是Linux特有的I/O复用函数。他在实现上与select和poll有很大的差异。首先epoll使用一组函数来完成任务。其次,epoll把用户所关心的文件描述符上的事件放在内核的一个事件表中,从而无须像select和poll把用户关心的事件每次调用的时候重复传送。但是epoll需要一个额外的文件描述符来唯一标识内核中的这个事件表。

    下面分为三个步骤来epoll的应用过程:

    1、创建内核事件表

            内核事件表是用来存储用户所关心的文件描述符。这个事件表的创建将使用epoll组合函数中的第一个epoll_create来创建,

            函数如下:

int epoll_create(int size);

                size:给内核一个提示,告诉它时间表需要多大

                返回值:指向内核中的时间表,该函数返回的描述符将用作其他函数调用的第一个参数。        

    2、操作用户感兴趣的事件

             主要用来操作内核事件表的事件,可以对用户感兴趣的事件进行注册、修改和删除。

             函数如下:         

int epoll_ctl(int epfd , int op , int fd , struct epoll_event *event);

                epfd:为内核事件表的引用地址  

                fd:要操作的文件描述符

                op:操作的类型。操作类型有三种,注册、修改和删除。

                event:指定的事件,里面包含用户感兴趣的事件以及携带的用户数据。它的结构类型为epoll_event。

                返回值:成功为0,失败为-1,

            epoll_event的结构为:

struct epoll_event{                _uint32_t events; //epoll事件,epoll支持的事件基本上和poll事件基本相同。对事件集合操作的方法和poll类似,在这里就不多介绍了。                     epoll_data_t data; //用户数据  }

   data用于存储用户数据其数据结构如下:

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

epoll_data_t是一个联合体,当描述符fd稍后成为就绪态时,联合体的成员可以用来指定传回给调用进程的信息。

      3、获取已经就绪的事件

        可以实现阻塞、非阻塞和阻塞一段时间的方式等待内核事件表的就绪。获取就绪的事件之后拷贝到用户空间,进行业务逻辑的处理。

    最后咱们来分析一下epoll_wait函数:主要用来在一段超时时间内等待一组文件描述符上的事件。

    函数如下:      

int  epoll_ctl(int epfd,,struct epoll_event *events,int maxevents,int timeout);

          epfd:为内核事件表的引用地址  

          events:已经就绪的事件列表,也就是将所有就绪的文件从内核事件表中复制到events中,这个数组只会返回已经就绪的事件。

          maxevents :最多监听的事件数量

          timeout:与poll和select函数作用相同,

     判断描述符就绪的条件与select和poll相同。但是对于描述符就绪之后的通知方式,epoll支持水平触发模式和边缘触发两种模式,默认采用的是水平触发模式。对于两种模式的介绍在文章:I/O多路复用之select系统调用 中我们已经分析。

    通过下面一个例子我们来分析一下两种模式之间的区别:

            当通过两种模式监控一个套接字的输入,接下来会发生如下事件。

            1、套接字有输入到来。

            2、我们调用一次epoll_wait()。无论我们采用的是水平触发还是边缘触发,该调用都会告诉我们该套接字已经就绪。

            3、再次调用epoll_wait()。

           如果我们采用的是水平触发,第二次调用epoll_wait(),还会返回该套接字处于就绪状态。如果我们采用边缘触发,第二次调用将会阻塞,因为自从上次调用以来没有新的输入。

      我们知道边缘触发模式要配合非阻塞I/O的模式进行使用,所以java NIO开发中经常会看到将Channel设置为非阻塞模式。通分析java  nio中的Selector源码发现在注册和修改事件信息的api中没有提供关于选择采用水平触发模式和边缘触发模式的API,应该在JVM的实现中指定了触发模式(待确认)。

二、epoll系统调用的优缺点

        优点:

            1、每次调用select()和poll(),内核都会检查一遍所传递的所有文件描述符。而对于epoll(),通过epoll_ctl指定了需要监视的文件描述符,内核会在内核事件表中记录此感兴趣的事件,不需要每次都进行传递。

            2、每次调用select()和poll()之后都会将结果设置到所传入的文件描述符集合中,并将集合再拷贝到用户空间。对于epoll()则只是将就绪的事件放入到就绪列表中,由epoll_wait()函数进行调用返回,极大的提升了性能。

            3、每次调用select()都会需要初始化输入数据,并且对于入参的文件描述符的数量有限制,在Linux平台上不能超过1024 个。epoll()没有这种限制。

            4、每次调用select()和poll()之后,返回值是一个集合,里面包含就绪和未就绪的文件描述符,需要用户程序自己来循环判断哪些文件描述符已经就绪。而epoll()则会直接返回所有已经就绪的文件描述符。

            5、对于select()和poll(),随着所要监控的描述符数量的增多,性能会不断的下降。而epoll则会下降的少。

     下面图片为三个函数监控文件描述符的对比:        

java epoll实例 java epoll select_文件描述符_02

    图片来自《UNIX系统编程手册 下》p1121

        缺点:

            1、Linux专有,可移植性差

            2、需要多个函数来共同支持,并且需要维护事件表来维护事件

 参考书籍:《UNIX系统编程手册 下》、《UNIX网络编程 卷一》和《Linux高性能服务器》