epoll边缘触发_万字长文浅析:Epoll与Java Nio的那些事儿

451326a6c19c6ab51f3b63b839576ec1.gif点蓝色字关注“汀雨笔记”

“ Epoll 是Linux内核的高性能、可扩展的I/O事件通知机制。

b5bc64253ea15a12c2670dcc28776e57.png

图片来自 Pexels

在linux2.5.44首次引入epoll,它设计的目的旨在取代既有的select、poll系统函数,让需要大量操作文件描述符的程序得以发挥更优异的性能(wikipedia example: 旧有的系统函数所花费的时间复杂度为O(n), epoll搜索文件描述符的时间复杂度O(log n)监听处理事件的复杂度O1。epoll实现的功能与poll类似,都是监听多个文件描述符上的事件。

epoll底层是由可配置的操作系统内核对象建构而成,并以文件描述符(file descriptor)的形式呈现于用户空间(from wikipedia: 在操作系统中,虚拟内存通常会被分成用户空间,与核心空间这两个区段。这是存储器保护机制中的一环。内核核心扩展(kernel extensions)、以及驱动程序,运行在核心空间上。而其他的应用程序,则运行在用户空间上。所有运行在用户空间的应用程序,都被统称为用户级(userland))。

多说一点关于内核的

它是一个用来管理软件发出的数据I/O的一个程序,并将数据交由CPU和电脑其他电子组件处理,但是直接对硬件操作是非常复杂的,通常内核提供一种硬件抽象的方法来完成(由内核决定一个程序在什么时候对某部分硬件操作多长时间),通过这些方法来完成进程间通信和系统调用。

宏内核:

      宏内核简单来说,首先定义了一个高阶的抽象接口,叫系统调用(System call))来实现操作系统的功能,例如进程管理,文件系统,和存储管理等等,这些功能由多个运行在内核态的程序来完成。

微内核:

        微内核结构由硬件抽象层和系统调用组成;包括了创建一个系统必需的几个部分;如线程管理,地址空间和进程间通信等。微核的目标是将系统服务的实现系统的基本操作规则分离开来。

linux就是使用的宏内核。因为它能够在运行时将模块调入执行,使扩充内核的功能变得更简单。

epoll做了什么事?

epoll 通过使用红黑树(RB-tree)搜索被监视的文件描述符(file descriptor)。

在 epoll 实例上注册事件时,epoll 会将该事件添加到 epoll 实例的红黑树上并注册一个回调函数,当事件发生时会将事件添加到就绪链表中。

epoll的结构?

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

①epoll_create

向内核申请空间,创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。在最初的实现中,调用者通过 size 参数告知内核需要监听的文件描述符数量。如果监听的文件描述符数量超过 size, 则内核会自动扩容。而现在 size 已经没有这种语义了,但是调用者调用时 size 依然必须大于 0,以保证后向兼容性。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的。

epoll_ctl

向 epfd 对应的内核epoll 实例添加、修改或删除对 fd 上事件 event 的监听op 可以为 EPOLL_CTL_ADD, EPOLL_CTL_MOD, EPOLL_CTL_DEL 分别对应的是添加新的事件,修改文件描述符上监听的事件类型,从实例上删除一个事件。如果 event 的 events 属性设置了 EPOLLET flag,那么监听该事件的方式是边缘触发

events可以是以下几个宏的集合:

  • EPOLLIN:触发该事件,表示对应的文件描述符上有可读数据。(包括对端SOCKET正常关闭);

  • EPOLLOUT:触发该事件,表示对应的文件描述符上可以写数据;

  • EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

  • EPOLLERR:表示对应的文件描述符发生错误;

  • EPOLLHUP:表示对应的文件描述符被挂断;

  • EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

  • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。

例如:

struct epoll_event ev;
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);

epoll_wait

   Linux-2.6.19又引入了可以屏蔽指 定信号的epoll_wait:  epoll_pwait   接收发生在被侦听的描述符上的,用户感兴趣的IO事件。简单点说:通过循环,不断地监听暴露的端口,看哪一个fd可读、可写~

timeout 为 0 时,epoll_wait 永远会立即返回。而 timeout 为 -1 时,epoll_wait 会一直阻塞直到任一已注册的事件变为就绪。当 timeout 为一正整数时,epoll 会阻塞直到计时结束或已注册的事件变为就绪。因为内核调度延迟,阻塞的时间可能会略微超过 timeout (毫秒级)。

