当前位置: 首页> 游戏> 手游 > 在东莞建公司网站_公司网站介绍模板 html_网站关键词全国各地的排名情况_搜索引擎的使用方法和技巧

在东莞建公司网站_公司网站介绍模板 html_网站关键词全国各地的排名情况_搜索引擎的使用方法和技巧

时间:2025/7/11 15:52:37来源:https://blog.csdn.net/Linveee/article/details/146373911 浏览次数:1次
在东莞建公司网站_公司网站介绍模板 html_网站关键词全国各地的排名情况_搜索引擎的使用方法和技巧

epoll(Linux特有的高效I/O复用机制)

1、历史

epoll() Linux 2.6 内核引入的高性能 I/O 多路复用机制,首次发布于 2002 年。它是为了解决 select() 和 poll() 在高并发场景下的性能瓶颈而设计的,特别适合处理大量并发连接

epoll() 的引入标志着 Linux 在 I/O 多路复用技术上的重大突破成为现代高并发网络编程的核心技术

2、相比于 select() 和 poll() 的优化

① 事件驱动模型:epoll() 使用事件驱动模型,内核通过回调机制通知应用程序哪些文件描述符就绪,避免了 select() 和 poll() 的轮询开销,显著提升了性能。

② 高效内存管理:epoll() 使用红黑树和链表管理文件描述符,减少了内存拷贝和遍历的开销,特别适合处理大量并发连接。

③ 支持边缘触发与水平触发:epoll() 提供了边缘触发(ET)和水平触发(LT)两种模式,用户可以根据需求选择更高效的处理方式,进一步优化性能。

④ 无文件描述符数量限制:epoll() 没有 select() 和 poll() 的文件描述符数量限制,能够高效处理数十万甚至上百万的并发连接。

3、性能瓶颈

① 事件驱动模型的复杂性:epoll() 的实现基于事件驱动模型,虽然性能优异,但其内部机制(如红黑树和链表的管理)较为复杂,对内核开发者的要求较高。

② 边缘触发的潜在问题:epoll() 支持边缘触发(ET)模式,虽然效率更高,但需要应用程序确保一次性处理完所有就绪事件,否则可能导致事件丢失,增加了编程的复杂性。

③ 平台依赖性:epoll() 是 Linux 特有的机制,无法直接移植到其他操作系统(如 Windows 或 macOS),限制了其跨平台应用。

4、意义

epoll() 的出现彻底解决了 select() 和 poll() 在高并发场景下的性能瓶颈,成为 Linux 下最先进的 I/O 多路复用机制。它不仅显著提升了系统的并发处理能力和资源利用率,还为现代高并发网络服务器(如 Nginx、Redis 等)提供了强大的技术支持。epoll() 的引入标志着 Linux 在 I/O 多路复用技术上的成熟,推动了高性能网络编程的发展。

 5、epoll的组成

epoll主要由三个函数一个结构体组成。

(1)函数

epoll 的核心是通过三个主要的操作接口来管理文件描述符。

1)、epoll_create

创建一个 epoll 实例。

参数

int epoll_create(int size);

size: 该参数在现代的 Linux 系统中已不再实际使用(不影响 epoll 的工作),其值可以设置为任意正数,但通常我们设置为 1。它的最初意义是指定内核内部用于存储事件的空间大小,然而在现代的内核中,这个参数的作用已经被废弃。所以你可以传入任意一个数值。

返回值

成功时,返回一个 epoll 实例的文件描述符(int 类型)。

失败时,返回 -1,并且设置 errno 来说明错误。

头文件

 2)、epoll_ctl

对文件描述符进行控制(添加、删除、修改)。

参数

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epfd: 由 epoll_create 或 epoll_create1 返回的 epoll 实例的文件描述符。

op: 操作类型,指定你对文件描述符 fd 进行的操作。它可以是以下三种之一:

EPOLL_CTL_ADD:将 fd 加入到 epoll 实例中进行事件监听。

EPOLL_CTL_MOD:修改已经注册的 fd 的事件(例如添加更多事件监听)。

EPOLL_CTL_DEL:将 fd 从 epoll 实例中删除。

fd: 要操作的文件描述符,通常是套接字(socket)或文件(file)。

event: 一个指向 struct epoll_event 结构体的指针,表示你希望监控的事件。

返回值

成功时,返回 0。

失败时,返回 -1,并设置 errno。

头文件

3)、epoll_wait

等待事件的发生。

参数

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

