select epoll

高并发基石|深入理解IO复用技术之epoll 该文有些错误
poll&&epoll实现分析(二)——epoll实现
如果这篇文章说不清epoll的本质,那就过来掐死我吧! (3)
epoll边缘触发模式
再谈Linux epoll惊群问题的原因和解决方案

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]的数据处理
        }
    }
}

原理:需要被监听的socket的列表为fds,调用select时,会有一个监听进程A,把A放入各个socket的阻塞队列中,当任何一个socket有事件发生时,将进程A从各个socket的阻塞队列中移除,将进程A放入工作队列,进程A遍历fds来查找哪个socket有事件发生。
在这里插入图片描述在这里插入图片描述在这里插入图片描述
select的缺点显而易见:

  1. 每次调用select都要把进程放到每个socket的阻塞队列中,每次有事件发生都要将进程从每个socket的阻塞队列中移除,进程A进入工作队列工作时又要遍历socket来看是谁有事件发生了。

很显然:
1.我们肯定中断时保存一个是谁让中断的就好了。
2.每次将进程阻塞到全部socket的等待队列,然后唤醒的时候将进程从所有阻塞队列移除了太麻烦了,,。每个socket的都注册一个回调函数,中断的时候用该函数处理,标记一个数据结构rdlist存储谁有事件发生了,socket有中断信号的话 中断处理程序会给这个数据结构rdlist添加上现在 对应socket的标记。然后rdlist的事件就交给另外的线程了。

epoll

int s = socket(AF_INET, SOCK_STREAM, 0);   
bind(s, ...)
listen(s, ...)

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

while(1){
    int n = epoll_wait(...)
    for(接收到数据的socket){
        //处理
    }
}

在这里插入图片描述
不是添加到每个socket等待队列把,就是等待回调函数要操作。每个socket没有等待队列这一说把。
在这里插入图片描述在这里插入图片描述
假设计算机中正在运行进程A和进程B,在某时刻进程A运行到了epoll_wait语句。如下图所示,内核会将进程A放入eventpoll的等待队列中,阻塞进程。(由于rdlist现在为空 所以调用epoll_wait的进程被阻塞)
在这里插入图片描述
当socket接收到数据,中断程序一方面修改rdlist,另一方面唤醒eventpoll等待队列中的进程,进程A再次进入运行状态(如下图)。也因为rdlist的存在,进程A可以知道哪些socket发生了变化。
在这里插入图片描述

在这里插入图片描述
原理:

  1. epoll_create:创建一个类型为struct eventpoll的对象,并返回一个与之对应的文件描述符,之后应用程序在用户态使用epoll的时候都将依靠这个文件描述符。
  2. epoll_ctl调用epoll_insert实现:
    1)创建并初始化一个strut epitem类型的对象,完成该对象和被监控事件以及epoll对象eventpoll的关联;
    2)将struct epitem类型的对象加入到epoll对象eventpoll的红黑树中管理起来;
    3)将struct epitem类型的对象注册事件就绪时会调用的回调函数,在epoll中该回调函数就是ep_poll_callback();
    4)ovflist主要是暂态处理,调用ep_poll_callback()回调函数的时候发现eventpoll的ovflist成员不等于EP_UNACTIVE_PTR,说明正在扫描rdllist链表,这时将就绪事件对应的epitem加入到ovflist链表暂存起来,等rdllist链表扫描完再将ovflist链表中的元素移动到rdllist链表;
  3. epoll_wait:执行epoll_wait,由于rdlist为空,将A进程放入eventpoll的等待队列,阻塞进程。当某个socket收到数据,对应的epitem就加入到rdlist,同时唤醒eventpoll中的等待进程,加入工作队列。

(由于要把socket和epitem关联起来,才用了红黑树)

epoll的惊群问题

在epoll中存在惊群问题。进程a调用epoll_wait()后,rdlist没删掉这个epitem之前,a会唤醒下一个进程,下一个进程可能也会唤醒下下一个,直到这个epitemt才会从队列rdlist中被删除,才停止唤醒下一个进程的操作。但这个事件只能被一个线程得到,得不到的线程重新被阻塞。

LT:事件被得到后没处理完的话继续放到rdlist里面。触发条件:套接字可读或可写。
ET:事件被得到后就从队里面删除。触发条件:套接字缓冲状态发生变化。什么情况产生饥饿现象:我们考虑一个应用,从外来连接接收数据,然后对数据进行处理。如果用边缘触发处理,对一个套接字就需要循环读取,直到没有数据可读为止(通过返回EAGIN,实际上如果读取的数据小于提供的缓冲大小也可以做这样的断定)。如果其中一个连接源源不断的发送数据,这个套接口的读循环就无法退出,导致其它连接没有机会被处理。

在epoll官方没有正式修复这个问题之前,Nginx作为知名使用者采用全局锁来限制每次可监听fd的进程数量,每次只有1个可监听的进程,后来在Linux 3.9内核中增加了SO_REUSEPORT选项实现了内核级的负载均衡。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值