五、多路复用-Epoll

5. 基础函数介绍

先来介绍几个epoll函数的使用:

5.1.epoll_create - 创建文件描述符

#include <sys/epoll.h>
int epoll_create ( int size ); //创建文件描述符,size早期为hash的桶大小,目前无实际意义

5.2.epoll_ctl - 注册监控事件

1 #include <sys/epoll.h>
  /**
  参数
  fd:要操作的文件描述符
  op:指定操作类型
操作类型:
  EPOLL_CTL_ADD:往事件表中注册fd上的事件
  EPOLL_CTL_MOD:修改fd上的注册事件
  EPOLL_CTL_DEL:删除fd上的注册事件
  event:指定事件,它是epoll_event结构指针类型
  
  */
2 int epoll_ctl ( int epfd, int op, int fd, struct epoll_event *event );
  • epoll_event定义:
/**
结构体说明:
- events:描述事件类型,和poll支持的事件类型基本相同(两个额外的事件:EPOLLET和EPOLLONESHOT,高效运作的关键)
- data成员:存储用户数据
*/

struct epoll_event
 {
     __unit32_t events;    // epoll事件
     epoll_data_t data;     // 用户数据 
 };

5.3. epoll_wait函数

/**

返回:成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno
timeout:epoll的超时时间,单位是毫秒。当timeout为-1是,epoll_wait调用将永远阻塞,直到某个时间发生。当timeout为0时,epoll_wait调用将立即返回。
maxevents:指定最多监听多少个事件
events:检测到事件,将所有就绪的事件从内核事件表中复制到它的第二个参数events指向的数组中。
*/
#include <sys/epoll.h>
int epoll_wait ( int epfd, struct epoll_event* events, int maxevents, int timeout );

5.4.epoll重要概念介绍

5.4.1 EPOLL ONE SHOT 事件

一个线程在读取完某个socket上的数据后开始处理这些数据,而数据的处理过程中该socket又有新数据可读,此时另外一个线程被唤醒来读取这些新的数据。于是,就出现了两个线程同时操作一个socket的局面。可以使用epoll的EPOLLONESHOT事件实现一个socket连接在任一时刻都被一个线程处理。

作用:
   对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多出发其上注册的一个可读,可写或异常事件,且只能触发一次。
 
使用:
   注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个sockt。

效果:
   尽管一个socket在不同事件可能被不同的线程处理,但同一时刻肯定只有一个线程在为它服务,这就保证了连接的完整性,从而避免了很多可能的竞态条件。

5.4.2 LT与ET模式

二者的差异在于level-trigger模式下只要某个socket处于readable/writable状态,无论什么时候进行epoll_wait都会返回该socket;而edge-trigger模式下只有某个socket从unreadable变为readable或从unwritable变为writable时,epoll_wait才会返回该socket。

在这里,笔者强烈推荐《彻底学会使用epoll》系列博文,这是笔者看过的,对epoll的ET和LT模式讲解最为详尽和易懂的博文。下面的实例均来自该系列博文。限于篇幅原因,很多关键的细节,不能完全摘录。

5.4.3 ET实例

话不多说,直接上代码。

#include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h>

int main(void)
{
  int epfd,nfds;
  struct epoll_event ev,events[5]; //ev用于注册事件,数组用于返回要处理的事件
  epfd = epoll_create(1); //只需要监听一个描述符——标准输入
  ev.data.fd = STDIN_FILENO;
  ev.events = EPOLLIN|EPOLLET; //监听读状态同时设置ET模式
  epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev); //注册epoll事件
  for(;;)
  {
    nfds = epoll_wait(epfd, events, 5, -1);
    for(int i = 0; i < nfds; i++)
    {
      if(events[i].data.fd==STDIN_FILENO)
        printf("welcome to epoll's word!\n");

    }
  }
}

编译:

gcc server.c -o server

执行结果:

在这里插入图片描述

结果说明:

  1. 当用户输入一组字符,这组字符被送入buffer,字符停留在buffer中,又因为buffer由空变为不空,所以ET返回读就绪,输出”welcome to epoll’s world!”。
  2. 之后程序再次执行epoll_wait,此时虽然buffer中有内容可读,但是根据我们上节的分析,ET并不返回就绪,导致epoll_wait阻塞。(底层原因是ET下就绪fd的epitem只被放入rdlist一次)。
  3. 用户再次输入一组字符,导致buffer中的内容增多,根据我们上节的分析这将导致fd状态的改变,是对应的epitem再次加入rdlist,从而使epoll_wait返回读就绪,再次输出“Welcome to epoll’s world!”。

5.4.4 LT实例

接下来我们将上面2.2.1程序的第11行做如下修改:

ev.events=EPOLLIN;    //默认使用LT模式

编译并运行,结果如下:

在这里插入图片描述

​ 程序陷入死循环,因为用户输入任意数据后,数据被送入buffer且没有被读出,所以LT模式下每次epoll_wait都认为buffer可读返回读就绪。导致每次都会输出”welcome to epoll’s world!”。

#include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h>

int main(void)
{
    int epfd,nfds;
    struct epoll_event ev,events[5];                    //ev用于注册事件,数组用于返回要处理的事件
    epfd = epoll_create(1);                                //只需要监听一个描述符——标准输入
    ev.data.fd = STDIN_FILENO;
    ev.events = EPOLLIN;                                //监听读状态同时设置LT模式
    epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);    //注册epoll事件
    for(;;)
    {
        nfds = epoll_wait(epfd, events, 5, -1);
        for(int i = 0; i < nfds; i++)
        {
            if(events[i].data.fd==STDIN_FILENO)
            {
                char buf[1024] = {0};
                read(STDIN_FILENO, buf, sizeof(buf));
                printf("welcome to epoll's word!\n");
            }
        }
    }
}

​ 本程序依然使用LT模式,但是每次epoll_wait返回读就绪的时候我们都将buffer(缓冲)中的内容read出来,所以导致buffer再次清空,下次调用epoll_wait就会阻塞。所以能够实现我们所想要的功能——当用户从控制台有任何输入操作时,输出”welcome to epoll’s world!”

程序3:

#include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h>

int main(void)
{
    int epfd,nfds;
    struct epoll_event ev,events[5];                    //ev用于注册事件,数组用于返回要处理的事件
    epfd = epoll_create(1);                                //只需要监听一个描述符——标准输入
    ev.data.fd = STDIN_FILENO;
    ev.events = EPOLLIN|EPOLLET;                        //监听读状态同时设置ET模式
    epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);    //注册epoll事件
    for(;;)
    {
        nfds = epoll_wait(epfd, events, 5, -1);
        for(int i = 0; i < nfds; i++)
        {
            if(events[i].data.fd==STDIN_FILENO)
            {
                printf("welcome to epoll's word!\n");
                ev.data.fd = STDIN_FILENO;
                ev.events = EPOLLIN|EPOLLET;                        //设置ET模式
                epoll_ctl(epfd, EPOLL_CTL_MOD, STDIN_FILENO, &ev);    //重置epoll事件(ADD无效)
            }
        }
    }
}

运行结果:

在这里插入图片描述

出现上述循环打印的原因是因为epoll_ctl更换了操作位成EPOLL_CTL_MOD。

https://www.cnblogs.com/lojunren/p/3856290.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值