文章目录
- 多路复用的介绍
- select ()
- poll()
- epoll()
多路复用的介绍
IO多路复用是一种技术,允许单个线程同时管理多个输入/输出通道,如网络套接字或文件描述符。
在IO多路复用中,这些通道被注册到一个事件管理器,然后通过阻塞方式等待事件(例如数据可读或可写)的发生。一旦有事件发生,线程就会被唤醒,并针对具体事件进行处理。这种技术的优点包括提高系统并发性能、减少资源消耗、简化编程模型、提高可扩展性。但也有缺点,如学习曲线较陡、对编程模型要求较高、不适用于CPU密集型任务。
常见的io多路复用包括select、poll、epoll
select ()
select() 是一种IO多路复用技术,允许一个进程监视多个文件描述符,判断它们中的哪些描述符可以进行IO操作,从而实现非阻塞IO。
#include <sys/select.h>int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数说明:
nfds:要监听的文件描述符集中的最大文件描述符值加1。
readfds、writefds、exceptfds:分别是读、写、异常事件的文件描述符集合,如果对应的事件发生,就会被置为1。
timeout:表示 select() 函数的超时时间,如果设置为 NULL 则表示一直阻塞直到有事件发生,如果设置为 0 则表示不阻塞立即返回,其他情况下表示等待指定的时间。
select() 函数的工作流程如下:
调用 select() 函数时,内核会检查 readfds、writefds 和 exceptfds 中所包含的文件描述符集合,以及设置的超时时间。
如果超时时间已到,select() 函数返回0,表示没有文件描述符就绪。
如果有文件描述符就绪,select() 函数返回就绪的文件描述符数量。
对于就绪的文件描述符,可以通过检查 readfds、writefds 和 exceptfds 来确定是哪种事件。
select() 的一些限制包括:
最大文件描述符数量通常有限(通常是 1024),因此不能用于管理大量文件描述符。
每次调用 select() 都需要将完整的文件描述符集合传递给内核,这可能会产生一定的性能开销。
poll()
poll() 函数是一种IO多路复用技术,用于在单个线程中管理多个IO操作。它与 select() 函数类似,但在一些方面更加灵活和高效。
#include <poll.h>int poll(struct pollfd fds[], nfds_t nfds, int timeout);
参数介绍:
fds[]:是一个指向 pollfd 结构体数组的指针,每个 pollfd 结构体描述一个待检查的文件描述符以及需要检查的事件。
nfds:是数组中结构体的数量。
timeout:是等待的超时时间,单位为毫秒,可以设置为 -1 表示一直等待直到有事件发生,0 表示立即返回,其他正数表示等待指定的时间。
工作原理:
调用 poll() 函数时,将 fds[] 中描述的文件描述符以及需要检查的事件注册到内核中。
poll() 函数阻塞等待,直到有文件描述符就绪或超时。
当有文件描述符就绪或超时时,poll() 函数返回。
对于就绪的文件描述符,可以通过 revents 字段来确定是哪种事件。
poll()的优点:
没有文件描述符数量的限制,可以同时监视大量的文件描述符。
不需要像 select() 函数一样传递文件描述符集合的大小,因为 nfds 参数已经指定了数组的大小。
poll()使用注意事项:
在高并发环境中,poll() 函数可能会有一定的性能开销,因为它需要遍历整个 fds[] 数组来检查文件描述符的状态。
在某些操作系统上,poll() 的实现可能不如 select() 或 epoll() 高效。
epoll()
epoll() 是 Linux 上一种高性能的 IO 多路复用技术,用于在单个线程中管理大量的 IO 操作。相比于 select() 和 poll(),epoll() 在处理大量并发连接时具有更好的性能和扩展性。
#include <sys/epoll.h>int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epoll_create():创建一个 epoll 实例,返回一个文件描述符,用于后续的操作。
epoll_ctl():向 epoll 实例中添加、修改或删除要监听的文件描述符及其对应的事件。
epoll_wait():等待文件描述符就绪,返回就绪的文件描述符及其对应的事件。
工作原理:
调用 epoll_create() 创建一个 epoll 实例。
调用 epoll_ctl() 向 epoll 实例中添加需要监听的文件描述符及其事件。
调用 epoll_wait() 等待文件描述符就绪。
epoll_wait() 返回就绪的文件描述符及其对应的事件。
epoll的优点:
高性能:epoll() 使用基于事件驱动的方式工作,避免了 select() 和 poll() 中的线性扫描问题,因此在大量并发连接的情况下性能更好。
高扩展性:epoll() 支持边缘触发(edge-triggered)模式和水平触发(level-triggered)模式,使得它更适合于不同类型的应用场景。
没有文件描述符数量的限制:epoll() 支持管理大量的文件描述符,没有 select() 和 poll() 中的数量限制。
边缘触发(Edge-triggered)和水平触发(Level-triggered):
边缘触发模式下,epoll_wait() 只会在文件描述符的状态发生变化时返回就绪事件,需要应用程序自行处理完整的数据。
水平触发模式下,epoll_wait() 会在文件描述符的状态仍然就绪时重复返回事件,直到文件描述符的状态不再就绪。
epoll的注意事项:
epoll() 是 Linux 特有的 API,在其他平台上可能不可用。
使用 epoll() 时需要注意避免内存泄漏和资源耗尽的问题,因为它需要管理大量的文件描述符和事件。
在一些特定场景下,如短连接服务器,epoll() 可能没有太大的优势,甚至可能比 select() 和 poll() 略逊一筹。