Boost.Asio网络开发基础知识(二):异步框架总览

  虽然知道异步库实现基本都是对操作系统底层支持的select/poll/epoll/kqueue机制的封装,可是热爱学习的偶,还是想像之前对Libevent一样,如果能了解Boost.Asio的内部异步机制会让人感觉是一件很爽的事情,于是就厚着脸皮把Boost.Asio的核心——io_service代码跟踪读了一下。话说阅读C++的代码确实比C麻烦不少,尤其当其中夹杂着复杂的继承关系、大量泛型参数的时候,同时Boost库的一大风格是为了代码的整洁和编译速度的优化,进行了类声明和实现的分离,形成了大量重复文件名的代码以及一般编辑器不支持的ipp后缀,阅读跳转起来十分不便,想想也真是累啊。但是还是希望自己能早日适应下来!既然选择了这条路,就迟早得迈得过这条坎才行。
  之前也分析过,Windows系统得益于其重叠IO(Overlapped I/O)和完成端口(I/O completion port)机制,算是在操作系统级别支持异步IO操作的,而Linux类系统虽然也有aio_xxx的异步接口,但是用之者寥寥,现在高性能服务端基本都是清一色的select/epoll+普通IO操作实现的。以前看文章据说Linux的aio_xxx不太给力,不知道是用户的选择导致了Linux内核对aio_xxx的发展缺乏动力,还是aio_xxx不完美导致开发者不愿意使用呢?
  要实现跨平台的异步库,由Reactor来实现Proactor容易,因为只要额外添加一步IO操作就可以了,但是反过来要Proactor剥离出Reactor几乎是不可能的,所以当时研究Libevent在Windows平台下好像就只支持select机制,所以预估Libevent在Windows平台下的并发量性能有限。Boost.Asio则是选择了Proactor模式,完全利用了Windows的异步特性,同时在Linux平台下通过增强Reactor来实现Proactor模式,我想这也是Boost.Asio作为一个跨平台的网络库所了不起的地方,同时也是其立志进入C++标准最有力的一票。

一、前期准备

对于了解一个项目,最佳实践当然是Read The Fuck Code!
可是在阅读项目代码的时候,虽然也可以像读文章一样人工判断出执行流程,但是如果能够以边调试边运行的方式来跟踪,那么结果将会更加的确信可靠。在Linux下面,虽然编辑神器Vim/Emacs可以各种外挂配置形成超级IDE神器,但是自己这个年纪还是怕折腾了——直接用的SlickEdit,这家伙号称是最贵的IDE,但是确实挺好用的,而且是跨平台的(但是真正Windows下大多人还是会用微软自家的Visual Studio吧,真幸福),等在下能够经济自由的那一天,定要好支持一下。SlickEdit对于一般的代码是做的很好的,但是对于大量模板情况下的跟踪,还是有所缺陷,比如没法显示实例化的模板参数类型,希望能不断更新改进。
我在学习调试的时候,是直接使用upstream的boost版本,但是很多发行版会打包甚至安装一些旧的发布版本,为了使用自己控制的upstream版本,需要额外的配置一下环境:
(a). 在GitHub上面clone最新的boost源代码,然后编译安装到本地的目录,参考的编译命令可以是:

1
2
3
4
5
➜  boost git:(master) ✗ ./bootstrap.sh
➜ boost git:(master) ✗ ./b2 headers
➜ boost git:(master) ✗ mkdir builddir installdir
➜ boost git:(master) ✗ ./b2 --build-dir=builddir --prefix=installdir -a -q toolset=gcc variant=debug
➜ boost git:(master) ✗ #./b2 -j4 --build-dir=builddir --prefix=/home/taozj/root/ --without-python -a -q toolset=gcc cxxflags="-std=c++0x" variant=debug threading=multi install

(b). 随便新建一个项目,Boost.Asio的example中有一大把,比如找一个包含包含async_read/write_some调用的简单例程的(比如async_udp_echo_server.cpp),然后修改项目的Makefile,将自己指定的boost安装目录中的include和lib添加到编译环境中去。Makefile的例子可以参考我项目中使用的
此外,还要修改系统的LD_LIBRARY_PATH,可以通过在/etc/ld.conf.d目录中添加一个上面的安装目录,然后运行ldconfig刷新一下系统即可,否则最终链接的时候还是会报错找不到符号的。
(c). 这个时候,就可以使用ldd命令查看编译出来的可执行文件依赖哪些链接库,看看是不是最终依赖到了指定的本地版本boost,同时也可以运行一下可执行程序看是否正常。

