Linux高级I/O:多路转接模型

目录
- 一.常见的IO模型介绍
- 二.多路转接I/O
- 1.select
- 1.1.函数解析
- 1.2. select特点和缺点
- 1.3.基于 select 的多客户端网络服务器
- 2.poll
- 2.1.poll函数解析
- 2.2.poll特点和缺点
- 2.3.基于poll的tcp服务器
- 3.epoll
- 3.1.系列函数解析
- 3.2.epoll原理解析
- 2.3.基于 select 的多客户端网络服务器
- 3.4.epoll的优点
- 三.LT和ET
一.常见的IO模型介绍
在 Linux 中,主要有以下几种 I/O 模型:
- 阻塞 I/O:在阻塞 I/O 模型中,当进程请求 I/O 操作时,它会被挂起,直到该操作完成。这种方式进程在等待 I/O 的同时不能做其他的事。
- 非阻塞 I/O:非阻塞 I/O 允许进程发出 I/O 请求后立即返回,进程可以继续执行其他操作。为了检查 I/O 是否完成,进程需要通过轮询方式不断检查状态,所以也常称为非阻塞轮询IO,这种方式可以减少等待时间,但是在高频率轮询时很消耗cpu资源。
- I/O 多路复用:I/O 多路复用通过使用 select、poll 或 epoll 等系统调用,使一个进程/线程能够同时监视多个文件描述符。该模型在处理多个并发连接时效率较高,适合网络服务器等应用场景。
- 信号驱动 I/O:在信号驱动 I/O 模型中,进程在发出 I/O 请求后可以继续执行其他任务,操作的完成通过信号通知。这种方式可以减少轮询,但是处理起来较为复杂。
- 异步 I/O:异步 I/O 是最先进的模型,进程发出 I/O 请求后立即返回,并在操作完成时通过回调函数或其他机制获得通知。异步 I/O 模型通常提供最高的性能,适合高负载、高并发的应用程序。
二.多路转接I/O
1.select
1.1.函数解析
#include <sys/select.h>int pselect(int nfds, fd_set *restrict readfds,fd_set *restrict writefds, fd_set *restrict errorfds,const struct timespec *restrict timeout,const sigset_t *restrict sigmask);int select(int nfds, fd_set *restrict readfds,fd_set *restrict writefds, fd_set *restrict errorfds,struct timeval *restrict timeout);void FD_CLR(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_ZERO(fd_set *fdset);
- nfds:待监视的文件描述符最大值 + 1。
- readfds:指向一组需要监视“可读”事件的文件描述符集合。
- writefds:指向一组需要监视“可写”事件的文件描述符集合。
- exceptfds:指向一组需要监视异常事件的文件描述符集合。
- timeout:timeout 是一个 struct timeval 结构体,包含等待的秒数和微秒数,如果在超时时间内有文件描述符变为可用状态,select 会返回,返回值是可用的文件描述符数量。如果发生错误,select 返回 -1,并设置 errno。
- select 函数返回就绪的文件描述符数,如果返回 0 则表示超时。
fd_set的介绍
fd_set 是一个位图结构,用于存储多个文件描述符。select 使用宏来操作 fd_set,常见的宏有:
FD_ZERO(fd_set *set):将文件描述符集合置空。
FD_SET(int fd, fd_set *set):将文件描述符 fd加入集合。
FD_CLR(int fd, fd_set *set):将文件描述符 fd 从集合中移除。
FD_ISSET(int fd, fd_set *set):检查文件描述符 fd 是否在集合中,并且是否准备就绪。
下面是一个简单的使用演示代码:
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(sockfd, &readfds);
int maxfd = sockfd + 1;int result = select(maxfd, &readfds, nullptr, nullptr, nullptr);
if (result > 0) {if (FD_ISSET(sockfd, &readfds)) {// 处理读操作}
}
初始化fd_set集合将需要监控的fd添加进去,接着调用select函数,maxfd是需要监控的fd集合中最大值再加1,select可以同时监控读事件、写事件、异常事件。timeout设置为nullptr标识阻塞等待直到事件有就绪的或者出错。
1.2. select特点和缺点
特点:
- 多路复用:select 允许一个进程同时监控多个文件描述符,等待其中一个或多个变为可读、可写或异常状态。
- 可以处理不同类型的文件描述符:能够监控常规文件、套接字、管道等多种类型的文件描述符。
- 跨平台支持:在许多 Unix-like 操作系统以及 Windows 中都有支持,具有良好的移植性。
缺点:
- 性能瓶颈:当监控的文件描述符数量很大时,select 的性能会显著下降。每次调用 select 都需要遍历所有文件描述符,增加了开销。
- 文件描述符数量限制:select 有文件描述符的数量限制(通常是 1024,取决于类型fd_set的大小),超过这个限制就不能监控更多的描述符。
- 每次调用都需要重置:每次调用 select 都需要重新设置文件描述符集合,这在处理多个连接时会带来额外的开销。
- 无优先级支持:select 不能为不同的文件描述符设置优先级,所有的描述符在监控时是平等的。
- 不适合高并发场景:在高并发情况下(如数千个连接),select 的效率会下降,通常需要使用其他机制(如 poll、epoll 或 kqueue)来更好地处理大量并发连接。
1.3.基于 select 的多客户端网络服务器
下面我们来实现一个简单的基于 select 的 TCP 服务器来窥探他的使用(附上详细注释):
#pragma once#include <iostream>
#include <sys/select.h>
#include <sys/time.h>
#include <string>#include "Socket.hpp"using namespace std;// 默认端口号
static const uint16_t defaultport = 8888;
// 最大文件描述符数量
static const int fd_num_max = (sizeof(fd_set) * 8);
int defaultfd = -1; // 默认文件描述符,表示未使用class SelectServer
{
public:// 构造函数,初始化端口和文件描述符数组SelectServer(uint16_t port = defaultport) : _port(port){for (int i = 0; i < fd_num_max; i++){fd_array[i] = defaultfd; // 初始化所有文件描述符为默认值}}// 初始化服务器,创建套接字、绑定和监听bool Init(){_listensock.Socket(); // 创建套接字_listensock.Bind(_port); // 绑定到指定端口_listensock.Listen(); // 开始监听return true;}// 接受新的连接void Accepter(){// 处理连接事件string clientip; // 客户端IPuint16_t clientport = 0; // 客户端端口int sock = _listensock.Accept(&clientip, &clientport); // 接受连接if (sock < 0) return; // 失败则返回// 将新连接的sock添加到fd_array数组int pos = 1; // 从1开始,0位置为监听套接字for (; pos < fd_num_max; pos++) // 查找可用的位置{if (fd_array[pos] != defaultfd)continue; // 当前位置已被占用,继续查找elsebreak; // 找到可用位置}if (pos == fd_num_max) // 如果数组已满{close(sock); // 关闭新连接}else // 有空位{fd_array[pos] = sock; // 存储新连接的文件描述符}}// 接收数据void Recver(int fd, int pos){char buffer[1024]; // 缓冲区ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // 读取数据if (n > 0) // 有数据到达{buffer[n] = 0; // 结束符cout << "get a messge: " << buffer << endl; // 输出接收到的信息}else if (n == 0) // 客户端关闭连接{close(fd); // 关闭连接fd_array[pos] = defaultfd; // 从数组中移除}else // 发生错误{close(fd); // 关闭连接fd_array[pos] = defaultfd; // 从数组中移除}}// 分发事件void Dispatcher(fd_set &rfds){for (int i = 0; i < fd_num_max; i++) // 遍历文件描述符数组{int fd = fd_array[i];if (fd == defaultfd)continue; // 跳过未使用的描述符if (FD_ISSET(fd, &rfds)) // 检查描述符是否就绪{if (fd == _listensock.Fd()) // 如果是监听套接字{Accepter(); // 处理新连接}else // 其他连接{Recver(fd, i); // 接收数据}}}}// 启动服务器void Start(){int listensock = _listensock.Fd(); // 获取监听套接字的文件描述符fd_array[0] = listensock; // 将监听套接字放入数组的第一个位置while (true){fd_set rfds; // 文件描述符集合FD_ZERO(&rfds); // 清空集合int maxfd = fd_array[0]; // 最大文件描述符for (int i = 0; i < fd_num_max; i++) // 遍历文件描述符数组{if (fd_array[i] == defaultfd)continue; // 跳过未使用的描述符FD_SET(fd_array[i], &rfds); // 将就绪的描述符加入集合if (maxfd < fd_array[i]) // 更新最大文件描述符{maxfd = fd_array[i];}}// 设置超时为 0,非阻塞轮询struct timeval timeout = {0, 0}; // 超时设置为 0// 调用 select 检测事件int n = select(maxfd + 1, &rfds, nullptr, nullptr, timeout);switch (n){case 0:cout << "time out, timeout: " << timeout.tv_sec << "." << timeout.tv_usec << endl; // 超时处理break;case -1:cerr << "select error" << endl; // 错误处理break;default:cout << "get a new link!!!!!" << endl;Dispatcher(rfds); // 处理就绪的事件break;}}}// 析构函数,关闭监听套接字~SelectServer(){_listensock.Close(); // 关闭套接字}private:Sock _listensock; // 监听套接字对象uint16_t _port; // 服务器端口int fd_array[fd_num_max]; // 文件描述符数组,用于维护连接
};
2.poll
2.1.poll函数解析
#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- fds: 传入一个结构体数组,用于保存需要监控的文件描述符以及感兴趣的事件。
- nfds: 数组 fds 中的文件描述符个数。
- timeout: 指定超时时间,单位为毫秒。
- 返回值小于0表示出错,等于表示超时,大于0表示事件就绪。
pollfd 结构体
struct pollfd {int fd; // 文件描述符short events; // 需要监控的事件short revents; // 实际发生的事件,由 poll 填充
};
- fd: 需要监控的文件描述符。
- events: 指定感兴趣的事件,比如 POLLIN(可读)、POLLOUT(可写)等。
- revents: 存储实际发生的事件,由 poll 调用后填充。
events和revents的取值:
| 事件类型 | events 值 | revents 值 | 描述 |
|---|---|---|---|
| 可读事件 | POLLIN | POLLIN | 数据可读,通常指套接字上有数据可读 |
| 可写事件 | POLLOUT | POLLOUT | 数据可写,通常指套接字可以发送数据 |
| 错误事件 | POLLERR | POLLERR | 发生错误,需要处理 |
| 挂起事件 | POLLHUP | POLLHUP | 连接已关闭或挂起 |
| 非法请求 | POLLNVAL | POLLNVAL | 监控的文件描述符无效 |
| 超时 | - | - | 当设置的超时时间到达,但没有事件发生 |
2.2.poll特点和缺点
poll的优点:
- 无文件描述符限制:与 select 不同,poll 不受文件描述符数量的限制,因此可以处理更多的连接。
- 简单性:poll 的使用相对select简单,容易理解和实现。
poll的缺点:
- 线性扫描:在事件发生时,poll 会线性扫描所有文件描述符,效率较低。对于大量文件描述符,响应时间可能变长。
- 性能问题:每次调用 poll 时,都需要传递整个文件描述符数组,性能降低
- 不支持边缘触发:poll **不支持边缘触发模式
2.3.基于poll的tcp服务器
下面我们来实现一个简单的基于poll的 TCP 服务器来窥探他的使用(附上详细注释):
#pragma once#include <iostream>
#include <poll.h> // 引入poll头文件,用于多路复用
#include <sys/time.h> // 时间操作相关
#include "Socket.hpp" // 自定义的Socket类,封装了socket的基本操作using namespace std;static const uint16_t defaultport = 8888; // 默认监听端口号为8888
static const int fd_num_max = 128; // 最大支持的文件描述符数量
int defaultfd = -1; // 默认无效的文件描述符值
const int _no events = 0; // 无事件标志class PollServer
{
public:// 构造函数,初始化监听端口号,并设置poll事件数组PollServer(uint16_t port = defaultport) : _port(port){for (int i = 0; i < fd_num_max; i++) // 初始化所有的文件描述符和事件{_event_fds[i].fd = defaultfd; // 将所有文件描述符设置为默认值_event_fds[i].events = _no events; // 清空所有事件类型_event_fds[i].revents = _no events; // 清空所有返回事件}}// 初始化服务器,创建监听套接字并绑定到指定端口bool Init(){_listensock.Socket(); // 创建监听socket_listensock.Bind(_port); // 绑定到指定端口_listensock.Listen(); // 开始监听return true; // 初始化成功返回true}// 处理新连接的客户端void Accepter(){// 有新的连接事件触发,接收连接string clientip; // 用于存储客户端IP地址uint16_t clientport = 0; // 用于存储客户端端口号int sock = _listensock.Accept(&clientip, &clientport); // 接收客户端连接,返回新socketif (sock < 0) return; // 如果返回值小于0,表示接收失败// 将新连接的socket放入_event_fds数组int pos = 1;for (; pos < fd_num_max; pos++) // 查找空闲的数组位置{if (_event_fds[pos].fd != defaultfd) // 如果当前文件描述符已经被占用,继续寻找continue;elsebreak; // 找到空闲位置退出循环}if (pos == fd_num_max) // 如果位置满了,无法接收更多客户端{close(sock); // 关闭新连接}else{// 将新socket添加到poll事件数组中,监听读事件_event_fds[pos].fd = sock;_event_fds[pos].events = POLLIN; // 监听读事件_event_fds[pos].revents = _no events; // 初始化返回事件PrintFd(); // 打印当前在线的fd列表}}// 处理客户端发送过来的消息void Recver(int fd, int pos){char buffer[1024]; // 接收数据的缓冲区ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // 从客户端读取数据if (n > 0) // 正常读取到数据{buffer[n] = 0; // 将数据末尾补上字符串结束符cout << "get a messge: " << buffer << endl; // 打印接收到的消息}else if (n == 0) // 客户端主动断开连接{close(fd); // 关闭文件描述符_event_fds[pos].fd = defaultfd; // 从事件数组中移除该客户端}else // 读取出错{close(fd); // 关闭文件描述符_event_fds[pos].fd = defaultfd; // 从事件数组中移除该客户端}}// 事件分发器,处理所有准备好的事件void Dispatcher(){for (int i = 0; i < fd_num_max; i++) // 遍历所有的文件描述符{int fd = _event_fds[i].fd;if (fd == defaultfd) // 如果是默认值,说明该位置空闲,跳过continue;if (_event_fds[i].revents & POLLIN) // 如果当前文件描述符有读事件{if (fd == _listensock.Fd()) // 如果是监听socket上的事件,处理新的连接{Accepter(); // 处理新连接}else // 如果是普通客户端socket上的事件{Recver(fd, i); // 接收客户端消息}}}}// 服务器主循环,启动服务器并进入事件处理void Start(){_event_fds[0].fd = _listensock.Fd(); // 将监听socket放入第一个位置_event_fds[0].events = POLLIN; // 监听读事件int timeout = 1000; // 设置poll的超时时间为3秒for (;;){// 调用poll函数,监控事件int n = poll(_event_fds, fd_num_max, timeout);switch (n){case 0: // 超时无事件cout << "time out... " << endl;break;case -1: // 发生错误cerr << "poll error" << endl;break;default: // 有事件发生cout << "get a new link!!!!!" << endl;Dispatcher(); // 分发处理就绪的事件break;}}}// 打印当前在线的文件描述符列表void PrintFd(){cout << "fd list: ";for (int i = 0; i < fd_num_max; i++) // 遍历所有的文件描述符{if (_event_fds[i].fd == defaultfd) // 跳过无效的文件描述符continue;cout << _event_fds[i].fd << " "; // 打印文件描述符}cout << endl;}// 析构函数,关闭监听socket~PollServer(){_listensock.Close();}private:Sock _listensock; // 自定义封装的监听socketuint16_t _port; // 服务器监听的端口号struct pollfd _event_fds[fd_num_max]; // poll事件数组,保存所有的文件描述符及其事件
};
3.epoll
epoll 是 Linux 下用于处理 I/O 事件的高效多路复用机制,适用于需要处理大量并发连接的网络服务器,相较于poll提供了更好的性能,可以说是他的升级版。
3.1.系列函数解析
int epoll_create(int size);
创建一个 epoll 实例,返回一个文件描述符(指向的struct_file对象细节在原理讲解),用于后续的 epoll_ctl 和 epoll_wait 调用。
参数:
- size:指定要监听的最大文件描述符数量。
- 返回值:成功返回 epoll 文件描述符,失败返回 -1 并设置 errno。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
控制 epoll 实例的行为,包括添加、修改或删除文件描述符的事件。
参数:
- epfd:由 epoll_create 返回的 epoll 文件描述符。
- op:操作类型,可以是以下之一:
- EPOLL_CTL_ADD:添加文件描述符。
- EPOLL_CTL_MOD:修改文件描述符。
- EPOLL_CTL_DEL:删除文件描述符。
- fd:要操作的文件描述符。
- event:指向 epoll_event 结构的指针,描述要监听的事件。
- 返回值:成功返回 0,失败返回 -1 并设置 errno。
epoll_event 结构:
struct epoll_event {uint32_t events; // 事件类型epoll_data_t data; // 用户数据
};
union epoll_data {void *ptr; // 用户自定义数据指针int fd; // 文件描述符uint32_t u32; // 无符号整型数据uint64_t u64; // 无符号长整型数据
};
events:
- 类型:uint32_t
- 描述:要监听的事件类型,可以多个组合。
- 常用的事件类型包括:
EPOLLIN:表示文件描述符可读。
EPOLLOUT:表示文件描述符可写。
EPOLLERR:表示发生错误。
EPOLLHUP:表示挂起事件。
EPOLLET:边缘触发模式(Edge Triggered),意味着在状态变化时才通知。
使用示例:
struct epoll_event event;
event.events = EPOLLIN | EPOLLET; // 监听可读事件,设置为边缘触发模式
event.data.fd = socket_fd; // 关联的文件描述符
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:等待事件发生,返回已就绪的个数。
参数:
- epfd:由 epoll_create 返回的 epoll 文件描述符。
- events:保存就绪事件。
- maxevents:events 数组的大小。
- timeout:等待的时间,单位是毫秒。可以设置为 0(不阻塞),-1(永久等待),或正值(指定超时时间)。
- 返回值:成功时返回就绪事件的数量,失败返回 -1 并设置 errno。
3.2.epoll原理解析
当调用 epoll_create 时,内核会执行以下操作:
- L创建一个 eventpoll 结构体实例,用于管理注册的文件描述符及其事件。
- 返回一个文件描述符(fd),该 fd 代表了 eventpoll 结构体。
eventpoll 结构体内容:
struct eventpoll {struct list_head wq_entry; // 等待队列条目,用于管理等待事件的线程struct hlist_head rdlist; // 用于管理注册的可读事件的文件描述符struct hlist_head pr_list; // 用于优先级处理的链表struct rb_root rbr_tree; // 红黑树,管理注册的文件描述符struct rb_root rbr_wait; // 等待事件的红黑树wait_queue_head_t wait; // 等待队列,用于线程调度int epfd; // epoll 文件描述符int wakeup; // 唤醒标志// 其他字段...
};
主要使用过程解析:连接基于tcp实现的epoll多客户端网络服务器后,将需要监控的fd添加到红黑树(rbr_tree),等待网卡数据就绪发送中断信号,将数据向上交付后,在红黑树查找到对应的fd,将此结点(包含fd和对应事件)插入就绪队列中(wq_entry),便于用户调用epoll_wait获取就绪节点。
epoll模型原理:

2.3.基于 select 的多客户端网络服务器
#pragma once#include <iostream>
#include <memory>
#include <sys/epoll.h>
#include "Socket.hpp"
#include "Epoller.hpp"
using namespace std;// 定义事件常量
const int num = 64; // epoll 事件数组的最大数量// EpollServer 类,用于处理基于 epoll 的服务器逻辑
class EpollServer
{public:// 构造函数,初始化端口、监听套接字和 epollerEpollServer(uint16_t port): _port(port),_listsocket_ptr(new Sock()), // 创建一个新的套接字对象_epoller_ptr(new Epoller()) // 创建一个新的 epoller 对象{}// 初始化服务器void Init(){_listsocket_ptr->Socket(); // 创建套接字_listsocket_ptr->Bind(_port); // 绑定端口_listsocket_ptr->Listen(); // 开始监听 }// 接收新连接void Accepter(){string clientip; // 客户端 IP 地址uint16_t clientport; // 客户端端口int sock = _listsocket_ptr->Accept(&clientip, &clientport); // 接收新连接if (sock > 0){// 将新连接的套接字添加到 epoll 中进行事件监听_epoller_ptr->EpllerUpdate(EPOLL_CTL_ADD, sock, EPOLLIN);}}// 测试用的接收函数void Recver(int fd){char buffer[1024]; // 接收缓冲区ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // 读取数据if (n > 0){buffer[n] = 0; // 确保字符串结束cout << "get a messge: " << buffer << endl; // 输出接收到的消息// 发送回显消息string echo_str = "server echo $ ";echo_str += buffer;write(fd, echo_str.c_str(), echo_str.size());}else if (n == 0){_epoller_ptr->EpllerUpdate(EPOLL_CTL_DEL, fd, 0); // 从 epoll 中移除close(fd); // 关闭套接字}else{// 发生接收错误_epoller_ptr->EpllerUpdate(EPOLL_CTL_DEL, fd, 0); // 从 epoll 中移除close(fd); // 关闭套接字}}// 分发事件处理void Dispatcher(struct epoll_event revs[], int num){for (int i = 0; i < num; i++){uint32_t events = revs[i].events; // 获取事件类型int fd = revs[i].data.fd; // 获取文件描述符if (events & EPOLLIN) // 可读事件{if (fd == _listsocket_ptr->Fd()) // 如果是监听套接字{Accepter(); // 接受新连接}else{// 处理普通套接字的可读事件Recver(fd);}}else{}}}// 启动服务器void Start(){// 将监听套接字添加到 epoll 中进行事件监听_epoller_ptr->EpllerUpdate(EPOLL_CTL_ADD, _listsocket_ptr->Fd(), EPOLLIN);struct epoll_event temp[num]; // 创建事件数组while(true){int n = _epoller_ptr->EpollerWait(revs, num); // 等待事件发生if (n > 0){// 有事件就绪Dispatcher(temp, n); // 分发事件}else if (n == 0){lg(Info, "time out ..."); // 超时日志}else{lg(Error, "epll wait error"); // 错误日志}}}// 析构函数,关闭监听套接字~EpollServer(){_listsocket_ptr->Close(); // 关闭监听套接字}private:shared_ptr<Sock> _listsocket_ptr; // 监听套接字指针shared_ptr<Epoller> _epoller_ptr; // epoller 指针uint16_t _port; // 服务器端口
};Epoll.hpp
#pragma once#include <cerrno>
#include <cstring>
#include <sys/epoll.h>class Epoller
{static const int size = 128;public:Epoller(){_epfd = epoll_create(size);if (_epfd == -1){}else{cout<<"epoll_create success "<<_epfd<<endl;}}int EpollerWait(struct epoll_event revents[], int num){int n = epoll_wait(_epfd, revents, num, -1);return n;}int EpllerUpdate(int oper, int sock, uint32_t event){int n = 0;if (oper == EPOLL_CTL_DEL){n = epoll_ctl(_epfd, oper, sock, nullptr);if (n != 0){cout<<"epoll_ctl error"<<endl;}}else{struct epoll_event ev;ev.events = event;ev.data.fd = sock;n = epoll_ctl(_epfd, oper, sock, &ev);if (n != 0){cout<<"epoll_ctl error"<<endl;}}return n;}~Epoller(){if (_epfd >= 0)close(_epfd);}private:int _epfd;int _timeout{1000};
};
3.4.epoll的优点
- 高性能:相比于 select 和 poll,epoll 在处理大量文件描述符时性能更优,尤其是在有很多文件描述符处于非活动状态时。epoll
使用内核中的红黑树和链表来管理文件描述符,能够快速地插入、删除和查找事件。- 支持边缘触发和水平触发:epoll 支持两种工作模式:水平触发(Level Triggered, LT):默认模式,只有当文件描述符处于可读或可写状态时,epoll_wait 才会返回。边缘触发(Edge Triggered,
ET):只有在状态改变时才会通知,这意味着你需要在事件发生时一次性读取所有数据,这种模式更加高效,但需要开发者小心处理。- 不限制文件描述符数量:epoll 可以处理的文件描述符数量大于 select 的 1024 限制,具体数量仅受系统资源限制。
- 减少上下文切换:epoll 通过在内核中管理事件,减少了用户空间和内核空间之间的切换,从而提高了性能。
三.LT和ET
epoll 的 LT 和 ET 模式解析
epoll 提供了两种工作模式:水平触发(Level Triggered, LT)和边缘触发(Edge Triggered, ET)。这两种模式适用于不同的场景,epoll默认是LT。
1. 水平触发(LT)
- 工作方式:
- 在 LT 模式下,只要文件描述符处于可读或可写状态,
epoll_wait就会返回该文件描述符。 - 如果文件描述符有未读的数据,
epoll_wait会多次通知,即使在没有新数据到达的情况下,只要状态满足条件,它都会返回,支持阻塞读写和非阻塞读写。
- 在 LT 模式下,只要文件描述符处于可读或可写状态,
2. 边缘触发(ET)
-
工作方式:
- 在 ET 模式下,
epoll_wait只在状态变化时通知事件。例如,文件描述符从不可读变为可读时,才会触发。 - 一旦事件被触发,开发者需要尽可能地读取所有可用数据,直到
EAGAIN错误出现,只支持非阻塞的读写。每次通知都必须把本轮的数据全取走,必须非阻塞读。
- 在 ET 模式下,
-
LT 适用场景:
- 简单的应用场景,或者对性能要求不高的情况。
- 需要处理大量连接但不要求高并发性能的应用。
-
ET 适用场景:
- 高性能网络服务,处理大量并发连接的场景,如高负载的 Web 服务器。
- 需要尽量减少系统调用次数,提升性能的应用。
相关文章:
Linux高级I/O:多路转接模型
目录 一.常见的IO模型介绍二.多路转接I/O1.select1.1.函数解析1.2. select特点和缺点1.3.基于 select 的多客户端网络服务器 2.poll2.1.poll函数解析2.2.poll特点和缺点2.3.基于poll的tcp服务器 3.epoll3.1.系列函数解析3.2.epoll原理解析2.3.基于 select 的多客户端网络服务器…...
MongoDB Limit 与 Skip 方法
MongoDB Limit 与 Skip 方法 MongoDB 是一个流行的 NoSQL 数据库,它提供了灵活的数据存储和强大的查询功能。在处理大量数据时,我们常常需要限制返回的结果数量或者跳过一部分结果,这时就可以使用 MongoDB 的 limit 和 skip 方法。 Limit 方…...
【2025】中医药健康管理小程序(安卓原生开发+用户+管理员)
博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…...
VulnHub-Bilu_b0x靶机笔记
Bilu_b0x 靶机 概述 Vulnhub 的一个靶机,包含了 sql 注入,文件包含,代码审计,内核提权。整体也是比较简单的内容,和大家一起学习 Billu_b0x.zip 靶机地址: https://pan.baidu.com/s/1VWazR7tpm2xJZIGUS…...
Python | Leetcode Python题解之第421题数组中两个数的最大异或值
题目: 题解: class Trie:def __init__(self):# 左子树指向表示 0 的子节点self.left None# 右子树指向表示 1 的子节点self.right Noneclass Solution:def findMaximumXOR(self, nums: List[int]) -> int:# 字典树的根节点root Trie()# 最高位的二…...
如何将普通Tokenizer变成Fast Tokenizer
现在的huggingface库里面Tokenizer有两种,一种就是普通的,另一种是fast的。fast和普通的区别就是fast使用rust语言编写,在处理大量文本的时候会更快。我自己测试的时候单一一句的话fast要比普通的慢一些,当量叠上来,到…...
联合复现!考虑最优弃能率的风光火储联合系统分层优化经济调度!
前言 目前,尽管不断地追逐可再生能源全额消纳方式,大幅减小弃风弃光电量,但是若考虑风电、光伏发电的随机属性,全额消纳可能造成电网峰谷差、调峰难度及调峰调频等辅助服务费用的剧增,引起电网潜在运行风险。因此&…...
Vue开发前端图片上传给java后端
前端效果图 图片上传演示 1 前端代码 <template><div><!-- 页面标题 --><h1 class"page-title">图片上传演示</h1><div class"upload-container"><!-- 使用 van-uploader 组件进行文件上传,v-model 绑…...
react hooks--useCallback
概述 useCallback缓存的是一个函数,主要用于性能优化!!! 基本用法 如何进行性能的优化呢? useCallback会返回一个函数的 memoized(记忆的) 值;在依赖不变的情况下,多次定义的时候,返回的值是…...
828华为云征文|华为云Flexus X实例docker部署最新Appsmith社区版,搭建自己的低代码平台
828华为云征文|华为云Flexus X实例docker部署最新Appsmith社区版,搭建自己的低代码平台 华为云最近正在举办828 B2B企业节,Flexus X实例的促销力度非常大,特别适合那些对算力性能有高要求的小伙伴。如果你有自建MySQL、Redis、Ng…...
webservice cxf框架 jaxrs jaxws spring整合 接口测试方法 wsdl报文详解 springboot整合 拦截器 复杂参数类型
webservice cxf框架 jaxrs jaxws spring整合 【java进阶教程之webservice深入浅出【黑马程序员】】 webservice接口测试方法 【SoapUI让你轻松玩转WebService接口测试【特斯汀学院】】 webservice wsdl报文详解 【webservice - 尚硅谷周阳新视频】 webservice springbo…...
2024AI做PPT软件如何重塑演示文稿的创作
现在AI技术的发展已经可以帮我们写作、绘画,最近我发现了不少ai做ppt的工具!不体验不知道,原来合理使用AI工具可以有效的帮我们进行一些办公文件的编写,提高了不少工作效率。如果你也有这方面的需求就接着往下看吧。 1.笔灵AIPPT…...
谷神后端list转map
list转map /*** list2map* list转map:支持全量映射、单字段映射。* * param $list:list:列表。* param $key:string:键。* param $field:string:值字段域。** return map**/ #function list2map($list, $key, $field)#if ($vs.util.isList($list) and $vs.util.is…...
Java集合(Map篇)
一.Map a.使用Map i.键值(key-value)映射表的数据结构,能高效通过key快速查找value(元素)。 ii.Map是一个接口,最常用的实现类是HashMap。 iii.重复放入k-v不会有问题,但是一个…...
VUE3配置路由(超级详细)
第一步创建vue3的项目...
【笔记】机器学习算法在异常网络流量监测中的应用
先从一些相对简单的综述类看起,顺便学学怎么写摘要相关工作的,边译边学 机器学习算法在异常网络流量监测中的应用 原文:Detecting Network Anomalies in NetFlow Traffic with Machine Learning Algorithms Authors: Quc Vo, Philippe Ea, Os…...
江协科技STM32学习- P15 TIM输出比较
🚀write in front🚀 🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝…...
使用python-pptx批量删除备注:清除PPT文档中的所有备注信息
哈喽,大家好,我是木头左! 在制作和分享PPT时,经常需要添加一些注释或备注来帮助观众更好地理解内容。然而,有时候需要将这些备注从PPT中移除,以保持演示的简洁性。幸运的是,可以使用python-pptx库来实现这一目标。本文将详细介绍如何使用python-pptx批量删除PPT中的备注…...
RTX NVIDIA 3090卡配置对应pytorch,CUDA版本,NVIDIA驱动过程及问题整理
买了两块3090卡闲置很长时间了,之前tf 1.12.0版本用习惯了不想转工具。这段时间闲下来转了之后有些环境不适配,在雷神帮助下安装完毕,虽然出了点怪东西,整体还好。 原环境CUDA为11.4 其他配置如下 之前conda install的pytorch实为…...
【Verilog学习日常】—牛客网刷题—Verilog快速入门—VL21
根据状态转移表实现时序电路 描述 某同步时序电路转换表如下,请使用D触发器和必要的逻辑门实现此同步时序电路,用Verilog语言描述。 电路的接口如下图所示。 输入描述: input A , input clk , …...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
tomcat指定使用的jdk版本
说明 有时候需要对tomcat配置指定的jdk版本号,此时,我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...
前端开发者常用网站
Can I use网站:一个查询网页技术兼容性的网站 一个查询网页技术兼容性的网站Can I use:Can I use... Support tables for HTML5, CSS3, etc (查询浏览器对HTML5的支持情况) 权威网站:MDN JavaScript权威网站:JavaScript | MDN...
Python常用模块:time、os、shutil与flask初探
一、Flask初探 & PyCharm终端配置 目的: 快速搭建小型Web服务器以提供数据。 工具: 第三方Web框架 Flask (需 pip install flask 安装)。 安装 Flask: 建议: 使用 PyCharm 内置的 Terminal (模拟命令行) 进行安装,避免频繁切换。 PyCharm Terminal 配置建议: 打开 Py…...
算法—栈系列
一:删除字符串中的所有相邻重复项 class Solution { public:string removeDuplicates(string s) {stack<char> st;for(int i 0; i < s.size(); i){char target s[i];if(!st.empty() && target st.top())st.pop();elsest.push(s[i]);}string ret…...
Shell 解释器 bash 和 dash 区别
bash 和 dash 都是 Unix/Linux 系统中的 Shell 解释器,但它们在功能、语法和性能上有显著区别。以下是它们的详细对比: 1. 基本区别 特性bash (Bourne-Again SHell)dash (Debian Almquist SHell)来源G…...
实现p2p的webrtc-srs版本
1. 基本知识 1.1 webrtc 一、WebRTC的本质:实时通信的“网络协议栈”类比 将WebRTC类比为Linux网络协议栈极具洞察力,二者在架构设计和功能定位上高度相似: 分层协议栈架构 Linux网络协议栈:从底层物理层到应用层(如…...
Go 语言中的内置运算符
1. 算术运算符 注意: (自增)和--(自减)在 Go 语言中是单独的语句,并不是运算符。 package mainimport "fmt"func main() {fmt.Println("103", 103) // 13fmt.Println("10-3…...
Kafka 消息模式实战:从简单队列到流处理(一)
一、Kafka 简介 ** Kafka 是一种分布式的、基于发布 / 订阅的消息系统,由 LinkedIn 公司开发,并于 2011 年开源,后来成为 Apache 基金会的顶级项目。它最初的设计目标是处理 LinkedIn 公司的海量数据,如用户活动跟踪、消息传递和…...