epfd: 由 epoll_create 或 epoll_create1 返回的 epoll 实例的文件描述符。

events: 一个指向 epoll_event 数组的指针,用于存放发生的事件。当有事件发生时,epoll_wait 会将这些事件信息填充到该数组中。

maxevents: 数组 events 中可以存放的最大事件数量。如果发生的事件数量超过这个值,epoll_wait 只会返回最多 maxevents 个事件。通常,这个值设为系统允许的最大值,或者根据实际需要来设置。

timeout: 超时时间(单位:毫秒),指定 epoll_wait 在等待事件时的最长等待时间。如果是 -1,则表示无限期阻塞,直到事件发生。如果是 0,则立即返回,不阻塞。如果是正数,表示等待的最大毫秒数。

P.S.   epoll_wait 是阻塞式的,它会一直等待直到有事件发生或超时。在高并发场景下,你可以通过调整 timeout 来控制等待的策略。如果你设置了 timeout = 0,它就会变成一个非阻塞的调用,立刻返回并检查是否有事件。timeout = -1 表示会一直等待,直到有事件。

返回值

成功时,返回发生的事件数(即填充到 events 数组中的事件数量)。

失败时,返回 -1,并设置 errno。

头文件

简单使用

struct epoll_event ev;
ev.events = EPOLLIN;    // 设置监听可读事件
ev.data.fd = sockfd;    // 将套接字文件描述符保存到 `data.fd` 字段中// 将套接字文件描述符添加到 epoll 实例中进行监听
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev);// 等待事件发生
struct epoll_event events[MAX_EVENTS];
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);  // 阻塞等待

(2)结构体

epoll_event 是 epoll API 中一个非常重要的结构体,它用于描述和传递与文件描述符相关的事件信息。当你使用 epoll_ctl 来注册、修改或删除事件,或者使用 epoll_wait 来等待事件发生时,都会涉及到 epoll_event 结构体。

struct epoll_event {uint32_t events;    // 监听的事件类型epoll_data_t data;  // 用户数据(通常是文件描述符或自定义数据)
};

头文件

这个结构体包含两个字段:events data

①events 字段

events 是一个 uint32_t 类型的字段,用于指定要监听的事件类型,可以是以下一些常见的事件标志(可以单独使用,也可以按位 OR 组合使用):

EPOLLIN: 可读事件,表示文件描述符可以读取数据。

EPOLLOUT: 可写事件,表示文件描述符可以写入数据。

EPOLLERR: 错误事件,表示文件描述符发生了错误。

EPOLLHUP: 挂起事件,表示文件描述符的连接已被挂起或关闭。

EPOLLET: 边缘触发模式(Edge Triggered),这个标志告诉 epoll 以边缘触发模式处理事件,而非默认的水平触发模式(Level Triggered)。如果选择边缘触发模式,只有在事件从未被检测到的状态变为可处理状态时才会通知一次,而不是每次都通知(适用于非阻塞 I/O)。

使用按位 OR 操作符将多个事件标志组合:

ev.events = EPOLLIN | EPOLLET;  // 监听可读事件,使用边缘触发模式

②data 字段

data 是一个联合体类型 epoll_data_t,它用于存储与该事件相关的用户自定义数据。通常,它用来存储文件描述符、指针或其他你需要的上下文信息。

epoll_data_t 定义如下:

typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64;
} epoll_data_t;

data 可以存储不同类型的数据,具体使用哪种类型取决于你如何设计。常用的是存储文件描述符 fd,用于标识哪个文件描述符发生了事件。

示例

#include <sys/epoll.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>#define MAX_EVENTS 10int main() {int epoll_fd = epoll_create(1); // 创建 epoll 实例if (epoll_fd == -1) {perror("epoll_create");return -1;}// 假设我们有一个文件描述符 fdint fd = open("testfile.txt", O_RDONLY);if (fd == -1) {perror("open");close(epoll_fd);return -1;}// 创建 epoll_event 结构体struct epoll_event ev;ev.events = EPOLLIN | EPOLLET; // 监听可读事件,并使用边缘触发模式ev.data.fd = fd;               // 将文件描述符存储到 epoll_event 的 data.fd 字段// 将文件描述符 fd 添加到 epoll 实例中进行监听if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {perror("epoll_ctl");close(fd);close(epoll_fd);return -1;}// 等待事件发生struct epoll_event events[MAX_EVENTS];int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); // 阻塞等待if (nfds == -1) {perror("epoll_wait");close(fd);close(epoll_fd);return -1;}// 处理返回的事件for (int i = 0; i < nfds; i++) {if (events[i].events & EPOLLIN) {printf("File descriptor %d is ready for reading\n", events[i].data.fd);}}// 关闭文件描述符和 epoll 实例close(fd);close(epoll_fd);return 0;
}

