epoll----原理

5 篇文章 0 订阅

注:关于epoll,在我浏览了各路大神的相关博客后,以一个观后者的身份记录下我个人的理解,这种多服用技术,我下面将结束其在短连接的情况下的操作系统下的流程(长连接操作类似,原理相同),我们熟悉的redis,nginx,在底层的socket运用的就是这种方式来实现高并发下的服务器事件处理。

        在此之前,我们先了解一下epoll涉及的linux相关操作:

        1:工作队列:里面存放的是各种运行的进程,内核不断地执行里面的进程。(不是按顺序的,一般都是有时间片,时间到了,不管当前进程是否执行完,都会去执行其他进程,后面也会再回来执行,可以认为工作队列上的进程都是在分时执行的,不过速度很快,所以看上去像是在同时执行)

        2:等待队列:比如socket就是有等待队列的,可以认为是某个阻塞的进程,可以唤醒后加入到工作队列继续执行

        3:中断程序:原理就不解释了,简单理解为就是,硬件(键盘,鼠标,网卡......)发起了中断信号,然后把某个等待队列中的进程放到工作队列

        4:文件系统:有自己的文件结构,如发送缓存区,接收缓存区,等待队列等

 

场景一:简单的一次短连接服务器端的流程

        假设服务端在此次的所有操作为进程A;进程A中的流程如下:

//创建

socketint s = socket(AF_INET, SOCK_STREAM, 0);

//绑定,监听操作

bind(s, ...)

listen(s, ...)

//接收客户端连接

int c = accept(s, ...)

//接收客户端数据

recv(c, ...);

//其他的数据操作

..

        流程:

        1:服务端接收到客户端连接,执行进程A,进程A在工作队列中执行

        2:执行socket()方法后,会创建一个socket对象,这个对象有其接收缓存区,等待队列(还有其他,但是只关注这两个)

        3:进程A执行到 recv() 操作时,阻塞进程,进程A对出工作队列,进程A放入到socket的等待队列中 。

        4:客户端发送数据到服务端,网卡收到客户端的数据,中断程序,数据放到内存。最后的结果是数据会放到socket 的接收缓存区,socket唤醒等待队列中的进程A,重新放入工作队列

        5:唤醒后的进程A继续执行,通过网络数据包中的ip端口信息,可以找到对应的socket,获取接收缓存区的数据,执行后续操作

 

        总结:

        recv()方法只能监听一个socket,但是服务器往往需要监听多个客户端,所有有了 select的用法

 

场景二:select的流程

下面为select的简单用法

//前面的操作与上面相同
int s = socket(AF_INET, SOCK_STREAM, 0); 
bind(s, ...);
listen(s, ...);

int fds[] = 存放需要监听的socket

while(1){

int n = select(..., fds, ...)

for(int i=0; i < fds.count; i++){

if(FD_ISSET(fds[i], ...)){

//fds[i]的数据处理

}

}}

        在代码中,先准备一个数组(下面代码中的fds),让fds存放着所有需要监视的socket。然后调用select,如果fds中的所有socket都没有数据,select会阻塞,直到有一个socket接收到数据,select返回,唤醒进程。用户可以遍历fds,通过FD_ISSET判断具体哪个socket收到数据,然后做出处理。

        流程:

        1:前面绑定监听操作省略,进程A把所有监听的socket放入fds 中(比如监听的socket有3个,分别为socket1  socket2  socket3),进入while无限循环

        2:进程A调用select方法时阻塞,进程A阻退出工作队列同时放入3个socket的等待队列中,如果返回时0,阻塞(说明3个socket都没接收到消息), 如果非0(有可能是1 2 3,看这3个socket有几个收到了数据),中断程序,把改socket等待队列中的进程A重新放入工作队列,同时删除其他socket中的等待队列中的进程A,进入for循环。

        3:遍历所以fds,用FD_ISSET判断哪个socket有数据,进行数据处理。

        4:当进程A数据处理完后,重新进入进入while循环,又再次进入步骤 2 中,所以客户端发起一次数据和发起N次数据到服务端都可以监控。(一次for循环只处理一个socket的数据)

        总结 :

        每次调用select都需要将进程加入到所有监视socket的等待队列,每次唤醒都需要从每个队列中移除。这里涉及了两次遍历,而且每次都要将整个fds列表传递给内核,有一定的开销。正是因为遍历操作开销大,出于效率的考量,才会规定select的最大监视数量,默认只能监视1024个socket。

        进程被唤醒后,程序并不知道哪些socket收到数据,还需要遍历一次。

 

场景三:epoll的流程

       下面为epoll的简单用法

//前面的操作与上面相同
int s = socket(AF_INET, SOCK_STREAM, 0);
bind(s, ...);
listen(s, ...);

int epfd = epoll_create(...);epoll_ctl(epfd, ...); //将所有需要监听的socket添加到epfd中

while(1){

接收到数据的socket = epoll_wait(...)

for(接收到数据的socket){

//处理

}}

先用epoll_create创建一个epoll对象eventpoll,再通过epoll_ctl将需要监视的socket添加到epfd中,最后调用epoll_wait等待数据。

        流程:

        1:进程A调用epoll_create方法时,内核会创建一个eventpoll对象(也就是程序中epfd所代表的对象)。eventpoll对象也是文件系统中的一员,和socket一样,它也会有等待队列。

        2:进程A调用epoll_ctl方法时,添加或删除所要监听的socket 到epfd(比如监听的socket有3个,分别为socket1  socket2  socket3,eventpoll保存这些socket的信息用的是红黑树rbr,检索会很快),eventpoll添加到epfd 中所有的socket的等待队列中

        3:进程A调用epoll_wait方法时阻塞,进程A退出工作队列,添加到eventpoll的等待队列中

        4:当epfd有一个或N个socket 收到数据时中断程序,唤醒socket 等待队列的 eventpoll ,把该socket的引用添加到eventpoll 中的双向链表rdlist 中,同时唤醒eventpoll等待队列中的进程A,进程A继续运行,epoll_wait获得了收到数据的socket,执行后续操作(通过回调方法,具体的不太清楚,而且应该会再次把eventpoll放入对应的socket 的等待队列中)

        5:当进程A数据处理完后,重新进入进入while循环,又再次进入步骤 3 中,所以客户端发起一次数据和发起N次数据到服务端都可以监控。(一次for循环处理完epoll_wait返回的所有接收到数据的socket)

 

 

epoll

如下图所示,eventpoll包含了lock、mtx、wq(等待队列)、rdlist等成员。rdlist和rbr是我们所关心的。

 

如果这篇文章说不清epoll的本质,那就过来掐死我吧!

epoll原理示意图,图片来源:《深入理解Nginx:模块开发与架构解析(第二版)》,陶辉

 

就绪列表的数据结构

就绪列表引用着就绪的socket,所以它应能够快速的插入数据。

程序可能随时调用epoll_ctl添加监视socket,也可能随时删除。当删除时,若该socket已经存放在就绪列表中,它也应该被移除。

所以就绪列表应是一种能够快速插入和删除的数据结构。双向链表就是这样一种数据结构,epoll使用双向链表rdllist来实现就绪队列。

索引结构

既然epoll将“维护监视队列”和“进程阻塞”分离,也意味着需要有个数据结构来保存监视的socket。至少要方便的添加和移除,还要便于搜索,以避免重复添加。红黑树是一种自平衡二叉查找树,搜索、插入和删除时间复杂度都是O(log(N)),效率较好。epoll使用了红黑树作为索引结构(对应上图的rbr)。

 

ps:因为操作系统要兼顾多种功能,以及由更多需要保存的数据,rdlist并非直接引用socket,而是通过epitem间接引用,红黑树的节点也是epitem对象。同样,文件系统也并非直接引用着socket。为方便理解,本文中省略了一些间接结构。

 

总结: 感觉很多地方描述不够清楚也不一定对,图也没画,待优化,上述有的资料是看其他大佬那复制的,有空再补全

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值