epoll文件描述符用完后,直接用close关闭,并且会自动被侦听的文件描述符集合中删除

epoll实战

说了这么多原理,脑壳怕嗡嗡的吧,来看看实战清醒下~ 如上知道:每次添加/修改/删除被侦听文件描述符都需要调用epoll_ctl,所以要尽量少地调用epoll_ctl,防止其所引来的开销抵消其带来的好处。有的时候,应用中可能存在大量的 短连接 (比如说Web服务器),epoll_ctl将被频繁地调用,可能成为这个系统的瓶颈。 传统的 select以及poll的效率会因为在线人数的线形递增而导致呈二次乃至三次方的下降,这些直接导致了 网络服务器 可以支持的人数有了个比较明显的限制。这是因为他们有限的文件描述符和遍历所有的fd所带来的低效。 重点哦~ 当你拥有一个 很大的socket集合,不过由于 网络延时 ,任一时间只有 部分socket是“活跃”的,但是select/poll 每次调用都会线性扫描全部的集合,导致效率呈现 线性下降epoll不存在这个问题,它 只会对“活跃”的socket进行操作---这是因为在内核实现中epoll是根据 每个fd上面的callback函数实现的。那么, 只有“活跃”的socket才会主动的去调用 callback函数,其他idle(空闲)状态socket则不会,在这点上,epoll实现了一个“伪”AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
int epfd = epoll_create(POLL_SIZE);
    struct epoll_event ev;
    struct epoll_event *events = NULL;
    nfds = epoll_wait(epfd, events, 20, 500);
    {
        for (n = 0; n             if (events[n].data.fd == listener) {
                //如果是主socket的事件的话,则表示
                //有新连接进入了,进行新连接的处理。
                client = accept(listener, (structsockaddr *)&local, &addrlen);
                if (client 0) {
                    perror("accept");
                    continue;
                }
                setnonblocking(client);        //将新连接置于非阻塞模式
                ev.events = EPOLLIN | EPOLLET; //并且将新连接也加入EPOLL的监听队列。
                //注意,这里的参数EPOLLIN|EPOLLET并没有设置对写socket的监听,
                //如果有写操作的话,这个时候epoll是不会返回事件的,如果要对写操作
                //也监听的话,应该是EPOLLIN|EPOLLOUT|EPOLLET
                ev.data.fd = client;
                if (epoll_ctl(epfd, EPOLL_CTL_ADD, client, &ev) 0) {
                    //设置好event之后,将这个新的event通过epoll_ctl加入到epoll的监听队列里面,
                    //这里用EPOLL_CTL_ADD来加一个新的epoll事件,通过EPOLL_CTL_DEL来减少一个
                    //epoll事件,通过EPOLL_CTL_MOD来改变一个事件的监听方式。
                    fprintf(stderr, "epollsetinsertionerror:fd=%d", client);
                    return -1;
                }
            }
            else if(event[n].events & EPOLLIN)
            {
                //如果是已经连接的用户,并且收到数据,
                //那么进行读入
                int sockfd_r;
                if ((sockfd_r = event[n].data.fd) 0)
                    continue;
                read(sockfd_r, buffer, MAXSIZE);
                //修改sockfd_r上要处理的事件为EPOLLOUT
                ev.data.fd = sockfd_r;
                ev.events = EPOLLOUT | EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd_r, &ev)
            }
            else if(event[n].events & EPOLLOUT)
            {
                //如果有数据发送
                int sockfd_w = events[n].data.fd;
                write(sockfd_w, buffer, sizeof(buffer));
                //修改sockfd_w上要处理的事件为EPOLLIN
                ev.data.fd = sockfd_w;
                ev.events = EPOLLIN | EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd_w, &ev)
            }
            do_use_fd(events[n].data.fd);
        }
    }
简单说下流程:
  • 监听到有新连接进入了,进行新连接的处理;

  • 如果是已经连接的用户,并且收到数据,读完之后修改sockfd_r上要处理的事件为EPOLLOUT(可写);

  • 如果有数据发送,写完之后,修改sockfd_w上要处理的事件为EPOLLIN(可读)

epoll在Java中怎么去调用的?

基础知识: 文件描述符:
  • (参考《Unix网络编程》译者的注释)

  • 文件描述符是Unix系统标识文件的int,Unix的哲学一切皆文件,所以各自资源(包括常规意义的<

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值