1
2
3
4
5
6
7
8
9
➜  asio_learn ldd Debug/asio_learn 
linux-vdso.so.1 => (0x00007ffd33dc9000)
libboost_system.so.1.62.0 => /home/user/Dropbox/ReadTheCode/boost/installdir/lib/libboost_system.so.1.62.0 (0x00007f277ad55000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f277ab38000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f277a7b5000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f277a59f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f277a1d6000)
/lib64/ld-linux-x86-64.so.2 (0x0000556214f39000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f2779ecc000)

(d). 修改SlickEdit编辑器,在Project Properties->Directories->Include中,把本地的installdir/include加到最前面去,刷新Retag一下Slickedit,查看里面的变量定义跳转文件是不是正确。
至此,这个学习跟踪环境就完成了。

二、io_service中三个成员变量

ioservice核心类主要成员变量只有三个:init、serviceregistry和impl_。
(a). [detail/signal_init.hpp] detail::signalinit<> init:在默认构造signal_init的时候,会将SIGPIPE的信号给忽略掉,这算基本是所有网络开发程序的默认行为;
(b). [io_service.hpp] impltype& impl:通过宏的控制,impl_type在Linux平台等价于task_io_service类型,Linux和Windows两种平台的操作在这里会进行分流;
(c). [detail/service_registry.hpp] detail::service_registry* serviceregistry:其实现类型是一个单例对象的侵入链表,其中每一个服务都有对应的ID号,Linux平台在后面经常遇到的服务有detail::task_io_service、detail::reactive_socket_service、detail::epoll_reactor,这些个类一方面是跟平台相关密切,同时他们之间的联系也是错综复杂,暂且不表吧!

[detail/impl/epoll_reactor.ipp]task_io_service中有几个重要的成员变量:
(a). atomic_count outstandingwork; 表示未完成的work数目,在work_start()的时候会增加,在work_finished()的时候会递减;
(b). [detail/reactorfwd.hpp] reactor* task; 这个reactor在reactor_fwd.hpp中通过宏BOOST_ASIO_HAS_XXX来进行控制的,这也就是说没有Libevent那种可以通过调用代码的方式灵活的选择选择select/poll/epoll各种模型了,通常在Linux中会被定义成epoll_reactor;
(c). [detail/op_queue.hpp] op_queue opqueue; 这里实现了一个operation_queue的队列容器,同时定义了op_queue_access这个访问者类对这个op队列进行操作管理。这个队列是多个线程共享的,存储的元素可以是descriptor_state、reactor_op类型,后面会经常涉及到这两种类型。

三、Initiator发起异步请求

从最常见的async_read_some这个socket读取请求,看看Initiator发起的这条路径是怎么处理的。
当开始创建和连接一个套接字的时候,其名字作用空间是[ip/tcp.hpp]ip::tcp::socket,从代码中可以看到所谓的socket(basic_stream_socket)、accept(basic_socket_acceptor)、resolver(basic_resolver)都是一个个typedef的别名而已,而[basic_stream_socket.hpp]basic_stream_socket经过模板实例化,其类声明就变成了

1
2
class basic_stream_socket
: public basic_socket<tcp, stream_socket_service<tcp>>

看到这个basic_streamsocket中的成员函数,让人宽心了一点点,因为我们开发时候常见的IO操作接口(async)send、(async)receive、(async)writesome、(async)read_some都显露出来了,因为这些函数本来就是socket调用的嘛,定睛一看其基本都是调用this->get_service()中的同名成员函数,而既然basic_stream_socket中没有get_service()的定义,那么这个函数一定是在其基类中被继承而来的(原谅我后面的这些模板参数我直接替换掉了):

1
2
explicit basic_socket(boost::asio::io_service& io_service)
: basic_io_object<stream_socket_service>(io_service);

basic_socket的构造函数需要传递一个io_service的参数,这个参数后面喂给了basic_io_object,就是这个类消化了这个参数:

1
2
3
4
5
6
7
explicit basic_io_object(boost::asio::io_service& io_service)
: service(boost::asio::use_service<stream_socket_service>(io_service)){
service.construct(implementation);
}

