epoll 里面的et, lt

在Linux网络编程中,epoll是一种高效的事件驱动I/O多路复用机制,用于管理大量的文件描述符(通常是套接字)并监控它们上的事件。epoll支持两种工作模式:边缘触发(Edge-Triggered,ET)和水平触发(Level-Triggered,LT)。下面我将详细解释这两种模式,并提供示例说明。

  1. 边缘触发(Edge-Triggered,ET):
    • 边缘触发模式仅在文件描述符状态发生变化时通知应用程序。这意味着如果你没有处理所有可用的数据或事件,epoll不会再次通知你,直到下一个状态变化。
    • 用于ET模式的epoll_wait函数仅在文件描述符的状态从不可读/不可写变为可读/可写时返回。
    • ET模式要求应用程序在通知后立即处理数据,以避免遗漏任何事件。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>

int main() {
    int epoll_fd = epoll_create1(0);
    struct epoll_event event;
    event.events = EPOLLIN | EPOLLET; // ET模式
    event.data.fd = STDIN_FILENO; // 监视标准输入

    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
        perror("epoll_ctl");
        exit(EXIT_FAILURE);
    }

    struct epoll_event events[10];
    while (1) {
        int num_events = epoll_wait(epoll_fd, events, 10, -1);
        if (num_events == -1) {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }
        for (int i = 0; i < num_events; i++) {
            if (events[i].data.fd == STDIN_FILENO) {
                // 处理标准输入可读事件
                char buffer[1024];
                ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer));
                if (bytes_read == -1) {
                    perror("read");
                    exit(EXIT_FAILURE);
                }
                if (bytes_read == 0) {
                    // EOF,标准输入关闭
                    printf("Standard input closed.\n");
                    exit(EXIT_SUCCESS);
                }
                // 处理读取的数据
                printf("Read %zd bytes: %.*s\n", bytes_read, (int)bytes_read, buffer);
            }
        }
    }
    return 0;
}
View Code

 

  1. 水平触发(Level-Triggered,LT):
    • 水平触发模式在文件描述符上的状态变化仍然有效,但epoll_wait会一直返回,直到描述符上的事件被处理,即使只处理了部分数据。
    • 应用程序需要自己维护事件的状态,以确保所有数据都被处理。

示例:

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

int main() {
    int epoll_fd = epoll_create1(0);
    struct epoll_event event;
    event.events = EPOLLIN; // LT模式
    event.data.fd = STDIN_FILENO; // 监视标准输入

    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
        perror("epoll_ctl");
        exit(EXIT_FAILURE);
    }

    struct epoll_event events[10];
    while (1) {
        int num_events = epoll_wait(epoll_fd, events, 10, -1);
        if (num_events == -1) {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }
        for (int i = 0; i < num_events; i++) {
            if (events[i].data.fd == STDIN_FILENO) {
                // 处理标准输入可读事件
                char buffer[1024];
                ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer));
                if (bytes_read == -1) {
                    perror("read");
                    exit(EXIT_FAILURE);
                }
                if (bytes_read == 0) {
                    // EOF,标准输入关闭
                    printf("Standard input closed.\n");
                    exit(EXIT_SUCCESS);
                }
                // 处理读取的数据
                printf("Read %zd bytes: %.*s\n", bytes_read, (int)bytes_read, buffer);
            }
        }
    }
    return 0;
}
View Code

总之,

ET模式只在文件描述符状态变化时通知应用程序。

这种模式下,当文件描述符状态发生变化(比如,新的数据到达,或者数据已经被消费)时,epoll_wait()会返回相应的事件。在处理这些事件时,必须要使用非阻塞I/O,否则如果数据没有读或写完,会导致阻塞,进而影响程序效率。

 

LT模式在文件描述符上的事件仍然有效,直到应用程序处理完它们。

这种模式下,当文件描述符状态发生变化,并且这个变化的状态一直保持到epoll_wait()调用时,epoll_wait()才会返回相应的事件。这种模式下,可以使用阻塞I/O

 

选择哪种模式取决于应用程序的需求和设计。

 

 

 

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

