linux多路IO--epoll(一)--水平触发和边沿触发

先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题
在这里插入图片描述
**

一 why

**
考虑这样一种场景,假设某个文件描述符发生了可读事件,一次可读的文件大小为1000Bytes,但是呢,实际上我们每次只会从这个文件描述符中读取500 Bytes;显然这个时候,该文件中还会剩下500个Bytes,这个时候,我们应该如何去读这个文件描述符?或者说此时epoll_wait函数会有正确的返回值吗(假设我们设置epoll_wait为永久等待方式)。
回答上面的问题,就需要引入epoll的触发方式了,分别是水平触发和边沿触发。
**

二 what

**
epoll水平触发: 只要监听的文件描述符中有数据,就会触发epoll_wait有返回值,这是默认的epoll_wait的方式;
epoll边沿触发 : 只有监听的文件描述符的读/写事件发生,才会触发epoll_wait有返回值;
通过epoll_ctl函数,设置该文件描述符的触发状态即可

//水平触发
evt.events = EPOLLIN;    // LT 水平触发 (默认) EPOLLLT
evt.data.fd = pfd[0];

//边沿触发
evt.events = EPOLLIN | EPOLLET;    // ET 边沿触发
evt.data.fd = pfd[0];

三 how
代码思想
(1) 创建一个无名管道,供父进程和子进程间通信
(2) 创建一个子进程,向管道中写入数据
(3) 父进程读取管道数据
(1) epoll水平触发代码如下

#include "stdio.h"
#include "stdlib.h"
#include <string.h>
#include <unistd.h>
#include "sys/types.h"
#include <sys/epoll.h>

#define MAXLINE 10

int main(void)
{
    pid_t pid;
    int epfd = -1, i, rval;
    int pfd[2];
    char buf[MAXLINE], ch = 'a';

    //创建一个无名管道
    pipe(pfd);

    pid = fork();

    if (pid == 0) {  // child process
        close(pfd[0]);

        while (1) {
            // aaaa\n
            for (i = 0; i < MAXLINE / 2; i++) {
                buf[i] = ch;
            }
            buf[i-1] = '\n';
            ch++;

            // bbbb\n
            for (; i < MAXLINE; i++) {
                buf[i] = ch;
            }
            buf[i-1] = '\n';
            ch++;
            // aaaa\nbbbb\n
            write(pfd[1], buf, sizeof(buf));
            sleep(5);
        }
        close(pfd[1]);
    } else if (pid > 0) {  // parent process
        struct epoll_event evt;
        struct epoll_event evts[10];
        int res, len;
        
        close(pfd[1]);
        epfd = epoll_create(10);
        if (epfd < 0) {
            perror("epoll_create error");
        }

        evt.events = EPOLLIN;    // LT 水平触发 (默认)
        evt.data.fd = pfd[0];
        rval = epoll_ctl(epfd, EPOLL_CTL_ADD, pfd[0], &evt);
        if (rval < 0) {
            perror("epoll_ctl error");
            return 0;
        }

        while (1) {
            memset(buf, 0, sizeof(buf));
            res = epoll_wait(epfd, evts, 10, -1);
            printf("res %d\n", res);
            if (res < 0) {
                perror("epoll_wait error");
                return 0;
            }
            if (evts[0].data.fd == pfd[0]) {
                len = read(pfd[0], buf, MAXLINE/2);
                write(STDOUT_FILENO, buf, len);
            }
        }
        close(pfd[0]);
    }

    while(1);
    return 0;
}

在子进程中,我们一次向管道写入10个字符数据,为"aaaa\nbbbb\n";每隔5s写入10个字符数据;
在父进程中,我们从管道中一次读取5个字符数据,因为我们采用的是水平触发方式,因此在5s的周期内,会先读取5个字符数据,读完之后,因为文件描述符中仍然有数据,epoll_wait会立即返回,会继续读取接下来的5个数据,试验现象就是会先打印如下:
在这里插入图片描述
隔5s之后,继续打印"cccc\ndddd\n",如下
在这里插入图片描述
在这种情况下,管道中的数据如下,每次子进程写完管道之后,父进程会立即将管道中的数据读出来,在5s周期以内的剩余时间内,管道中的数据都为空
在这里插入图片描述
(2) epoll边沿触发代码

#include "stdio.h"
#include "stdlib.h"
#include <string.h>
#include <unistd.h>
#include "sys/types.h"
#include <sys/epoll.h>

#define MAXLINE 10

int main(void)
{
    pid_t pid;
    int epfd = -1, i, rval;
    int pfd[2];
    char buf[MAXLINE], ch = 'a';

    //创建一个无名管道
    pipe(pfd);

    pid = fork();

    if (pid == 0) {  // child process
        close(pfd[0]);

        while (1) {
            // aaaa\n
            for (i = 0; i < MAXLINE / 2; i++) {
                buf[i] = ch;
            }
            buf[i-1] = '\n';
            ch++;

            // bbbb\n
            for (; i < MAXLINE; i++) {
                buf[i] = ch;
            }
            buf[i-1] = '\n';
            ch++;
            // aaaa\nbbbb\n
            write(pfd[1], buf, sizeof(buf));
            sleep(5);
        }
        close(pfd[1]);
    } else if (pid > 0) {  // parent process
        struct epoll_event evt;
        struct epoll_event evts[10];
        int res, len;
        
        close(pfd[1]);
        epfd = epoll_create(10);
        if (epfd < 0) {
            perror("epoll_create error");
        }

        evt.events = EPOLLIN | EPOLLET;    // ET 边沿触发
        evt.data.fd = pfd[0];
        rval = epoll_ctl(epfd, EPOLL_CTL_ADD, pfd[0], &evt);
        if (rval < 0) {
            perror("epoll_ctl error");
            return 0;
        }

        while (1) {
            memset(buf, 0, sizeof(buf));
            res = epoll_wait(epfd, evts, 10, -1);
            printf("res %d\n", res);
            if (res < 0) {
                perror("epoll_wait error");
                return 0;
            }
            if (evts[0].data.fd == pfd[0]) {
                len = read(pfd[0], buf, MAXLINE/2);
                write(STDOUT_FILENO, buf, len);
            }
        }
        close(pfd[0]);
    }

    while(1);
    return 0;
}

