Linux
Linux software install
Google repo的使用
Vector AP StartApplication编译脚本解析
Yocto的cmake版本升级
不能自动安装的解决方式
S32G-BSP35.0-SDK使用方法
S32G从SDK生成文件系统的制作过程
Linux Samba设置
Linux添加双网卡
S32G USB-Redirector安装指南
VS code自动生成Doxygen格式注释
Linux下使用多线程下载
使用pandoc 生成带中文的pdf
minicom无法输入的问题解决办法
使用 systemd unit 添加一条路由规则
CMake 教程
步骤 1:基本起点
步骤 2:添加lib库
步骤 3:为库添加使用要求
步骤 4:添加生成器表达式
步骤 5:安装和测试
步骤 6:添加支持测试仪表板
步骤 7: 添加系统内省
步骤 8:自定义命令和生成的文
步骤 9:打包安装程序
步骤 10:选择静态库或共享库
步骤 11:添加导出配置
步骤 12:打包 Debug 和 Release
添加虚拟网卡
Vector AP 去掉防篡改校验
Vector AP startapplication编译与使用
Vector AP问题汇总
Vector AP大型项目开发流程
Vector AP EM
Vector AP 最简单的开发示例
Linux kernel 版本降级
Vector AP StartApplicaiton
startappplication-machine-design
startapplicaiton-machine-integration
amsr-vector-fs-em-executionmanager-design
amsr-vector-fs-em-executionmanager
Vector AP 复杂模型的开发
第一章 Machine和MachineDesign
第二章 Execute Manager
第三章 Log
第四章 State Manager
第五章 State Manager 源码的理解
第六章 Someip daemon
第七章 IPC Service Discovery Daemon
crond的使用方法
解决蓝牙鼠标在 Ubuntu 中单位时间内断开的问题
VPS服务器自建教程
v2rayA的客户端使用配置
GDB调试指南入门篇:揭开程序运行世界的神秘面纱
GDB调试指南高级篇:成为调试专家,掌控程序的命运
Linux安装PyQt5并配置Qt Designer
ADB 命令大全
GoogleTest(一)
GoogleTest(二)简单的TEST宏
GoogleTest(三)简单类函数测试
C++ Template
1. 函数模板
2. 类模板
3. 非类型模板参数
软件版本号规范
EPOLL
C++手札
C++ 使用{}初始化有哪些好处?
现代 C++ decltype 深入解析
函数对象(functor)
Linux性能剖析:CPU、内存、网络与I/O压力测试
AP StateManager
C++ Lambda表达式
C++ 中的Lambda表达式
Lambda 表达式语法
Lambda 表达式的示例
手动发送UDP数据包
pyqt5生成的UI界面不能输入中文
自己搭建repo镜像
摄影
Sony仿富士PP值设置
诗词歌赋
本文档使用 MrDoc 发布
-
+
首页
EPOLL
`EPOLL` 是 Linux 内核提供的一种高效的 I/O 多路复用机制,用于监控多个文件描述符上的事件,以实现高效的网络或其他 I/O 操作管理。它在处理大量并发连接时表现优异,尤其适用于高性能服务器程序。 ### EPOLL 的核心概念 1. **多路复用**: - `EPOLL` 可以同时监控多个文件描述符(如 socket、文件、管道等)上的 I/O 事件(如读、写、异常等)。当任何一个文件描述符的状态发生变化(例如有数据可读或可写),`EPOLL` 就会通知应用程序。 2. **水平触发(Level-triggered, LT)与边缘触发(Edge-triggered, ET)**: - **水平触发(LT)**: 当文件描述符处于就绪状态时,`EPOLL` 会持续通知应用程序,直到该状态被处理。这是 `EPOLL` 的默认模式,类似于 `select` 和 `poll` 的行为。 - **边缘触发(ET)**: 只有在文件描述符从非就绪状态变为就绪状态时才会通知应用程序,且仅通知一次。应用程序需要在收到通知后及时处理所有就绪状态,否则可能会错过后续的事件。 3. **事件驱动模型**: - `EPOLL` 使用事件驱动的模型,即应用程序可以注册感兴趣的事件(如 `EPOLLIN`、`EPOLLOUT` 等),当这些事件发生时,`EPOLL` 将通知应用程序。这使得应用程序可以在事件发生前进入休眠状态,减少 CPU 使用率。 ### EPOLL 与其他多路复用机制的对比 - **`select` 和 `poll`**: 传统的多路复用机制,如 `select` 和 `poll`,都存在一些缺点,如最大文件描述符数量限制、每次调用时需要重新传递所有文件描述符等。它们的性能在处理大量文件描述符时会显著下降。 - **`epoll`**: 与 `select` 和 `poll` 相比,`epoll` 具有以下优势: - 没有最大文件描述符数量的限制(只受限于系统配置和内存)。 - 注册的文件描述符及其事件只需传递一次(通过 `epoll_ctl`),而不需要在每次调用时都重新传递。 - 在边缘触发模式下,`epoll` 可以极大地减少 I/O 函数调用次数,提高效率。 ### EPOLL 的主要系统调用 1. **`epoll_create`/`epoll_create1`**: - 创建一个 `epoll` 实例,返回一个 `epoll` 文件描述符。 2. **`epoll_ctl`**: - 用于控制 `epoll` 实例的行为,可以向 `epoll` 实例中添加、修改或删除文件描述符。 3. **`epoll_wait`**: - 等待事件的发生。它会阻塞调用,直到有文件描述符上的事件发生或超时。 ### 使用场景 `EPOLL` 非常适用于高并发的网络服务器或需要同时监控多个文件描述符的应用场景。例如: - Web服务器(如 Nginx) - 数据库服务器 - 实时通信系统 - 高性能网络代理 通过 `EPOLL`,开发者可以高效地处理成千上万的并发连接,而不会对系统资源造成过大的开销。 ### EPOLL的事件类型种类 `<sys/epoll.h>` 头文件中定义的 `enum EPOLL_EVENTS` 枚举包含了多种 `epoll` 事件标志,以下是每种事件的解释: 1. **EPOLLIN (0x001)**: 表示对应的文件描述符有数据可读(包括普通数据和优先数据)。 2. **EPOLLPRI (0x002)**: 表示对应的文件描述符有紧急数据可读(通常是带外数据,Out-of-band data)。 3. **EPOLLOUT (0x004)**: 表示对应的文件描述符可以写入数据。 4. **EPOLLRDNORM (0x040)**: 表示对应的文件描述符有普通数据可读。 5. **EPOLLRDBAND (0x080)**: 表示对应的文件描述符有优先数据可读。 6. **EPOLLWRNORM (0x100)**: 表示对应的文件描述符可以写入普通数据。 7. **EPOLLWRBAND (0x200)**: 表示对应的文件描述符可以写入优先数据。 8. **EPOLLMSG (0x400)**: 已过时。用于消息可用时的通知。 9. **EPOLLERR (0x008)**: 表示对应的文件描述符发生错误。 10. **EPOLLHUP (0x010)**: 表示对应的文件描述符挂起事件(即连接被关闭)。 11. **EPOLLRDHUP (0x2000)**: 表示对方关闭连接或半关闭(即对方调用了 `shutdown`)。 12. **EPOLLEXCLUSIVE (1u << 28)**: 表示在多线程环境中,确保只有一个线程处理事件,其他线程不会被唤醒。主要用于提高并发性能,避免不必要的上下文切换。 13. **EPOLLWAKEUP (1u << 29)**: 防止系统进入睡眠状态。如果设置了此标志,则在事件发生时,如果系统处于空闲状态,将唤醒系统。 14. **EPOLLONESHOT (1u << 30)**: 一次性触发事件。事件触发后,会自动停止监听该文件描述符,必须手动重新启用监听。 15. **EPOLLET (1u << 31)**: 边缘触发模式。表示事件只会在状态变化时触发一次,这种模式下必须确保处理程序读取所有可用数据。 这些事件可以组合使用,例如 `EPOLLIN | EPOLLET` 可以用于在边缘触发模式下监听读事件。 ### 示例 ```c #include <stdio.h> #include <stdlib.h> #include <sys/epoll.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <errno.h> #include <arpa/inet.h> #define MAX_EVENTS 10 // epoll_wait 函数每次可以返回的最大事件数 #define BUF_SIZE 1024 // 读写缓冲区的大小 #define PORT 8080 // 服务器监听的端口号 int main() { int epoll_fd, listen_fd, conn_fd; struct epoll_event ev, events[MAX_EVENTS]; // 用于 epoll 的事件结构体 struct sockaddr_in server_addr; // 服务器的地址结构 char buf[BUF_SIZE]; // 用于数据读写的缓冲区 int nfds, n; // nfds 是 epoll_wait 返回的事件数,n 是循环计数器 // 创建一个 epoll 实例,返回一个文件描述符 epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1"); exit(EXIT_FAILURE); } // 创建一个 TCP socket,用于监听客户端连接 listen_fd = socket(AF_INET, SOCK_STREAM, 0); if (listen_fd == -1) { perror("socket"); exit(EXIT_FAILURE); } // 设置 socket 选项:地址复用,防止在重新启动程序时出现 "Address already in use" 错误 int opt = 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 初始化服务器地址结构体 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; // 使用 IPv4 server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有可用网络接口 server_addr.sin_port = htons(PORT); // 将主机字节序转换为网络字节序 // 绑定 socket 到指定的 IP 地址和端口号 if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { perror("bind"); close(listen_fd); exit(EXIT_FAILURE); } // 开始监听 socket,允许最多 10 个待处理连接 if (listen(listen_fd, 10) == -1) { perror("listen"); close(listen_fd); exit(EXIT_FAILURE); } // 设置监听 socket 为非阻塞模式 int flags = fcntl(listen_fd, F_GETFL, 0); // 获取当前文件状态标志 fcntl(listen_fd, F_SETFL, flags | O_NONBLOCK); // 设置为非阻塞模式 // 将监听 socket 添加到 epoll 监听列表中,关注可读事件(即有新连接到达) ev.events = EPOLLIN | EPOLLET; // EPOLLIN 表示可读,EPOLLET 表示边缘触发模式 ev.data.fd = listen_fd; // 关联到监听 socket 的文件描述符 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) == -1) { perror("epoll_ctl: listen_fd"); close(listen_fd); close(epoll_fd); exit(EXIT_FAILURE); } // 将标准输入(文件描述符0)添加到 epoll 监听列表中,关注可读事件 ev.events = EPOLLIN; // 仅关注可读事件 ev.data.fd = STDIN_FILENO; // 关联到标准输入 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1) { perror("epoll_ctl: stdin"); close(listen_fd); close(epoll_fd); exit(EXIT_FAILURE); } // 进入事件循环,等待并处理事件 while (1) { // 等待事件的发生,最多返回 MAX_EVENTS 个事件 nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait"); exit(EXIT_FAILURE); } // 处理每个发生的事件 for (n = 0; n < nfds; ++n) { if (events[n].data.fd == STDIN_FILENO) { // 处理标准输入事件 // 从标准输入读取数据 int len = read(STDIN_FILENO, buf, BUF_SIZE); if (len == -1) { perror("read"); exit(EXIT_FAILURE); } buf[len] = '\0'; // 添加字符串终止符 printf("Read from stdin: %s", buf); } else if (events[n].data.fd == listen_fd) { // 处理新连接事件 // 接受所有新连接(由于使用了非阻塞模式,accept 可能不会阻塞) while ((conn_fd = accept(listen_fd, NULL, NULL)) > 0) { printf("Accepted new connection: fd = %d\n", conn_fd); // 将新连接 socket 设置为非阻塞模式 flags = fcntl(conn_fd, F_GETFL, 0); fcntl(conn_fd, F_SETFL, flags | O_NONBLOCK); // 将新连接 socket 添加到 epoll 监听列表中,关注可读事件 ev.events = EPOLLIN | EPOLLET; ev.data.fd = conn_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev) == -1) { perror("epoll_ctl: conn_fd"); close(conn_fd); } } // 如果 accept 返回 -1,且错误是 EAGAIN 或 EWOULDBLOCK,表示所有连接都已被处理 if (conn_fd == -1) { if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("accept"); exit(EXIT_FAILURE); } } } else { // 处理客户端数据事件 int client_fd = events[n].data.fd; // 获取客户端 socket 描述符 int len = read(client_fd, buf, BUF_SIZE); // 读取客户端发送的数据 if (len == -1) { if (errno != EAGAIN && errno != EWOULDBLOCK) { // 处理读取错误 perror("read"); close(client_fd); } } else if (len == 0) { // 如果读到 0 字节,表示客户端已关闭连接 printf("Client disconnected: fd = %d\n", client_fd); close(client_fd); } else { // 处理读取到的数据 buf[len] = '\0'; // 添加字符串终止符 printf("Received from client (fd = %d): %s", client_fd, buf); } } } } // 关闭文件描述符,清理资源 close(listen_fd); close(epoll_fd); return 0; } ``` ### 编译运行 ```shell gcc test.c -o test ./test ``` 输出 
admin
2024年8月12日 09:27
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码