service_type& get_service()
{ return service; }

下面就需要看看这个[stream_socket_service.hpp]stream_socket_service东西是个什么鬼了,原来在Linux平台下,它的实现就是

1
2
3
4
// The platform-specific implementation.
service_impl_type service_impl_;

typedef detail::reactive_socket_service<tcp> service_impl_type;

[detail/reactive_socket_service.hpp]reactive_socket_service可是个好类啊,其继承自reactive_socket_service_base,两个类中几乎囊括了所有对于socket的设置、IO等操作,当然为了整洁起见,所有对于socket的控制操作和底层收发操作都被实现成了自由函数,并封装在[detail/impl/socket_ops.ipp]socket_ops名字空间中。
例如上面,对于socket.async_read_some()这个调用,那么它的调用链就是(其中的<->表示继承关系):

1
2
3
4
5
basic_stream_socket::get_service().async_receive(this->get_implementation(),
buffers, 0, BOOST_ASIO_MOVE_CAST(ReadHandler)(handler));

basic_stream_socket <-> basic_socket <-> basic_io_object::service
reactive_socket_service <-> reactive_socket_service_base::async_receive

然后整个async_receive的实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// buffer must be valid for the lifetime of the asynchronous operation.
template <typename MutableBufferSequence, typename Handler>
void async_receive(base_implementation_type& impl,
const MutableBufferSequence& buffers,
socket_base::message_flags flags, Handler& handler)
{
bool is_continuation =
boost_asio_handler_cont_helpers::is_continuation(handler);

// Allocate and construct an operation to wrap the handler.
typedef reactive_socket_recv_op<MutableBufferSequence, Handler> op;
typename op::ptr p = { boost::asio::detail::addressof(handler),
boost_asio_handler_alloc_helpers::allocate(
sizeof(op), handler), 0 }; // h v p
p.p = new (p.v) op(impl.socket_, impl.state_, buffers, flags, handler);

BOOST_ASIO_HANDLER_CREATION((p.p, "socket", &impl, "async_receive"));

start_op(impl,
(flags & socket_base::message_out_of_band)
? reactor::except_op : reactor::read_op,
p.p, is_continuation,
(flags & socket_base::message_out_of_band) == 0,
((impl.state_ & socket_ops::stream_oriented)
&& buffer_sequence_adapter<boost::asio::mutable_buffer,
MutableBufferSequence>::all_empty(buffers)));
p.v = p.p = 0;
}

这么一大段我先拷贝在这里,说句实话写的现在我确实看不懂,只知道有buffers和handler,后面发信给原作者问是什么回事,这才有些明白。对于reactive_socket_recv_op::ptr的类型来源,可以发现在reactive_socket_recv_op类定义中有BOOST_ASIO_DEFINE_HANDLER_PTR这么一个宏,而这个宏定义在头文件[detail/handler_alloc_helpers.hpp]当中,在其中辅助生成了一个ptr类型的内部类,其实也是做了一个RAII的作用,并且可以可选择性的释放成员p.p、p.v的内容,p.h一般用来存储handler。

1
2
3
BOOST_ASIO_DEFINE_HANDLER_PTR(reactive_socket_recv_op);

p.p = new (p.v) op(impl.socket_, impl.state_, buffers, flags, handler);

那么构造出来的p.p就是reactive_socket_recv_op类型了,其中包含了我们传递进去的buffers和handler,同时在调用start_op的时候,这个p.p也就是大名鼎鼎的descriptor_data。
然后就要关注通过调用startop函数,这个函数映射到reactor(epoll_reactor)的start_op操作,其这里完成的的工作主要有:
(a). 通过epoll_ctl的方式将套接字的EPOLLOUT事件侦听添加进去,同时把descriptor_data作为event的私有数据存储在ev.data.ptr;
(b). 将当前的操作的reactor_op添加到descriptor_data->opqueue[op_type].push(op);这个依据操作类型相关(read_op = 0, write_op = 1, connect_op = 1, except_op = 2)的队列里面去;
(c). 调用work_start()来增加outstandingwork的计数。
自此客户端发送的async_read_some已经被分派到操作系统epoll中去侦听相应注册的事件了,同时其buffers、handler等重要数据也被添加到了op队列中——完事具备,只欠东风了!

四、io_service中收集就绪事件