epoll_event 结构体常见用法

监听多个文件描述符:可以将多个文件描述符的事件信息存储在 epoll_event 数组中,以便一次性处理多个事件。

存储自定义数据:除了存储文件描述符,你还可以将自定义的数据(例如连接的客户端地址)存储到 data 字段中,帮助在事件处理时识别是哪一类事件。

边缘触发与水平触发:通过设置 EPOLLET 标志,你可以让 epoll 使用边缘触发模式,这样在事件发生时只会触发一次,而不是每次事件状态变化时都触发。这个模式适用于处理高效的非阻塞 I/O。

P.S.边缘触发与水平触发

 6、epoll 的工作原理

①注册文件描述符: 当应用程序希望监控某个文件描述符的 I/O 事件时,调用 epoll_ctl 向 epoll 实例注册文件描述符,并指定所需的事件类型(如 EPOLLIN, EPOLLOUT, EPOLLERR 等)。

②触发事件: 当内核检测到某个文件描述符上的事件发生时,它会将该事件放入事件队列中。如果多个事件同时发生,它们会被依次添加到事件队列中。

③等待事件: 应用程序通过 epoll_wait 等待事件的发生。调用会阻塞直到至少有一个事件发生,或者超时。

④处理事件: 一旦 epoll_wait 返回事件,应用程序可以处理这些事件并继续与文件描述符交互。

 7、对比(select、poll、epoll)

机制版本/时间优化点与特性意义与作用
select1980年代(POSIX)轮询机制,支持多个文件描述符,存在文件描述符数量限制提供了基本的多路复用I/O方式,但性能在大量文件描述符时低效。
poll1990年代(POSIX)解决了文件描述符数量限制,仍然使用轮询机制改进了select(),但依然是基于轮询机制,性能在大量并发时不足。
epoll2002年(Linux 2.5)基于事件驱动,无需轮询,支持边缘触发与水平触发高效的I/O复用机制,适用于高并发环境,极大提升了Linux网络I/O的性能。

 8、案例

实现了一个简单的 基于 epoll 的并发服务器,可以接收多个客户端的连接,并处理客户端的请求(回显功能)。通过 epoll 实现了高效的事件驱动模型,可以处理大量并发连接。

#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#include <sys/epoll.h>int main(){// 创建 socketint sockfd = socket(AF_INET, SOCK_STREAM, 0);// 设置服务器地址struct sockaddr_in servaddr;servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //0.0.0.0servaddr.sin_port = htons(2000);  //0-1023// 绑定 socketif (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){printf("bind failed:%s\n", strerror(errno));close(sockfd);return 1;}// 监听连接listen(sockfd, 10);printf("listen finished\n");int epfd = epoll_create(1);struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = sockfd;epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);while (1) {struct epoll_event events[1024] = {0};int nready = epoll_wait(epfd, events, 1024, -1);int i = 0;for (i = 0;i < nready;i ++) {int connfd = events[i].data.fd;if (connfd == sockfd) {int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("accept finshed: %d\n", clientfd);ev.events = EPOLLIN;ev.data.fd = clientfd;epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);} else if (events[i].events & EPOLLIN) {char buffer[1024] = {0};int count = recv(connfd, buffer, 1024, 0);if (count == 0) { // disconnectprintf("client disconnect: %d\n", connfd);close(connfd);epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);continue;}printf("RECV: %s\n", buffer);count = send(connfd, buffer, count, 0);printf("SEND: %d\n", count);}}}printf("listen finished\n"); 
close(sockfd);getchar();
printf("exit\n");
return 0;
}

9、总结

epoll 是 Linux 高性能网络编程的核心技术之一,它通过高效的事件通知机制解决了传统 select 和 poll 的性能瓶颈,使网络编程从IO驱动进化到事件驱动,推动了高并发服务器的发展。其设计体现了操作系统对现代网络应用的深度优化,是构建高性能、高扩展性网络服务的基石。

10、资料参考 

0voicehttps://github.com/0voice

关键字:在东莞建公司网站_公司网站介绍模板 html_网站关键词全国各地的排名情况_搜索引擎的使用方法和技巧

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: