事件触发模式 LT ET ?EPOLLIN EPOLLOUT 各种情况总结。【面试复盘】【学习笔记】

麻了,对 epoll 的触发机制理解不深刻…面试又被拷打了…

下面总结一下各种情况,并不涉及底层原理,底层原理看这里。

文章结构可以看左下角目录、

有什么理解的不对的,请大佬们指点。

先说结论,下面再验证:

  • LT(水平触发)

    • EPOLLIN 触发条件
         读缓冲区有数据就一直触发(即epoll_wait时能检测到),没有就不触发。

    • EPOLLOUT 触发条件:
          写缓冲区有空间可写,则一直触发。

  • ET(边缘触发)

    • EPOLLIN 触发条件:
          1. 当读 buff 从 空 -> 不空 时,触发;
          2. 当有新数据到达时,即读 buff 数据由 少 -> 多 时,触发;
          3. 当读 buff 有数据可读时,我们不处理,但是对相应fd进行epoll_mod IN事件时,触发。

    • EPOLLOUT 触发条件:
          1. 当写 buff 从 满 -> 不满 时,触发;
          2. 当有数据被送走时,即写 buff 数据由 多 -> 少 时,触发;
          3. 当写 buff 有数据,但是我们没处理(没发送出去),但是对相应fd进行epoll_mod OUT事件时,触发。

可以简单总结就是:
LT模式: 读buff有数据 / 写buff有空间,就触发;
ET模式: 读buff有数据,且数据减少 或 MOD时 / 写 buff 空间增加或MOD时,才触发。

验证:

下面将从几方面验证上面的结论:

LT模式
   检测EPOLLIN
      不读出数据
      读出数据
   检测EPOLLOUT
      不刷新缓冲区
      刷新缓冲区

ET模式
   检测EPOLLIN(1)
      不读出数据
      读出数据
   检测EPOLLIN(2,用MOD)
      不读出数据
      读出数据
   检测EPOLLOUT(1)
      不刷新缓冲区
      刷新缓冲区
   检测EPOLLOUT(2,用MOD)
      不刷新缓冲区
      刷新缓冲区

LT模式:

第一种:LT模式,检测EPOLLIN,未将数据读出

#include <unistd.h>
#include <iostream>
#include <sys/epoll.h>
using namespace std;

int main()
{
    int epfd = epoll_create(1); //创建epoll实例

    struct epoll_event ev,event[5];

    //设置参数
    ev.data.fd = STDIN_FILENO;  //接受键盘输入
    ev.events = EPOLLIN;    //默认就是LT模式
    epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ev); //监听键盘输入文件描述符

    while(1)
    {
        int nfd = epoll_wait(epfd,event,5,-1);

        for(int i=0;i<nfd;i++)
            if(event[i].data.fd == STDIN_FILENO)
            {
                cout<<"LT 模式下 EPOLLIN,未将数据读出\n";
                sleep(1);
            }
    }

    return 0;
}

结果:
输入test回车后,死循环;
证明socket读缓冲区有数据,则会一直触发EPOLLIN
事件触发模式 LT ET ?EPOLLIN EPOLLOUT 各种情况总结。【面试复盘】【学习笔记】_第1张图片



第二种:LT模式,测试EPOLLIN,将数据读出

#include <unistd.h>
#include <iostream>
#include <sys/epoll.h>
using namespace std;

int main()
{
    int epfd = epoll_create(1); //创建epoll实例

    struct epoll_event ev,event[5];

    //设置参数
    ev.data.fd = STDIN_FILENO;  //接受键盘输入
    ev.events = EPOLLIN;    //默认就是LT模式
    epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ev); //监听键盘输入文件描述符

    while(1)
    {
        int nfd = epoll_wait(epfd,event,5,-1);

        for(int i=0;i<nfd;i++)
            if(event[i].data.fd == STDIN_FILENO)
            {
                char buff[128];
                read(STDIN_FILENO,buff,sizeof(buff));
                cout<<"LT 模式下 EPOLLIN,读出数据\n";
                sleep(1);
            }
    }

    return 0;
}

