ZoyaPatel

epoll 深度解析:水平触发 (LT) 与边缘触发 (ET) 模式详解与应用对比

SohaniSharma

## 了解 Linux 中协程 (Coroutine) 切换的原理,包括它作为轻量级上下文切换的优势。深入探讨协程的用户级管理、协作式调度,以及与线程切换的性能对比。{alertInfo}

由人工编写审核,非AI生成内容,请放心观看!

{getToc} $title={文章目录} 

前提提要:

LT and ET 是电气学方面的概念,也就是水平触发方式和边缘状态触发方式的关系

水平触发 (LT) 与边缘触发 (ET) 模式详解与应用对比
水平触发 (LT) 与边缘触发 (ET) 模式详解与应用对比


ET:每次在的fd 文件描述符号状态变化的时候获取通知,也就是:从的有数据到数据为空的状态通知一次,文件描述符变化。

LT : 指的是 文件描述符满足条件时都会持续触发通知,只有有数据在缓存中,read 没有把所有的数据都取出来,那么下一次epoll_wait 还是回通知你。

特性边缘触发(ET)水平触发(LT)
事件触发频率只会触发一次,直到文件描述符的状态发生变化每次满足条件时都会触发事件(直到数据处理完为止)
数据读取要求必须在每次通知时读取所有数据,否则后续不会收到通知可以逐步读取数据,epoll 会持续通知直到数据全部读取
适用场景高性能、高并发的事件驱动场景,例如 Web 服务器简单的 I/O 操作,如文件监控或简单的网络应用
编程复杂度稍高,需要确保每次通知时处理完所有数据较低,只需要按需处理数据
性能高效,减少了冗余通知,适用于高并发应用可能会导致重复通知,增加 CPU 消耗

具体的例子

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

#define MAX_EVENTS 10

// 设置文件描述符为非阻塞模式
int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl");
        return -1;
    }

    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) == -1) {
        perror("fcntl");
        return -1;
    }

    return 0;
}

// 处理客户端请求的函数
void handle_client_request(int fd) {
    char buf[512];
    ssize_t bytes_read = read(fd, buf, sizeof(buf));

    if (bytes_read == -1) {
        if (errno == EAGAIN) {
            // 数据读取完毕
            return;
        }
        perror("read");
    } else if (bytes_read == 0) {
        // 客户端关闭连接
        printf("Connection closed by client\\n");
        close(fd);
    } else {
        buf[bytes_read] = '\\0';  // Null-terminate the string
        printf("Received request: %s\\n", buf);
        // 假设我们处理完请求并准备响应
    }
}

int main() {
    int epoll_fd, nfds;
    struct epoll_event ev, events[MAX_EVENTS];
    int server_fd;  // 服务器监听的套接字文件描述符

    // 创建 epoll 实例
    epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    // 假设 server_fd 已经通过 socket() 和 bind() 配置好,并且开始监听
    // 设置 server_fd 为非阻塞
    if (set_nonblocking(server_fd) == -1) {
        exit(EXIT_FAILURE);
    }

    // 将 server_fd 添加到 epoll 实例,并设置为边缘触发模式
    ev.events = EPOLLIN | EPOLLET;  // EPOLLIN: 可读事件,EPOLLET: 边缘触发
    ev.data.fd = server_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) == -1) {
        perror("epoll_ctl");
        exit(EXIT_FAILURE);
    }

    while (1) {
        nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }

        for (int i = 0; i < nfds; i++) {
            if (events[i].events & EPOLLIN) {
                // 处理客户端请求
                handle_client_request(events[i].data.fd);
            }
        }
    }

    close(epoll_fd);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <string.h>

#define MAX_EVENTS 10

// 设置文件描述符为非阻塞模式
int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl");
        return -1;
    }

    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) == -1) {
        perror("fcntl");
        return -1;
    }

    return 0;
}

// 处理文件读取的函数
void handle_file_read(int fd) {
    char buf[512];
    ssize_t bytes_read = read(fd, buf, sizeof(buf));

    if (bytes_read == -1) {
        if (errno == EAGAIN) {
            // 数据读取完毕
            return;
        }
        perror("read");
    } else if (bytes_read == 0) {
        // 文件末尾
        printf("EOF reached\\n");
    } else {
        buf[bytes_read] = '\\0';  // Null-terminate the string
        printf("New data in file: %s\\n", buf);
    }
}

