这两天公司开了新项目,后端使用Golang写的,于是想着用Golang来写个小玩意练练手,于是把目标对准了Golang中的net/http包,写个简单的收发文件的应用,在查找net/http的API的时候意外发现它底层使用的是epoll实现,这才突然想起来之前做C++网络编程的时候看到过epoll的部分,但是当时没有去详细了解,也就粗略知道个轮询的关键字就没再深究,这次正好有时间,就正好借着这个机会补一下这块的缺漏。
另外吐槽一下找这种奇葩的资料真难找,找了很多资料才搞明白。这篇博客也是希望帮助更多想深入了解epoll_event.data用法的哥们。
1、Epoll基础知识
Epoll主要用于实现多路复用技术,关于多路复用的实现方式总共有三种:select、poll、epoll,其中poll是对select的优化,但是治标不治本,所以这里仅对比一下select和epoll。
Epoll向来以性能著称,但是它和select之间的性能到底差在哪里,这是必须知晓的,要谈性能差距,肯定要从他们的实现方式开始聊了。
select的实现方式是给定一个文件描述符的队列,持续监听这个队列,当内核监听到有文件描述符的状态发生变化时,通知select进行处理,当select收到通知后,它只知道队列中有描述符就绪了,但是是哪个描述符并不清楚,所以需要遍历这个队列,查找哪些文件描述符准备就绪,然后进行接下来的操作,于是时间复杂度来到了O(N),同时它有一个比较大的弊端就是一个select只能监听1024个文件描述符,虽然可以通过改写头文件重新编译内核或者进行多线程select来缓解这个问题,但是终归是治标不治本,于是就由epoll来解决这两个比较大的问题了。
epoll的实现方式是给定一个epoll事件池与一个epoll_event数组(我称之为事件数组),这个事件池不限制最多能放入的事件个数,这就解决了select最多只能监听1024个文件描述符的问题,指定事件池后,同样由内核来监听每个事件的文件描述符与其指定的事件(每个事件是一个结构体,其中包含文件描述符和要等待的事件,看后面就能明白),当监听到文件描述符到达指定的状态时,由内核主动将就绪的事件拷贝到epoll_event数组中并返回就绪的描述符数量(这个拷贝由内核完成,效率很高),于是epoll能够直接获取到是哪些事件就绪,可以直接对epoll_event进行遍历,而不需要将事件池中的所有事件遍历,则直接将时间复杂度优化到了O(1),同时能够对每个事件附带一个data数据,在得到就绪的文件描述符时就能够一并得到它的状态。
介绍完了实现方式,他们之间的性能差距也就一目了然了,对于epoll的系统实现,可以看这篇文章,它对epoll的底层解释的比较详细。