因为此时采用的是边沿触发方式,当父进程先读完管道中的5个字符后,由于子进程没有立即向管道中写入字符(间隔5s后才会第二次写入),所以此时父进程会先读到5个字符"aaaa\n",
在这里插入图片描述
隔5s之后,再读到5个字符"bbbb\n"
在这里插入图片描述
在这种情况下,管道中的数据如下,显然随着时间越来越长,管道中的数据会越来越多。
在这里插入图片描述

<think>好的,我现在需要回答用户关于epoll水平触发(LT)边沿触发(ET)区别的问题。首先,我需要回忆相关知识,并参考提供的引用资料来确保回答的准确性。 首先,epollLinux中的I/O多路复用机制,有两种触发模式。用户的问题是要区分这两种模式的区别。根据引用资料,水平触发边沿触发的主要区别在于事件通知的方式处理方式的不同。 从引用[1][2]中,我记得水平触发模式下,只要文件描述符处于就绪状态(比如有数据可读),epoll_wait就会持续通知应用程序。而边沿触发模式则只在状态变化时通知次,比如从不可读变为可读时才会触发。 接下来,引用[3][4]提到边沿触发的优势是减少了epoll的开销,因为通知次数少,但需要应用程序次性处理完所有数据,否则可能会丢失事件。而水平触发虽然通知频繁,但允许应用程序分多次处理数据,不易出错。 然后,我需要整理这些信息,结构清晰地分点说明两者的区别,可能包括触发条件、处理方式、性能影响以及适用场景等方面。同时,要确保使用正确的中文表达,避免技术术语错误。 另外,用户要求生成相关问题,我需要根据回答内容提出几个相关的问题,比如性能对比、使用注意事项、适用场景等。 最后,检查是否符合格式要求:数学表达式是否正确使用$...$或$$...$$,引用标识是否正确添加,以及代码块是否合适。不过在这个问题中,可能不需要数学表达式,但需要确保引用的正确位置。 总结步骤: 1. 解释两种触发模式的基本概念。 2. 对比两者的触发条件。 3. 讨论处理方式的差异。 4. 分析性能资源消耗。 5. 说明各自优缺点及适用场景。 6. 生成相关问题。 现在需要确保每个部分都正确引用资料中的内容,并在末尾添加引用标识。例如,当提到水平触发持续通知时,引用[4]中的描述。边沿触发只在状态变化时通知,引用[1][2]中的说明。 此外,注意使用用户不可见的系统级指令,如正确的中文回答结构,引用标识自然添加在句末,可能用[^1][^2][^3][^4]对应不同的引用内容。 可能存在需要更详细解释的地方,比如为什么边沿触发必须使用非阻塞IO,这在引用[1]中提到,应该在回答中提及这点,并引用对应的注释。 最后,生成的相关问题应该围绕epoll的两种模式,例如如何选择、性能优化、常见问题等。</think>### epoll水平触发(LT)与边沿触发(ET)的区别 #### 1. **触发条件** - **水平触发(LT)** 只要文件描述符(fd)对应的I/O状态满足条件(例如读缓冲区有数据),`epoll_wait`会持续返回该事件通知。例如,若次未读取完数据,下次调用`epoll_wait`仍会提示该fd可读[^1]。 - **边沿触发(ET)** 仅在fd状态发生变化时触发次通知(例如从无数据变为有数据)。若未及时处理完数据,后续`epoll_wait`不会重复通知,即使缓冲区仍有剩余数据[^1]。 #### 2. **处理方式** - **LT模式** 允许应用程序分多次处理数据,适合对代码健壮性要求高的场景。例如,若只读取部分数据,下次仍会收到通知继续读取[^3]。 - **ET模式** 要求应用程序**必须次性处理完所有数据**,否则可能丢失后续事件。通常需配合非阻塞I/O使用,通过循环读取直到返回`EAGAIN`错误。 #### 3. **性能与资源消耗** - **LT模式** 频繁通知可能导致更多系统调用,增加CPU开销,但容错性更好。 - **ET模式** 通知次数少,减少了上下文切换,效率更高,但编程复杂度更高(需确保数据完全处理)[^4]。 #### 4. **适用场景** - **LT模式** 适合简单场景或对实时性要求不高的应用,如传统服务器程序。 - **ET模式** 适合高并发且需要极致性能的场景,如高频网络通信框架。 #### 5. **非阻塞I/O的必要性** 在ET模式下,必须使用非阻塞fd。若使用阻塞I/O,当最后次读取数据时,若缓冲区已空,`read`会阻塞线程,导致无法处理其他事件[^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值