epoll 里面的et, lt
在Linux网络编程中,epoll
是一种高效的事件驱动I/O多路复用机制,用于管理大量的文件描述符(通常是套接字)并监控它们上的事件。epoll
支持两种工作模式:边缘触发(Edge-Triggered,ET)和水平触发(Level-Triggered,LT)。下面我将详细解释这两种模式,并提供示例说明。
- 边缘触发(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; }
- 水平触发(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; }
总之,
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)。这两种模式在使用上有一些区别和联系,下面详细解释它们以及各自的应用场景:
-
边缘触发(Edge-Triggered,ET):
- 边缘触发模式只在文件描述符状态发生变化时通知应用程序,而且只通知一次。这意味着如果你没有处理所有可用的数据或事件,
epoll
不会再次通知你,直到下一个状态变化。 - 用于ET模式的
epoll_wait
函数仅在文件描述符的状态从不可读/不可写变为可读/可写时返回。
应用场景:
- 高性能场景:适用于高性能网络服务器,因为它要求应用程序在通知后立即处理数据,以避免遗漏任何事件。
- 高并发场景:ET模式适用于需要处理大量并发连接的服务器,因为它可以有效减少不必要的事件通知,提高性能。
- 边缘触发模式只在文件描述符状态发生变化时通知应用程序,而且只通知一次。这意味着如果你没有处理所有可用的数据或事件,
-
水平触发(Level-Triggered,LT):
- 水平触发模式在文件描述符上的状态变化仍然有效,即使状态没有变化,
epoll_wait
也会一直返回。这意味着如果你没有处理所有可用的数据或事件,epoll_wait
会返回,并在下次调用时再次返回。 - 应用程序需要自己维护事件的状态,以确保所有数据都被处理。
应用场景:
- 一般应用场景:LT模式适用于一般的应用程序,因为它的工作方式更容易理解和掌握,不容易出现遗漏事件的情况。
- 软件可维护性要求较高的场景:LT模式相对容易管理,更容易编写可维护的代码,因为不需要过多关注事件的精确触发。
- 水平触发模式在文件描述符上的状态变化仍然有效,即使状态没有变化,
区别和联系:
- 主要区别在于事件通知的方式和处理方式。ET模式只通知状态变化,而LT模式通知状态变化和保持通知直到处理完毕。
- ET模式要求应用程序在每次事件通知后处理所有可用数据,否则可能导致事件丢失。LT模式不会丢失事件,但需要应用程序自己维护事件状态。
- ET模式通常更高效,因为它减少了事件通知的次数,但要求应用程序高效地处理通知。
- LT模式相对容易理解和使用,适用于一般的网络编程任务,但可能会产生更多的事件通知。
选择使用哪种模式取决于应用程序的需求和性能要求。在高性能、高并发的场景中,ET模式通常更有优势。在一般应用中,LT模式可能更容易编写和维护。无论使用哪种模式,都需要小心处理事件以确保正确性和性能。