结果:
当我们将数据读出来后,EPOLLIN不触发了;
即socket读缓冲区没数据,不触发EPOLLIN。

事件触发模式 LT ET ?EPOLLIN EPOLLOUT 各种情况总结。【面试复盘】【学习笔记】_第2张图片



第三种:LT模式,测试EPOLLOUT,不刷新缓冲区

#include <unistd.h>
#include <iostream>
#include <sys/epoll.h>
using namespace std;

int main()
{
    int epfd = epoll_create(1); //创建epoll实例

    struct epoll_event ev,event[5];

    //设置参数
    ev.data.fd = STDOUT_FILENO;  //检测输出缓冲区
    ev.events = EPOLLOUT;    //默认就是LT模式
    epoll_ctl(epfd,EPOLL_CTL_ADD,STDOUT_FILENO,&ev); //监听输出文件描述符

    while(1)
    {
        int nfd = epoll_wait(epfd,event,5,-1);

        for(int i=0;i<nfd;i++)
            if(event[i].data.fd == STDOUT_FILENO)
            {
                /*
                    这里需要清楚一个点,我们检测的是标准输出:
                    用cout,不加\n的话,缓冲区不会刷新,仍然有数据;
                */

               cout<<"LT模式,测试EPOLLOUT,不刷新缓冲区-----";

                // sleep(1);    //这里不能sleep,不然会一直阻塞住...不懂
            }
    }

    return 0;
}

结果:死循环了
非常合理,没有将写缓冲区刷新,写缓冲区有空间可写则一直触发EPOLLOUT,当buffer满的时候,buffer会自动刷清输出,同样会造成epoll_wait返回写就绪。
在这里插入图片描述



第四种:LT模式,测试EPOLLOUT,刷新缓冲区(加一个endl)

cout<<"LT模式,测试EPOLLOUT,不刷新缓冲区-----"<<endl;

结果:
同样死循环
非常合理,刷新缓冲区后,写缓冲区有空间可写,则一直触发。

事件触发模式 LT ET ?EPOLLIN EPOLLOUT 各种情况总结。【面试复盘】【学习笔记】_第3张图片

——————————————————————————————————————————————————————

ET模式:

第五种:ET模式,测试EPOLLIN;

(1)不将读缓冲区数据读出

#include <unistd.h>
#include <iostream>
#include <sys/epoll.h>
using namespace std;

int main()
{
    int epfd = epoll_create(1); //创建epoll实例
    struct epoll_event ev,event[5];

    //设置参数
    ev.data.fd = STDIN_FILENO;  //检测输入缓冲区
    ev.events = EPOLLIN | EPOLLET;    //测试ET模式下
    epoll_ctl(epfd,EPOLL_CTL_ADD,EPOLLIN,&ev); //监听输入文件描述符

    while(1)
    {
        int nfd = epoll_wait(epfd,event,5,-1);

        for(int i=0;i<nfd;i++)
            if(event[i].data.fd == STDIN_FILENO)
            {
                cout<<"ET模式,测试EPOLLIN,不将缓冲区数据读出\n";
                // sleep(1);
            }
    }

    return 0;
}

结果:
输入一个test,输出一下,然后阻塞了,再输入再输出,然后阻塞;

分析流程:
    1. 一开始读缓冲区为空,阻塞;输入test,即将test送入读缓冲区,此时由 空 -> 不空,触发EPOLLIN。(合理,验证上面的结论)
    2. 触发EPOLLIN后,由于我们没对缓冲区处理,此时不会一直触发,即调用epoll_wait被阻塞住了。
    3. 当我们再次输入test时,读缓冲区数据增加,导致fd状态改变,此时也会触发EPOLLOUT,因此epoll_wait返回,再次输出。(合理,验证上面 读缓冲区 增加数据时 也会触发的结论)

事件触发模式 LT ET ?EPOLLIN EPOLLOUT 各种情况总结。【面试复盘】【学习笔记】_第4张图片


(2)那么如果我们将读缓冲区的数据读出来呢?

