关闭作为取消操作( Close as Cancel): 可以通过关闭socket来取消异步socket操作。这个设计笔记描述了为什么选择这种方法而不允许取消单个操作的理由
异步事件分离的一种设计模式。一个 Proactor异步执行长时间操作,并在操作完成时引发完成处理器处理操作结果
Proactor设计模式
异步操作(Asynchronous Operation)
定义异步执行的一个操作,例如在一个socket上的异步读或写
异步操作处理器(Asynchronous Operation Processor )
当操作完成时在一个完成事件队列上执行异步操作和队列事件。从高层次的观点来看,像boost::asio::stream_socket_services这样的services都是异步操作处理器
完成事件对列(Completion Event Queue)
缓存完成事件直到异步事件分离器使其出列
完成处理器(Completion Handler)
处理异步操作结果。他们都是函数对象,一般通过boost::bind创建
异步事件分离器(Asynchronous Event Demultiplexer)
阻塞等待完成事件队列上的事件发生并向其呼叫者返回一个完成事件
前掇器(Proactor)
呼叫异步事件分离器使完成事件出列并分派与事件关联的完成处理器(例如:调用函数对象)。boost::asio::io_service是这种抽象的主要实现。
发起器(Initiator)
具体应用中任何发起Asynchronous Operation(异步操作)的实体。它与异步操作处理器通过高层接口交互,如boost::asio::stream_socket,它反过来又委派给一个服务,如boost::asio::stream_socket_service
使用reactor模式实现(Implementation using reactor)
在许多平台上,asio根据Reactor模式来实现Proactor模式,例如select,epoll或kqueue.
对应于Proactor模式这些实现方法如下:
异步操作处理器(Asynchronous Operation Processor)
使用select,epoll或kqueue实现一个reactor。当反应器显示资源已准备好执行操作的时候,这个处理器执行异步操作并使关联的完成处理器在完成事件对列上出列。
完成事件对列(Completion Event Queue)
一个完成处理器(如函数对象)的链表
异步事件分离器(Asynchronous Event Demultiplexer)
通过等待一个事件或条件直到一个完成处理器在一个完成事件队列上可用来实现。
使用windows 重叠I/O实现(Implementation using Windows overlapped I/O)
在Windows NT,2000和XP上,asio利用重叠I/O提供一个Proactor模式的有效实现。对应Proactor模式的实现过程如下:
异步操作处理器(Asynchronous Operation Processor )
由操作系统实现.这些操作通过呼叫一个重叠函数如AcceptEx来初始化.
完成事件对列(Completion Event Queue )
由操作系统实现,与一个I/O完成端口关联。每个boost::asio::io_service 实例都有一个I/O完成端口。
异步事件分离器(Asynchronous Event Demultiplexer)
由asio呼叫使完成事件和与其关联的完成处理器出列。
优点(Advantages)
可移值性(Portability )
库接口不依赖操作系统底层多路技术而被重用。Proactor模式接口通过一系列事件资源来实现,包含代表了Reactor模式的同步事件分离器如select。
线程策略被与并发策略去耦合(Decoupling threading from concurrency )
因为代表应用的Proactor异步执行长时间运行的操作,因此不会被迫派生线程来增加并发。
性能(Performance),
像”thread-per-connectoin”这样的实现策略,由于上下文切换、同步和CPU间的数据移动而降低了系统性能。Proactor模式能通过减少系统线程数来避免上下文切换,而且只激活需要处理事件的逻辑控制线程。
应用程序同步简单化(Simplified application synchronization)
异部操作完成处理器可以写在好像它们存在一个单线程环境里,因此开发应用逻辑可以很少或不用考虑同步问题。
函数组合(Function composition)
Functon composition指的是实现多个函数以提供高层次操作。例如发送一种特殊格式的消息,每个函数根据多次呼叫低层次的读或写操作来实现。例如,考虑某个协议的每个消息都由确定长度的头部和一个长度由头部指定的可变长的主体部分组成。read_message消息能通过两次低层次读操作完成,首先接收头部获取主体长度,然后接收主体部分。
在Proactor模式中组合函数,异步操作可以链在一起,那就是说某个操作的完成处理器能初始化下一个。封装这个链中的第一个呼叫,以至于呼叫者勿须注意正在执行连锁的异步操作这样高层次的操作。
这样组合新操作的能力使得在库上开发高层次抽象简单化,例如支持某特殊协议的一些函数。
缺点(Disadvantages)
程序复杂性(Program complexity)
由于在操作开始和完成之间时间和空间的分离,使用异步机制开发应用程序更加困难。反向的控制流可能使应用程序更加难于调试。
内存使用(Memory usage)
连续不确定的读写操作期间都需要缓冲区。另一方面,Reactor模式直到socket准备好了读或写才需要缓冲区。
参考(References)
[1] D. Schmidt et al, Pattern Oriented Software Architecture, Volume 2. Wiley, 2000.
线程(Threads)
Asio在一个特殊平台上的实现可能使用一个或多个额外的线程来模拟异步。这些线程尽可能对库的使用者不可见。特别是这些线程:
· 不准直接呼叫用户代码
· 必须阻塞所有信号
如下保证来实现这个方法:
· 只有目前呼叫boost::asio::io_service::run()的线程才能呼叫异步完成处理器
因此,创建和管理所有能传送通告的线程是库使用者的责任。
理由(Rationale)
· 通过在一个单线程中呼叫boost::asio::io_service::run(),用户可以避免与同步相关的开发复杂性。例如,库使用者能在一个单线程里(从用户的角度来看)实现可伸缩服务器。
· 库使用者可能在线程刚启动不久并且其它程序代码没有运行之前需要执行初始化。例如:Microsoft's COM 的使用者在这个线程执行其它COM操作之前必须呼叫CoInitializeEx。
服务(Services)
Asio发布sockets(和其它资源)的抽象实现被分为三个部分
· Service类--------提供了某个平台上实现资源的包装
template <typename Allocator = std::allocator<void> >
class stream_socket_service
{
public:
// ...
typedef implementation_defined impl_type;
// ...
template <typename Handler>
void async_receive(impl_type& impl, void* data, size_t max_length,
socket_base::message_flags flags, Handler handler);
// ...
};
· 模板类-------------提供了一个面向对象接口的模板类,模板参数就是the service.
template <typename Service>
class basic_stream_socket
{
public:
typedef Service service_type;
typedef typename service_type::impl_type impl_type;
// ...
template <typename Handler>
void async_receive(void* data, size_t max_length,
socket_base::message_flags flags, Handler handler)
{
service_.async_receive(impl_, data, max_length, flags, handler);
}
// ...
private:
service_type& service_;
impl_type impl_;
};
· A typedef -------一个典型的使用方法
typedef basic_stream_socket<stream_socket_service<> > stream_socket;
这个设计准备满足如下要求:
· 为相应资源有效封装了操作系统接口。在大多数操作系统中,一个socket是一个int型,因此service包装类容易移植而且不用再加一个“重量”类。
· 允许定制socket的实现。一些开发者可能需要使用不同的分配器或者一个完全不同的sockets实现,这些都可以通过给basic_stream_socket模板类提供一个不同的参数来实现。
· 让所有的一般使用都没有差别(就像std::string vs std::basic_string)
服务和分离器 (Services and the Demuxer)
Boost::asio::io_service类作为一个可扩展的服务集合与由facets组合的std::locale相似,io_service为每个服务类型包含一个服务对象,这些服务通过它们的类型来存取(如boost::asio::basic_io_service::get_servce)
但是不像std::locale, services只有在io_service第一次使用时被载入。这意味着你不必支付与一个服务相关的资源开销,除非你实例化相应类。例如,在Win32上dead_time_service 的实现使用一个后台线程,但是这个线程直到程序中有一个deadline_timer对象时才会被创建。
这个设计允许通过用户自定义servcices来扩展io_service。例如,某用户希望使用一个后台线程池来模拟异步数据库存取,那么可以创建一个database_connection_service类来创建和管理这个线程池,并且每个数据库连接对象使用这个服务。
处理器(Handlers)
所有的异步操作当操作完成后都需要呼叫一个函数对象。这种函数对象的类型是一个模板参数,如下:
template <typename Handler>
void async_wait(Handler handler);
另外一种可选方法可以将boost::function对象作为回调参数:
void async_wait(boost::function<void(error&)> f);
过去没有使用这种方法是因为使用boost::function实现每个函数对象都需要动态分配对象而降低了效率。
一些异步操作已经分配了在操作期间存在的对象。例如,Win32 后台已经创建了从OVERLAPPED 结构的对象,这些动态分配的对象包含处理器对象的拷贝,如果使用boost::function会再拷贝一次,而需要两次分配内存。
选取的设计仍然允许用户传递boost::function对象作为回调处理器。而且仍然允许使用boost::function作为后台实现,但是并不需要它。
在asio中可以通过关闭socket来取消异步socket操作。既没有独立取消某个socket的操作,也没有取消单个操作的方法。选择这种方法是在实用、可移植性和效率上的一个最好平衡。
例如,虽然Win32提供了一个Cancello函数可以取消一个未完成的操作,但是只有在boost::thread中初始化这个操作并在其中呼叫Cancello才有效。对一个可移植的网络库来说,这个限制太麻烦了,但关闭socket能取消任何线程上的操作。
缓冲区(Buffers)
为了允许开发高效的网络应用程序,asio库提供了分发收集操作。这些操作提供一个或多个缓冲区(每个缓冲区都是连续空间):
· A scatter-read 接收数据至多个缓冲区中
· A gather-write从多个缓冲区传输数据.
因此我们需要一个代表缓冲区集合的抽象,asio定义了一个类型(实际上是两个类型)来代表一个单独的缓冲区,这些单独的缓冲区被存储在一个包容器里,而它将会传递至scatter-gather 操作。Therefore we require an abstraction to represent a collection of buffers. The approach asio uses is to define a type (actually two types) to represent a single buffer. These can be stored in a container, which may be passed to the scatter-gather operations.
用一个地址和大小可以代表连续内存块的缓冲区。可改变的内存(modifiable memory)和不可改变的内存(non-modifiable memory)是有区别的,因此这两种类型定义如下:
typedef std::pair<void*, std::size_t> mutable_buffer;
typedef std::pair<const void*, std::size_t> const_buffer;
一个mutable_buffer可以转变为一个const_buffer,但是反过来是无效的。Here, a mutable_buffer would be convertible to a const_buffer, but conversion in the opposite direction is not valid.
但是asio库没有使用上面的定义,而是定义两个类boost::asio::mutable _buffer和boost::asio::const_buffer类,目的是为连续内存提供一个不透明表示
· mutable_buffer可以转化为const_buffer,但是反过来是不允许的
· 可以保护缓冲区溢出。给定一个缓冲区实例,一个用户只能创建另外一个缓冲区来代表同样范围的内存块或者子区间。为了更加安全,asio 库包含了从POD元素array,boost::array,或std::vector自动检测缓冲区大小的机制
· 安全类型转换必须使用boost::asio::buffer_cast函数显示请求。一般应用程序不需要做这个,但是asio传递原始内存至底层操作系统函数需要调用。
最后,可以通过将多个缓冲区对象入放入容器中使用scatter-gather操作(如:boost::asio::read或boost::asio::write)来传递这些缓冲区。已经定义了Mutable_buffers和const_buffer可以使用std::vector,std::list,boost::array.可以参考boost::asio::buffer文档上的例子。
特定平台实现(Platform-Specific Implementation)
这个设计笔记记录了特定平台的实现细节,例如默认的多路分离机制,内部创建的线程数和创建线程的时间
Linux Kernel 2.4
多路分离机制(Demultiplexing Mechanism)
使用select实现多路分离。这意味着文件描述符的数目不允许操过FD_SETSIZE.
线程(Threads)
在一个线程中呼叫boost::asio::io_service::run()来使用select 实现多路分离。
每个io_servcie使用另一个线程来模拟异步主机解析。在第一次呼叫boost::asio::ip::tcp::resolver::async_resolve()或boost::asio::ip::udp::resolver::async_resolve()时创建。
Linux Kernel 2.6
多路分离机制(emultiplexing Mechanism)
使用epoll实现多路分离
线程(Threads)
在一个线程中呼叫boost::asio::io_service::run()来使用epoll 实现多路分离。每个io_servcie使用另一个线程来模拟异步主机解析,它在第一次呼叫boost::asio::ip::tcp::resolver::async_resolve()或boost::asio::ip::udp::resolver::async_resolve()时创建.
Solaris
多路分离机制(emultiplexing Mechanism)
使用kqueue实现多路分离。这意味着文件描述符的数目不允许操过FD_SETSIZE.
线程(Threads)
在一个线程中呼叫boost::asio::io_service::run()来使用kqueue 实现多路分离。每个io_servcie使用另一个线程来模拟异步主机解析。在第一次呼叫boost::asio::ip::tcp::resolver::async_resolve()或boost::asio::ip::udp::resolver::async_resolve()时创建。
Mac OS X
Demultiplexing Mechanism
使用select实现多路分离。这意味着文件描述符的数目不允许操过FD_SETSIZE.
线程(Threads)
在一个线程中呼叫boost::asio::io_service::run()来使用select 实现多路分离。每个io_servcie使用另一个线程来模拟异步主机解析。在第一次呼叫boost::asio::ip::tcp::resolver::async_resolve()
或boost::asio::ip::udp::resolver::async_resolve()时创建;
Windows 95, 98 and Me
多路分离机制(Demultiplexing Mechanism)
使用select实现多路分离。这意味着文件描述符的数目不允许操过FD_SETSIZE.
线程(Threads)
在一个线程中呼叫boost::asio::io_service::run()来使用select 实现多路分离。每个io_servcie使用另一个线程来模拟异步主机解析,在第一次呼叫boost::asio::ip::tcp::resolver::async_resolve()或boost::asio::ip::udp::resolver::async_resolve()时创建。
Windows NT, 2000 and XP
多路分离机制(Demultiplexing Mechanism)
为所有的异步datagram_socket、stream_socket和socket_acceptor操作(除了异步connect)使用重叠I/O和I/O完成端口实现多路分离。
使用select实现dead_timer操作和模拟异步连接。
线程(Threads)
在一个线程中呼叫boost::asio::io_service::run()来使用I/O完成端口实现多路分离;
每个io_servcie使用另一个线程来实现选择分离,它在第一个dead_timer、datagram_socket或streamsocket创建时创建。
每个io_servcie使用另一个线程来模拟异步主机解析。在第一次呼叫boost::asio::ip::tcp::resolver::async_resolve()或boost::asio::ip::udp::resolver::async_resolve()时创建;