在开发中,我们都是先期做一系列的准备工作(比如增加accept的连接handler、信号处理回调等),最后调用io_service.run()方法,从此主线程在此阻塞起来进行事件循环。当然,在ioservice中与run同类的函数还要包括好几种,只是没有run()这么常用而已。这些函数都是映射到实现部分impl(task_io_service)来调用的:
(a). std::size_t run(); 一直阻塞直到所有的任务都完成,或者没有其他handler可以被dispatch、io_service被stop掉;多个线程可以调用同一个io_service的run(),他们共同组成没有差异的线程池模式;本函数的正常退出是调用stop或者run out of work。
(b). std::size_t run_one(); 最多执行一个handler,它会阻塞等待直到一个handler被dispatched、或者io_service被stop。
(c). std::size_t poll(); 该函数不会阻塞,它会立即运行可以运行的handlers,直到没有剩余的ready handler、或者io_service被stop。
(d). std::size_t poll_one(); 非阻塞的运行至多一个handler。
另外还有两个常用到的成员函数dispatch()和post(),只是我们在调用async_xxxx的时候隐含的实现了他们相同的功能:
(e). dispatch(CompletionHandler handler) 将会请求io_service立即执行给定的handler,如果当前线程是调用run(), run_one(), poll(), poll_one()的线程,那么这个handler将会被立即执行,否则会插入到task_io_service::opqueue队列中;出现这种判断也不奇怪,因为任何的线程如果拿到io_service对象都可以用它发起异步操作请求,或者直接dispatch()/post()添加异步请求;
(f). post(CompletionHandler handler) 请求io_service立即执行给定的handler然后立即返回,实际就是插入到task_io_service::opqueue当中然后就返回了。

就捡最常见的run来说事吧,其调用链为
io_service::run() -> task_io_service::run(),核心代码如下

1
2
3
4
std::size_t n = 0;
for (; do_run_one(lock, this_thread, ec); lock.lock())
if (n != (std::numeric_limits<std::size_t>::max)()) //计数操作
++n;

这个n就是run()返回的数目,代表了已经执行handler的计数,最大值不会超过size_t的类型最大值,关键点就落在了这个do_run_one上面了(暂时把多线程方面的东西拿掉了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
while (!stopped_) {
if (!op_queue_.empty()) {
operation* o = op_queue_.front();
op_queue_.pop();
bool more_handlers = (!op_queue_.empty());

if (o == &task_operation_) {
task_interrupted_ = more_handlers;
task_cleanup on_exit = { this, &lock, &this_thread };
(void)on_exit;
task_->run(!more_handlers, this_thread.private_op_queue);
}
else {
std::size_t task_result = o->task_result_; //events
work_cleanup on_exit = { this, &lock, &this_thread };
(void)on_exit;
o->complete(*this, ec, task_result);
return 1;
}
}
else {
wakeup_event_.clear(lock);
wakeup_event_.wait(lock);
}
}
return 0;
}

成员变量taskoperation是一个task_io_service_operation类型,这里涉及到一个分流操作。跟踪代码发现在task_init以及这里RAII的task_io_service::task_cleanup的行为看来,taskoperation的效果都像是扮演者一个队列的尾端标记的作用,当队列中取出这个op就意味表示就绪的事件处理完了,需要重新收集就绪事件了,所以
(a). 当弹出的队列元素是taskoperation表示没有任务可以处理了,此时会调用task_->run(!more_handlers, this_thread.private_op_queue);,这个操作会通过epoll_reactor::run调用底层的epoll_wait收集更多的就绪事件,其本质就对应着下面的调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[detail/impl/epoll_reactor.ipp] void epoll_reactor::run(bool block, op_queue<operation>& private_op_queue);
// Block on the epoll descriptor.
epoll_event events[128];
int num_events = epoll_wait(epoll_fd_, events, 128, timeout);

// Dispatch the waiting events.
for (int i = 0; i < num_events; ++i){
void* ptr = events[i].data.ptr; //私有数据descriptor_state
{
descriptor_state* descriptor_data = static_cast<descriptor_state*>(ptr);
descriptor_data->set_ready_events(events[i].events);
private_op_queue.push(descriptor_data);
}
}

