Proactor 网络模型
Proactor 网络模型
本文内容主要参考 asio文档
Proactor 和其他网络模型最大的区别就是 Proactor 中,执行 I/O 的职责由操作系统代为我们承担,因此使用 Proactor 网络库的应用程序代码里,是不会有 read、write 等 I/O 接口调用的
Proactor
我们以 boost asio 为例,讲解 Proactor 网络库中会出现的模块
- Asynchronous Operation
- 定义了异步执行的操作
- 比如异步读写一个套接字
- Asynchronous Operation Processor
- 执行 Asynchronous Operation 并在操作完成后将事件放入 Completion Event Queue
- 可以认为类似于
reactive_socket_service
的内部服务是 Asynchronous Operation Processor
- Completion Event Queue
- 缓存操作完成的事件直到它们被 Asynchronous Event Demultiplexer 取走
- Completion Handler
- 处理异步操作的结果
- Asio 中 Completion Handler 必须是形式正确的可调用对象,一般通过
boost::bind
创建 - 必须是可移动构造的,异步操作会通过移动构造函数接管它的所有权
- Asynchronous Event Demultiplexer
- 阻塞等待 Completion Event Queue 中有事件产生,然后返回一个事件给调用者
- Proactor
- 调用 Asynchronous Event Demultiplexer 以获取事件,并分发事件给相关联的 Completion Handler
- 这个抽象实体的代表为
io_context
- Initiator
- 应用发起 Asynchronous Operation 的代码
- Initiator 通过高级 API 比如
basic_stream_socket
与 Asynchronous Operation Processor 交互,再由该接口委托给诸如reactive_socket_service
的服务
通过 Reactor 实现
在许多平台上,Asio 以 select、epoll、kqueue 等 Reactor API 来模拟实现 Proactor
- Asynchronous Operation Processor
- 当 Reactor 指出某个资源已经准备好执行操作,Asynchronous Operation Processor 执行异步操作并将相应的 Completion Handler 放入 Completion Event Queue
- Completion Event Queue
- 一系列 Completion Handler 构成的链表
- Asynchronous Event Demultiplexer
- 等待相应的事件或条件变量直到 Completion Event Queue 中有事件可用
通过Windows Overlapped I/O实现
在 Windows NT、2000 和 XP 上,Asio 利用 Overlapped I/O 的优势提供了一个高效的 Proactor 实现:
- Asynchronous Operation Processor
- 由操作系统实现,通过调用
AcceptEx
等 overlapped 函数以发起操作
- 由操作系统实现,通过调用
- Completion Event Queue
- 由操作系统实现,与某个 I/O 完成端口关联起来,每个
io_context
实例都对应一个 I/O 完成端口
- 由操作系统实现,与某个 I/O 完成端口关联起来,每个
- Asynchronous Event Demultiplexer
- 被 Asio 调用来从队列中取出事件和它关联的 Completion Handler
优势
- 可移植性
- 将线程与并发解耦
- 性能和可拓展性
- 简化应用同步
- Asio 中一般通过 strand 来实现应用间的同步,很少会去显式地使用锁
- 很容易实现复合函数(Function composition)
- 复合函数是指通过多次调用低层次读写 API 来提供高层次的操作,比如以特定协议格式发送消息
- 在一个操作的 Completion Handler 中启动下一个操作,就可以将异步操作串联在一起,实现复合函数
- 通过封装异步调用链中的第一个操作,用户就感知不到高层次的操作是通过级联实现的
劣势
- 程序复杂
- 特别明显的一点就是在 Proactor 模型中,应用逻辑往往是散落在各种回调函数里,这样我们的程序变得很难懂
- 内存占用
- 缓冲区空间必须在读写期间被保持,而这段时长往往是不确定的,而且每个并发的操作都需要独立的缓冲区
- Asio 中往往是通过 RAII 来管理缓冲区,因为程序员没法判断这段缓冲区到什么时候才会被释放,通过将缓冲区智能指针绑定到函数对象中,让他们的生命周期产生关联
- 对比 Reactor 模式,它在套接字准备好读写前都是不需要缓冲区空间的
- 缓冲区空间必须在读写期间被保持,而这段时长往往是不确定的,而且每个并发的操作都需要独立的缓冲区
本文来自博客园,作者:路过的摸鱼侠,转载请注明原文链接:https://www.cnblogs.com/ljx-null/p/15929782.html