for(int i=0;i<nfd;i++)
            if(event[i].data.fd == STDIN_FILENO)
            {
                char buff[128];
                read(STDIN_FILENO,buff,sizeof(buff));
                cout<<"ET模式,测试EPOLLIN,读出读缓冲区数据\n";
                // sleep(1);
            }

结果:
和不读出来是一样的,并不会出现死循环;
说明ET模式下,读缓冲数据 减少 / 非空->空 并不会触发EPOLLIN。
事件触发模式 LT ET ?EPOLLIN EPOLLOUT 各种情况总结。【面试复盘】【学习笔记】_第5张图片



第六种:ET模式下,测试EPOLLIN,用 EPOLL_MOD 重置

(1)不将 读缓冲区 数据读出:

#include <unistd.h>
#include <iostream>
#include <sys/epoll.h>
using namespace std;

int main()
{
    int epfd = epoll_create(1); //创建epoll实例
    struct epoll_event ev,event[5];

    //设置参数
    ev.data.fd = STDIN_FILENO;  //检测输入缓冲区
    ev.events = EPOLLIN | EPOLLET;    //测试ET模式下
    epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ev); //监听输入文件描述符

    while(1)
    {
        int nfd = epoll_wait(epfd,event,5,-1);

        for(int i=0;i<nfd;i++)
            if(event[i].data.fd == STDIN_FILENO)
            {
                cout<<"ET模式,测试EPOLLIN,用EPOLL_MOD重置,不读出缓冲区\n";

                ev.data.fd = STDIN_FILENO;  //检测输入缓冲区
                ev.events = EPOLLIN | EPOLLET;    //测试ET模式下

                epoll_ctl(epfd,EPOLL_CTL_MOD,STDIN_FILENO,&ev); //重新MOD事件(ADD无效)

                // sleep(1);
            }
    }

    return 0;
}

结果:
出现死循环(非常合理,验证了上面的结论,缓冲区非空,用EPOLL_MOD重置,会触发)

事件触发模式 LT ET ?EPOLLIN EPOLLOUT 各种情况总结。【面试复盘】【学习笔记】_第6张图片

(2)那如果将读缓冲区数据读出来呢?

for(int i=0;i<nfd;i++)
            if(event[i].data.fd == STDIN_FILENO)
            {
                cout<<"ET模式,测试EPOLLIN,用EPOLL_MOD重置,读出读缓冲区数据\n";
                char buff[128];
                read(STDIN_FILENO,buff,sizeof(buff));
                
                ev.data.fd = STDIN_FILENO;  //检测输入缓冲区
                ev.events = EPOLLIN | EPOLLET;    //测试ET模式下

                epoll_ctl(epfd,EPOLL_CTL_MOD,STDIN_FILENO,&ev); //重新MOD事件(ADD无效)

                // sleep(1);
            }

结果:
输入一次test,输出一次,也就是说 当读缓冲区为空,用EPOLL_MOD并不会一直触发。
事件触发模式 LT ET ?EPOLLIN EPOLLOUT 各种情况总结。【面试复盘】【学习笔记】_第7张图片



第七种:ET模式,测试EPOLLOUT;

(1)不刷新缓冲区

#include <unistd.h>
#include <iostream>
#include <sys/epoll.h>
using namespace std;

int main()
{
    int epfd = epoll_create(1); //创建epoll实例
    struct epoll_event ev,event[5];

    //设置参数
    ev.data.fd = STDOUT_FILENO;  //检测输出缓冲区
    ev.events = EPOLLOUT | EPOLLET;    //测试ET模式下,EPOLLOUT
    epoll_ctl(epfd,EPOLL_CTL_ADD,STDOUT_FILENO,&ev); //监听输出文件描述符

    while(1)
    {
        int nfd = epoll_wait(epfd,event,5,-1);

        for(int i=0;i<nfd;i++)
            if(event[i].data.fd == STDOUT_FILENO)
            {
                cout<<"ET模式,测试EPOLLOUT,不读出缓冲区";
                // sleep(1);
            }
    }

    return 0;
}

