sui_rust/ch03/s1_io_model.rs
1//! 第三章:Rust 异步编程概念
2//!
3//! # 3.1 异步 I/O 模型
4//!
5//! - 基本概念: 同步/异步、阻塞/非阻塞IO、多路复用、epoll/io_uring
6//! - Reactor/Preactor模型 与 事件抽象
7//! - minimio/mio
8
9/**
10
11 # 异步 I/O 模型
12
13 ## 基本概念
14
15 - 同步和异步,关注的是消息通信机制。(调用者视角)
16 - 同步,发出一个调用,在没有得到结果之前不返回。
17 - 异步,发出一个调用,在没有得到结果之前返回。
18 - 阻塞和非阻塞,关注的是程序等待调用结果的状态。(被调用者视角)
19 - 阻塞,在调用结果返回之前,线程被挂起。
20 - 非阻塞,在调用结果返回之前,线程不会被挂起。
21
22 阻塞,与系统调用有关。
23
24
25 ### I/O 模型
26
27 ```text
28 +-+ 阻 塞 I/O (BIO)
29 |
30 +-+ 非 阻 塞 I/O (NIO)
31 |
32 +----+ 同 步 I/O +--+
33 | |
34 | +-+ I/O 多 路 复 用
35 | |
36 | +-+ 信 号 驱 动 I/O
37 I/O 模 型 +---+
38 |
39 |
40 | +-+ Linux (AIO)
41 | | (io_uring)
42 +----+ 异 步 I/O +--+
43 |
44 +-+ windows (IOCP)
45
46 ```
47
48 ### 同步阻塞I/O (blocking I/O)
49
50 ```text
51 Application kernel
52 +---------+ +-----------+ +---+
53 | | syscall | no | |
54 | Read | +--------> | datagram | |
55 | recvfrom| | ready | |
56 | | | + | +-+ wait for
57 | | | | | +-+ data
58 | | | v | |
59 | | | datagram | |
60 | | | ready | +---+
61 | | | |
62 | | | copy | +---+
63 | | | datagram | |
64 |process | | + | +-+ copy data
65 |datagram | return | | | +-+ from kernel to user
66 | | <--------+ | v | |
67 | | | copy | +---+
68 | | | complete |
69 +---------+ +-----------+
70 ```
71
72 输入操作两个阶段:
73
74 1. 进程等待内核把数据准备好;这个阶段可以阻塞也可非阻塞,设置socket属性。
75 - 阻塞: recvfrom 阻塞线程直到返回数据就绪的结果。
76 - 非阻塞:立即返回一个错误,轮询直到数据就绪。
77 2. 从内核缓冲区向进程缓冲区复制数据。(一直阻塞)
78
79 异步I/O,recvfrom总是立即返回,两个阶段都由内核完成。
80
81 ### I/O 多路复用(I/O Multiplexing )
82
83 IO多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄。
84
85 支持I/O多路复用的系统调用有 select/pselect/poll/epoll,本质都是 同步 I/O,因为数据拷贝都是阻塞的。
86 通过 select/epoll 来判断数据报是否准备好,即判断可读可写状态。
87
88
89
90
91*/
92pub fn basic_concept() {}
93
94/**
95
96 ## epoll
97
98 ```text
99 +--------------------------------+ +-------------------------+
100 | epoll_ctl | | epoll_wait |
101 | | | |
102 | | | +----+ |
103 | +---+ | | | | |
104 | | | | | | | |
105 | +-+---+--+ | | +--+-+ |
106 | | | | | | |
107 | +--++ +-++ | | | |
108 epoll_create +----> | | | | | | | +--+-+ |
109 | +-+-+ +--+ +---->+ | | |
110 | | |event| | | |
111 | +----+--+ | | +--+-+ |
112 | | | | | | |
113 | ++ | | | | |
114 | +--+ +-+-+ | | +--+-+ |
115 | | | | | | | | | |
116 | +--+ +---+ | | | | |
117 | | | +----+ |
118 | 红 黑 树 | | 链 表 |
119 +--------------------------------+ +-------------------------+
120
121
122 ```
123
124 - epoll_create(int size) : 内核产生一个epoll实例数据结构,并返回一个epfd
125 - epoll_ctl(int epfd, int op, int fd, struct epoll_event *event):将被监听的描述符添加到红黑树或从红黑树中删除或者对监听事件进行修改。
126 - epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout): 阻塞等待注册的事件发生,返回事件的数目,并将触发的事件写入events数组中
127
128
129 epoll 两种触发机制:
130
131 - 水平触发机制(LT)。缓冲区只要有数据就触发读写。epoll 默认工作方式。select/poll只支持该方式。
132 - 边缘触发机制(ET)。缓冲区空或满的状态才触发读写。nginx 使用该方式,避免频繁读写。
133
134 惊群问题:
135
136 当多个进程/线程调用epoll_wait时会阻塞等待,当内核触发可读写事件,所有进程/线程都会进行响应,但是实际上只有一个进程/线程真实处理这些事件。
137 Liux 4.5 通过引入 EPOLLEXCLUSIVE 标识来保证一个事件发生时候只有一个线程会被唤醒,以避免多侦听下的惊群问题。
138*/
139pub fn epoll() {}
140
141/**
142 ## io_uring 异步 I/O 模型
143
144 Linux AIO 实现的并不理想,所以引入了新的异步I/O接口 io_uring。
145
146 ```text
147 +----+ Head +---------+ +----------+ Head
148 | | | | |
149 | | | | |
150 | +---------+ +----------+
151 | | | | |
152 | | | | |
153 | +---------+ +----------+
154 | | | | |
155 | | | | |
156 | +---------+ +----------+
157 | | | | |
158 | Tail +---------+ +----------+ Tail <--+
159 | +--------------------------------------------+ |
160 | | Kernel | |
161 | | | |
162 | | +-------+ +-------+ | |
163 | | | | | | | |
164 +---------------> | SQ | | CQ | +--------+
165 | | | | | |
166 | +-------+ +-------+ |
167 | |
168 +--------------------------------------------+
169
170 ```
171
172 io_uring接口通过两个主要数据结构工作:
173
174 - 提交队列条目(sqe)
175 - 完成队列条目(cqe)
176
177 这些结构的实例位于内核和应用程序之间的**共享内存**单生产者单消费者环形缓冲区中。
178
179 参考:
180
181 [https://thenewstack.io/how-io_uring-and-ebpf-will-revolutionize-programming-in-linux/](https://thenewstack.io/how-io_uring-and-ebpf-will-revolutionize-programming-in-linux/)
182
183 [https://cor3ntin.github.io/posts/iouring/#io_uring](https://cor3ntin.github.io/posts/iouring/#io_uring)
184
185*/
186pub fn io_uring() {}
187
188/**
189
190 ## 事件驱动编程模型
191
192 因为处理 I/O 复用的编程模型相当复杂,为了简化编程,引入了下面两种模型。
193
194 - Reactor(反应器) 模式,对应同步I/O,被动的事件分离和分发模型。服务等待请求事件的到来,再通过不受间断的同步处理事件,从而做出反应。
195 - Preactor(主动器) 模式,对应异步I/O,主动的事件分离和分发模型。这种设计允许多个任务并发的执行,从而提高吞吐量;并可执行耗时长的任务(各个任务间互不影响)。
196
197 Reactor Model:
198
199 ```text
200 +----------------+
201 req Dispatch | |
202 +------+ +--------> | req handler |
203 | | | +----------------+
204 | | +----+ |
205 +------+ | event +------------+ |
206 | | | |
207 +--------> | Service | |Dispatch +----------------+
208 | Handler +------------> | |
209 req +---------> | | | | req handler |
210 +------+ | +------------+ | +----------------+
211 | | | event |
212 | +----+ |
213 +------+ | Dispatch +----------------+
214 +--------->+ |
215 | req handler |
216 +----------------+
217
218 ```
219
220 三种实现方式:
221
222 - 单线程模式。 accept()、read()、write()以及connect()操作 都在同一线程。
223 - 工作者线程池模式。非 I/O 操作交给线程池处理
224 - 多线程模式。主Reactor (master) ,负责网络监听 , 子Reactor(worker) 读写网络数据。
225
226 读写操作流程:
227
228 1. 应用注册读写就绪事件和相关联的事件处理器
229 2. 事件分离器等待事件发生
230 3. 当发生读写就绪事件,事件分离器调用已注册的事件处理器
231 4. 事件处理器执行读写操作
232
233 参与者:
234 1. 描述符(handle):操作系统提供的资源,识别 socket等。
235 2. 同步事件多路分离器。开启事件循环,等待事件的发生。封装了 多路复用函数 select/poll/epoll等。
236 3. 事件处理器。提供回调函数,用于描述与应用程序相关的某个事件的操作。
237 4. 具体的事件处理器。事件处理器接口的具体实现。使用描述符来识别事件和程序提供的服务。
238 5. Reactor 管理器。事件处理器的调度核心。分离每个事件,调度事件管理器,调用具体的函数处理某个事件。
239
240*/
241pub fn event_driven() {}
242
243/**
244
245 ## Rust 实现 epoll server 示例讲解
246
247 1. [https://github.com/zupzup/rust-epoll-example/blob/main/src/main.rs](https://github.com/zupzup/rust-epoll-example/blob/main/src/main.rs)
248 2. [Reactor executor Example](https://github.com/zupzup/rust-reactor-executor-example)
249
250 ## 实现跨平台
251
252 1. [minimio](https://github.com/cfsamson/examples-minimio)
253 2. [mio](https://github.com/tokio-rs/mio) and mio-examples
254
255*/
256pub fn epoll_server() {}