上面的代码会根据epoll_wait的结果,将就绪事件所关联的descriptor_state数据添加到调用线程的this_thread.private_op_queue这个私有队列上面去。
(b). 添加到this_thread.private_op_queue就不管了么?当然不是,这里添加了一个RAII类型的task_io_service::task_cleanup,会自动把thisthread->private_op_queue的任务添加到task_ioservice->opqueue这个全局队列当中去,同时添加一个垫底的taskoperation,再次等待io_service调度这个任务。下个循环时候,队列弹出来的元素就不是taskoperation,而是descriptor_state类型,此后就有文章可做了,taskresult表示就绪的事件集合。这里需要注意descriptor_state是在派生了operation(task_io_service_operation)的同时,也添加了一些自己的操作,当调用descriptorstate::complete()函数的时候,其调用了自身的func函数,要想知道这个func_究竟是哪个函数实体,还得看它在构造的时候怎么初始化的,构造函数表明:

1
2
3
[detail/impl/epoll_reactor.ipp]
epoll_reactor::descriptor_state::descriptor_state()
: operation(&epoll_reactor::descriptor_state::do_complete)

这个func_是被初始化成了descriptor_state::do_complete成员函数,所以这里等价调用了descriptor_state::do_complete,其所做的操作为根据events调用descriptor_data->perform_io(events)函数,而perform_io函数再根据传递进来的就绪事件类型,依次调用对应具体事件的perform()函数,至于这里为啥complete会绕圈而不直接调用performio(),其实是因为这个complete()->func会被复用,类似于设计模式下的状态模式,等你看到下面就自然明白了。因此其调用链为:

1
2
descriptor_state::complete() -> func_ -> do_complete() -> perform_io() -> reactor_op::perform()
typedef bool (*perform_func_type)(reactor_op*);

perform调用的是performfunc函数。在之添加异步操作请求调用start_op的时候,创建了reactive_socket_recv_op对象,在其构造函数中可以清晰地看到:

1
reactor_op(&reactive_socket_recv_op_base::do_perform, complete_func)

所以这个performfunc就是reactive_socket_recv_op_base::do_perform成员函数,打开这个函数的定义,实际的IO操作就自然显现出来了,这里的接收函数,以及很多同类的IO操纵函数,都是被定义在socket_ops名字空间中的自由函数,上面已经说到了。

1
2
3
return socket_ops::non_blocking_recv(o->socket_, bufs.buffers(), bufs.count(), 
o->flags_, (o->state_ & socket_ops::stream_oriented) != 0,
o->ec_, o->bytes_transferred_);

在perform_io的函数中,还定义了一个十分重要的RAII对象io_cleanup,原理和上面的task_cleanup一样都是RAII的典型用例,可以学习过来。

1
2
3
4
5
6
7
8
operation* epoll_reactor::descriptor_state::perform_io(uint32_t events)
{
perform_io_cleanup_on_block_exit io_cleanup(reactor_);
...
io_cleanup.first_op_ = io_cleanup.ops_.front();
io_cleanup.ops_.pop();
return io_cleanup.first_op_;
}

在io_cleanup的析构函数中,首先会返回第一个reactor_op对象io_cleanup.firstop,然后后面如果还有处理过的op(比如event中堆积了多个就绪的事件),就会调用reactor_->ioservice.post_deferredcompletions(ops)把剩余的处理完的opn依次添加到task_io_service::opqueue上面去。
所以,第一个完成的op会被直接调用,其余的op会被添加全局的task_io_service::opqueue队列上面去。至此,该descriptor_state的底层的IO已经全部完成了。

在上面的perform_io返回了io_cleanup.firstop之后,会调用它的complete函数:

1
2
3
4
5
6
7
8
9
void epoll_reactor::descriptor_state::do_complete(
io_service_impl* owner, operation* base,
const boost::system::error_code& ec, std::size_t bytes_transferred)
{
...
if (operation* op = descriptor_data->perform_io(events)) {
op->complete(*owner, ec, 0);
}
}