int main() {
    // 创建 epoll 实例并获取 epoll 文件描述符
    int epoll_fd = epoll_create1(0);

    // 创建 epoll_event 结构体,用于描述要监视的事件
    struct epoll_event event;
    
    // 设置要监视的事件,这里使用 EPOLLIN 表示可读事件,EPOLLET 表示边缘触发模式
    event.events = EPOLLIN | EPOLLET; // ET模式
    event.data.fd = STDIN_FILENO; // 监视标准输入

    // 将事件添加到 epoll 实例中进行监视
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
        perror("epoll_ctl");
        exit(EXIT_FAILURE);
    }

    // 创建用于存储触发事件的 epoll_event 数组
    struct epoll_event events[10];
    
    // 进入事件循环
    while (1) {
        // 等待事件发生,epoll_wait 会一直阻塞直到有事件发生或出错
        int num_events = epoll_wait(epoll_fd, events, 10, -1);
        if (num_events == -1) {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }
        
        // 处理触发的事件
        for (int i = 0; i < num_events; i++) {
            if (events[i].data.fd == STDIN_FILENO) {
                // 如果事件是标准输入可读事件,进行处理
                
                // 创建缓冲区用于读取数据
                char buffer[1024];
                
                // 读取数据到缓冲区,bytes_read 存储实际读取的字节数
                ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer));
                if (bytes_read == -1) {
                    perror("read");
                    exit(EXIT_FAILURE);
                }
                
                // 如果读取到了 EOF,标准输入关闭,退出程序
                if (bytes_read == 0) {
                    printf("Standard input closed.\n");
                    exit(EXIT_SUCCESS);
                }
                
                // 处理读取的数据
                printf("Read %zd bytes: %.*s\n", bytes_read, (int)bytes_read, buffer);
            }
        }
    }
    return 0;
}

 

  • epoll_create1(0) 创建一个 epoll 实例,并返回一个 epoll 文件描述符。
  • struct epoll_event event 创建一个 epoll_event 结构体,用于描述要监视的事件。
  • event.events 设置要监视的事件类型,这里使用 EPOLLIN 表示可读事件和 EPOLLET 表示边缘触发模式。
  • event.data.fd 设置要监视的文件描述符,这里是标准输入。
  • epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) 将事件添加到 epoll 实例中进行监视。
  • epoll_wait(epoll_fd, events, 10, -1) 等待事件发生,一旦有事件发生,它会填充 events 数组并返回触发事件的数量。
  • 在事件循环中,我们遍历 events 数组,检查触发的事件是否是标准输入的可读事件。
  • 如果是标准输入的可读事件,我们创建一个缓冲区 buffer,然后使用 read 函数读取数据到缓冲区。
  • 如果读取到了 EOF,说明标准输入关闭,我们输出消息并退出程序。
  • 否则,我们处理读取到的数据并输出它。

----------------------------------

 

 

在Linux网络编程中,epoll是一种高效的事件驱动I/O多路复用机制,它支持两种工作模式:边缘触发(Edge-Triggered,ET)和水平触发(Level-Triggered,LT)。这两种模式在使用上有一些区别和联系,下面详细解释它们以及各自的应用场景:

  1. 边缘触发(Edge-Triggered,ET):

    • 边缘触发模式只在文件描述符状态发生变化时通知应用程序,而且只通知一次。这意味着如果你没有处理所有可用的数据或事件,epoll不会再次通知你,直到下一个状态变化。
    • 用于ET模式的epoll_wait函数仅在文件描述符的状态从不可读/不可写变为可读/可写时返回。

    应用场景:

    • 高性能场景:适用于高性能网络服务器,因为它要求应用程序在通知后立即处理数据,以避免遗漏任何事件。
    • 高并发场景:ET模式适用于需要处理大量并发连接的服务器,因为它可以有效减少不必要的事件通知,提高性能。
  2. 水平触发(Level-Triggered,LT):

    • 水平触发模式在文件描述符上的状态变化仍然有效,即使状态没有变化,epoll_wait也会一直返回。这意味着如果你没有处理所有可用的数据或事件,epoll_wait会返回,并在下次调用时再次返回。
    • 应用程序需要自己维护事件的状态,以确保所有数据都被处理。

    应用场景:

    • 一般应用场景:LT模式适用于一般的应用程序,因为它的工作方式更容易理解和掌握,不容易出现遗漏事件的情况。
    • 软件可维护性要求较高的场景:LT模式相对容易管理,更容易编写可维护的代码,因为不需要过多关注事件的精确触发。

区别和联系:

  • 主要区别在于事件通知的方式和处理方式。ET模式只通知状态变化,而LT模式通知状态变化和保持通知直到处理完毕。
  • ET模式要求应用程序在每次事件通知后处理所有可用数据,否则可能导致事件丢失。LT模式不会丢失事件,但需要应用程序自己维护事件状态。
  • ET模式通常更高效,因为它减少了事件通知的次数,但要求应用程序高效地处理通知。
  • LT模式相对容易理解和使用,适用于一般的网络编程任务,但可能会产生更多的事件通知。

选择使用哪种模式取决于应用程序的需求和性能要求。在高性能、高并发的场景中,ET模式通常更有优势。在一般应用中,LT模式可能更容易编写和维护。无论使用哪种模式,都需要小心处理事件以确保正确性和性能。

posted @ 2023-09-13 17:25  He_LiangLiang  阅读(23)  评论(0编辑  收藏  举报