int main() {
    int epoll_fd, nfds;
    struct epoll_event ev, events[MAX_EVENTS];
    int file_fd;  // 监控的文件文件描述符

    // 打开文件进行监控
    file_fd = open("logfile.txt", O_RDONLY);
    if (file_fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 设置文件描述符为非阻塞模式
    if (set_nonblocking(file_fd) == -1) {
        exit(EXIT_FAILURE);
    }

    // 创建 epoll 实例
    epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    // 将文件描述符添加到 epoll 实例,并设置为水平触发模式
    ev.events = EPOLLIN;  // EPOLLIN: 可读事件(水平触发)
    ev.data.fd = file_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, file_fd, &ev) == -1) {
        perror("epoll_ctl");
        exit(EXIT_FAILURE);
    }

    while (1) {
        nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }

        for (int i = 0; i < nfds; i++) {
            if (events[i].events & EPOLLIN) {
                // 处理文件读取
                handle_file_read(events[i].data.fd);
            }
        }
    }

    close(epoll_fd);
    close(file_fd);
    return 0;
}

对比分析:

ET 实现逻辑: 每次通知时,必须一次性读取所有数据(直到没有更多数据为止)。如果不这样做,epoll 不会再通知你有数据可读了。

void handle_client_request(int fd) {
    char buf[512];
    ssize_t bytes_read;

    while (1) {
        bytes_read = read(fd, buf, sizeof(buf));
        
        if (bytes_read == -1) {
            if (errno == EAGAIN) {
                // 数据读取完毕
                return;
            }
            perror("read");
            return;
        }

        if (bytes_read == 0) {
            // 客户端关闭连接
            printf("Connection closed by client\\n");
            close(fd);
            return;
        }

        // 处理读取到的数据
        buf[bytes_read] = '\\0';  // 确保数据以 null 结尾
        printf("Received request: %s\\n", buf);
    }
}

LT 的实现逻辑是:读取数据,即使没有读完,也可以退出,下一次还是会触发的要求你调用的读取函数的。

void handle_file_read(int fd) {
    char buf[512];
    ssize_t bytes_read = read(fd, buf, sizeof(buf));

    if (bytes_read == -1) {
        if (errno == EAGAIN) {
            // 数据读取完毕
            return;
        }
        perror("read");
    } else if (bytes_read == 0) {
        // 文件末尾
        printf("EOF reached\\n");
    } else {
        buf[bytes_read] = '\\0';  // Null-terminate the string
        printf("New data in file: %s\\n", buf);
    }
}
  • 每次 read() 只读取一次数据,但即使数据没有完全读取完,epoll 会在数据继续可读时 继续通知程序,直到所有数据被处理完为止。
  • 重要: 由于 epoll 会持续通知程序,程序不必担心错过数据,只要它持续读取即可

实际使用场景

  • 水平触发(LT) 比较适合 简单的、阻塞的程序,如常见的 文件监控 或 低并发网络应用,因为它不要求应用程序在每次通知时都处理完所有数据。
  • 边缘触发(ET) 更适合 高并发、高效的事件驱动系统,如 高性能服务器,它减少了通知的次数,提高了性能,但对应用程序的要求更高,必须确保每次事件处理时读写完所有数据。

多次触发性能消耗的原因:

  • 如果每次 文件描述符的状态满足条件(如有数据可读、可写)时都会通知应用程序(即 水平触发(LT)),那么每次数据变动时,系统都会调用 epoll_wait() 来检查事件。这意味着即使文件描述符的状态没有发生变化,应用程序仍然会被通知,导致系统必须频繁地调用 epoll_wait()
  • 每次系统调用都带来一定的性能开销,尤其是 大量的空轮询 会浪费 CPU 时间。
版权声明:感谢您的阅读,资源整理自网络,如果您发现任何侵权行为,请联系 理科生网 管理人员,管理员将及时删除侵权内容。否则均为 理科生网 原创内容,转载时请务必以超链接(而非纯文本链接)标注来源于理科生网及本文完整链接,感谢!{alertInfo}
Ahmedabad
Kolkata
Hyderabad
后一页 Bangalore 前一页

Random Manga

Ads

نموذج الاتصال