可能会觉得,这些对象被反复添加到task_io_service::opqueue中,而且都是调用complete,那么IO之前的complete和IO之后的complete是怎么区分的呢?其技巧在于:
(a). 第一次IO之前传递进去的是descriptorstate对象,在构造的时候将基类的operation::complete()的调用函数通过func媒介,指向了自己的成员函数do_complete(),在这个函数中定义执行了perform_io()操作;
(b). 在IO结束之后,其返回的类型是reactor_op,该对象的complete(),实际是调用最初我们启动async_read()提供的回调函数。
因为这两个类都是继承自operation的,所以都可以放到全局opqueue当中,公用同一套处理流程,而实际的操作会根据对象的不同而不同,算不算是一个状态模式呢。同时,这里也需要弄清楚:descriptor_state是以描述符为对象的,而reactor_op是以事件为对象的,一个描述符可以侦听多个事件,多个事件的op信息使用op_queue opqueue[max_ops];数组来保存。
通过最终调用reactive_socket_recv_op::do_complete,函数会将调用结果的ec和bytes_transferred传递进去,然后用户提供的回调函数被正式调用,整个异步操作流程到此结束。

自此,Initiator发起异步请求操作和Proactor发起套接字socket的事件侦听和处理就连接起来了,整个工作流程表述为:
(a). Intiator通过async_read_some发起请求,同时提供buffer和callback信息;
(b). 然后reactive_socket_service一方面将socket关注的事件添加到epoll侦听中去,同时将reactor_op相关的信息添加到与事件对应的opqueue的队列中;
(c). 另外通过io_service::run(),线程会通过do_runone()->task->run()->epoll_wait()的方式等待socket就绪的事件,一旦发现就会取出来并将descriptor_state添加到io_service队列上,io_service调度到这个descriptor_state后会调用perform_io进行底层的IO操作;
(d). IO完成后,会随即调用第一个reactor_op的complete;此时如果还有其它的reactor_op,会将剩余的添加到io_service::opqueue队列,让io_service稍后调度执行对应的complete。这些complete会最终会调用用户异步操作时候提供的回调函数,同时更新传递进来的引用参数做为调用结果。

五、composed operation

之前提到,async_read/write,async_read_until函数是composed operatio,比如一次调用async_read可能会调用零到多次的async_read_some直到某些条件被满足。这里的composed operation是自由函数,在[read.hpp,write.hpp]中声明和定义的,同时根据提供的buffer类型具有MutableBufferSequence和basic_streambuf两大类的重载版本。这里我们先只考虑MutableBufferSequence这种类型。
如果只考虑一种buffer类型(比如MutableBufferSequence),那么其async_read其实还有两种重载类型:一个是用户指定完成条件的,一个是没有指定的。从底层实现看来,两者的实现是极为相似的,只是后者提供了默认的结束条件,差异就在于当用户没有提供CompletionCondition的时候,默认是填满buffer的容量、或者发送错误的情况下async_read结束。为了使用方面,其实对于CompletionCondition条件谓语,Boost.Asio也为我们提供了很多预先定义好的类型,比如boost::asio::transfer_all()等。
贴出async_read的函数体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template <typename AsyncReadStream, typename MutableBufferSequence,
typename ReadHandler>
inline BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler,
void (boost::system::error_code, std::size_t))
async_read(AsyncReadStream& s, const MutableBufferSequence& buffers,
BOOST_ASIO_MOVE_ARG(ReadHandler) handler)
{
detail::async_result_init<
ReadHandler, void (boost::system::error_code, std::size_t)> init(
BOOST_ASIO_MOVE_CAST(ReadHandler)(handler));

detail::read_op<AsyncReadStream, MutableBufferSequence,
detail::transfer_all_t, BOOST_ASIO_HANDLER_TYPE(
ReadHandler, void (boost::system::error_code, std::size_t))>(
s, buffers, transfer_all(), init.handler)(
boost::system::error_code(), 0, 1);

return init.result.get();
}

我靠,又是一个极其复杂的函数头,函数体主要执行了两个模板函数detail::async_result_init和detail::read_op,
在detail::read_op的operator()调用中,可以看到在一个for(;;)有关于async_readsome()的调用,不过这个for(;;)写的也非常诡异,只有在break的时候才表示执行结束的条件满足了,接下来调用handler,否则会return掉,表示接下来还会再次跳入for(;;)循环中。

花了两天的功夫,总算看完了,也只能算是了解了个大概,很多的东西还是没懂。这个Boost.Asio中充斥着大量的闭包、模板元等特性,需要及其严密的逻辑和对整体的把控才能开发出来并稳定运行,由此真心膜拜一下大神Christopher M. Kohlhoff,感谢你对C++ Network的工作,希望你的代码能让我看懂!

参考文献