Linux 网络IO模型
4
种同步IO
,1
种异步IO
- 同步
I/O (Synchronous I/O)
:导致请求进程阻塞,直到I/O
操作完成。- 异步
I/O (Asynchronous I/O)
:不导致请求进程阻塞,理解返回,IO
由内核完成阻塞阶段:数据准备阶段 (等待数据到达内核缓冲区) 和 数据拷贝阶段 (数据从内核缓冲区拷贝到用户进程缓冲区)
阻塞IO(BIO)
流程
- 默认的就是阻塞
IO
,如read()
,write()
,liste()
- 数据准备和拷贝都在阻塞
特点
- 阻塞期间,整个应用程序都会被阻塞,易于实现
- 处理并发效率低
- 需要多线程处理每一个连接会导致大量的线程上下文切换和资源消耗。
- 一个线程在等待一个连接的
I/O
时,无法处理其他连接
非阻塞I/O (NIO)
流程
应用程序将文件描述符(如
socket
)设置为非阻塞模式 ,例如通过fcntl(fd, F_SETFL, O_NONBLOCK)
应用程序调用
I/O
函数 (如recvfrom
)如果数据还没有准备好,内核会立即返回一个错误码,但不会阻塞程序
应用程序需要不断地轮询 (
polling
) 内核,重复调用I/O
函数,看数据是否准备好当数据准备好后,再次调用
I/O
函数,内核将数据从内核缓冲区拷贝到用户进程的缓冲区。在此拷贝期间,应用程序进程仍然是阻塞的。数据拷贝完成后,
I/O
函数返回,应用程序获取数据。
特点
- 数据未准备完成时非阻塞的,准备完成数据拷贝时依然是阻塞(还是同步IO,实际的IO操作是拷贝阻塞的)
- 在等待数据期间,应用程序可以执行其他任务,不会被卡死。
- 忙等待: 应用程序需要不断轮询内核,消耗大量
CPU
时间。很少单独使用,通常与
I/O
多路复用结合使用【避免了忙等待
】
I/O多路复用 (I/O Multiplexing)
- 也称事件驱动 I/O (
Event-driven I/O
),select
,poll
,epoll
是其典型实现- 允许单个进程监视多个文件描述符,看它们中是否有任何一个变为可读、可写或出现异常
流程(epoll)
- 应用程序创建一个
epoll
实例 (epoll_create
)。- 应用程序将需要监视的文件描述符 (如多个
socket
) 以及关心的事件类型 (读/写) 注册到 epoll 实例中 (epoll_ctl)。- 应用程序调用
epoll_wait
,这个调用会阻塞应用程序进程,直到至少有一个被监视的文件描述符上的事件发生,或者超时。- 当某个或某些文件描述符上的事件发生时 (例如,数据到达,可读了),
epoll_wait
返回,并告知应用程序哪些文件描述符就绪了。- 应用程序遍历这些就绪的文件描述符,对每个就绪的描述符调用相应的 I/O 函数 (如 recvfrom)。
- 调用
recvfrom
时,数据通常已经准备好了 (因为 epoll_wait 通知了),内核将数据从内核缓冲区拷贝到用户进程缓冲区。在此拷贝期间,应用程序进程仍然是阻塞的。- 数据拷贝完成后,recvfrom 返回。
特点
- 两段阻塞:
epoll_wait
调用等待事件发生- 事件发生,调用对应
IO
操作(还是同步IO,实际的IO操作是拷贝阻塞的)- 优点:
- 通过通知避免了忙等待
- 单个线程或者少量线程就可以处理大量并发连接,避免了创建和切换大量线程的开销
epoll
相比select
和poll
具有更好的性能,尤其是在监视大量文件描述符时,因为它只返回活动的文件描述符,并且内部实现更高效(红黑树)。- 使用场景:高并发网络服务器,如
Nginx
,Redis
,Node.js
等
信号驱动I/O (Signal-driven I/O - SIGIO)
- 应用程序开启套接字的信号驱动 I/O 功能 (通过
fcntl
设置O_ASYNC
标志)。- 应用程序通过
sigaction
系统调用安装一个SIGIO
信号的处理函数。- 应用程序进程可以继续执行其他任务,不会被阻塞。
- 当数据准备好后,内核会为该进程生成一个 SIGIO 信号。
- 信号处理函数被调用。在信号处理函数中,应用程序可以调用
I/O
函数 (如recvfrom
) 来读取数据(还是同步IO
,依旧阻塞)- 数据拷贝完成后,
recvfrom
返回。
特点
- 应用程序在等待数据阶段不会被阻塞,而是通过信号通知
- 实际拷贝数据阶段依旧阻塞
- 减少了阻塞,但是较为复杂,可靠性缺乏,实际使用较少
异步I/O (AIO)
真正实现了非阻塞,
数据拷贝部分
也不阻塞
流程
- 应用程序调用
aio_read
(或其他AIO
函数),向内核发起一个异步读操作。该调用立即返回,应用程序不会被阻塞。- 内核开始处理这个异步
I/O
请求。这包括两个阶段:
- 等待数据准备好。
- 将数据从内核缓冲区直接拷贝到应用程序指定的缓冲区。 这两个阶段都由内核独立完成,应用程序进程不会被阻塞。
- 当整个 I/O 操作 (包括数据拷贝) 完成后,内核会通知应用程序。通知方式可以是:
- 通过信号 (例如,
aio_sigevent
结构中指定)。- 通过执行一个用户指定的回调函数 (某些 AIO 实现支持,
io_uring
通过完成队列)。- 也可以主动查询
aio_error
和aio_return
来检查状态。- 应用程序在收到通知后,数据已经在其指定的缓冲区中,可以直接使用。
特点
- 无阻塞:两个原本的阻塞过程内核自己完成,不再阻塞,
CPU
可以得到充分利用- 异步:调用
IO
操作后,可以继续操作,只需等待通知- 使用相对复杂,适合追求极致性能的系统
总结
优选IO多路复用