结果:
直接就阻塞了…
分析:第一次EPOLLOUT触发epoll_wait返回,然后我们往写缓冲区写数据,但是我们没刷新,此时缓冲区中有数据,此时当我们再次epoll_wait时,EPOLLOUT不触发,因此阻塞了。(非常合理,ET模式下,事件触发后不处理,下次不再触发)
在这里插入图片描述


(2)刷新缓冲区

cout<<"ET模式,测试EPOLLOUT,刷新缓冲区"<<endl;

结果:
死循环了,非常合理,验证了上面结论,ET模式下,写缓冲区数据由多变少时,会触发。
事件触发模式 LT ET ?EPOLLIN EPOLLOUT 各种情况总结。【面试复盘】【学习笔记】_第8张图片



第八种:ET模式,测试EPOLLOUT,用EPOLL_MOD重置

(1)不刷新缓冲区

#include <unistd.h>
#include <iostream>
#include <sys/epoll.h>
using namespace std;

int main()
{
    int epfd = epoll_create(1); //创建epoll实例
    struct epoll_event ev,event[5];

    //设置参数
    ev.data.fd = STDOUT_FILENO;  //检测输出缓冲区
    ev.events = EPOLLOUT | EPOLLET;    //测试ET模式下,EPOLLOUT
    epoll_ctl(epfd,EPOLL_CTL_ADD,STDOUT_FILENO,&ev); //监听输出文件描述符

    while(1)
    {
        int nfd = epoll_wait(epfd,event,5,-1);

        for(int i=0;i<nfd;i++)
            if(event[i].data.fd == STDOUT_FILENO)
            {
                cout<<"ET模式,测试EPOLLOUT,用EPOLL_MOD重置,不刷新写缓冲区";

                ev.data.fd = STDOUT_FILENO;  //检测输入缓冲区
                ev.events = EPOLLOUT | EPOLLET;    //测试ET模式下

                epoll_ctl(epfd,EPOLL_CTL_MOD,STDOUT_FILENO,&ev); //重新MOD事件(ADD无效)
            }
    }

    return 0;
}

                 

结果:
死循环,非常合理,验证了上面的,写缓冲区可写时,用EPOLL_MOD重置后会触发EPOLLOUT
在这里插入图片描述


(2)刷新缓冲区

cout<<"ET模式,测试EPOLLOUT,用EPOLL_MOD重置,刷新写缓冲区"<<endl;

结果:
同样死循环,非常合理,刷新之后写缓冲区依然可写,EPOLL_MOD重置后,触发EPOLLOUT
事件触发模式 LT ET ?EPOLLIN EPOLLOUT 各种情况总结。【面试复盘】【学习笔记】_第9张图片

总结

  • LT(水平触发):

    • EPOLLIN 触发条件:
         读缓冲区有数据就一直触发(即epoll_wait时能检测到),没有就不触发。

    • EPOLLOUT 触发条件:
          写缓冲区有空间可写,则一直触发。

  • ET(边缘触发)

    • EPOLLIN 触发条件:
          1. 当读 buff 从 空 -> 不空 时,触发;
          2. 当有新数据到达时,即读 buff 数据由 少 -> 多 时,触发;
          3. 当读 buff 有数据可读时,我们不处理,但是对相应fd进行epoll_mod IN事件时,触发。

    • EPOLLOUT 触发条件:
          1. 当写 buff 从 满 -> 不满 时,触发;
          2. 当有数据被送走时,即写 buff 数据由 多 -> 少 时,触发;
          3. 当写 buff 有数据,但是我们没处理(没发送出去),但是对相应fd进行epoll_mod OUT事件时,触发。

参考文章

彻底学会使用epoll(一)——ET模式实现分析

彻底学会使用epoll(二)——ET的读写操作实例分析

epoll LT/ET 深度剖析

深度剖析linux socket的epollin/epollout是何时触发的

我是一个找暑期实习的鼠鼠,今天是面完美团第二天,团子收了我吧!!!

你可能感兴趣的:(面试,c++,网络,经验分享,服务器)