Boost.Asio-进阶话题
这一章对Boost.Asio的一些进阶话题进行了阐述。在日常编程中研究这些问题是不太可能的,但是知道这些肯定是有好处的:
- 如果调试失败,你需要看Boost.Asio能帮到你什么
- 如果你需要处理SSL,看Boost.Asio能帮你多少
- 如果你指定一个操作系统,看Boost.Asio为你准备了哪些额外的特性
Asio VS Boost.Asio
Boost.Asio的作者也保持了Asio。你可以用Asio的方式来思考,因为它在两种情况中都有:Asio(非Boost的)和Boost.Asio。作者声明过更新都会先在非Boost中出现,然后过段时间后,再加入到Boost的发布中。
不同点被归纳到下面几条:
- Asio被定义在asio::的命名空间中,而Boost.Asio被定义在boost::asio::中
- Asio的主头文件是asio.hpp,而Boost.Asio的头文件是boost/asio.hpp
- Asio也有一个启动线程的类(和boost::thread一样)
- Asio提供它自己的错误码类(asio::error_code代替boost::system::error_code,然后asio:system_error代替boost::systrem::system_error)
你可以在这里查阅更多Asio的信息:http://think_async.com
你需要自己决定你选择的版本,我选择Boost.Asio。下面是一些当你做选择时需要考虑的问题:
- Asio的新版本比Boost.Asio的新版本发布要早(因为Boost的版本更新比较少)
- Asio只有头文件(而Boost.Asio的部分依赖于其他Boost库,这些库可能需要编译)
- Asio和Boost.Asio都是非常成熟的,所以除非你非常需要一些Asio新发布的特性,Boost.Asio是非常保险的选择,而且你也可以同时拥有其他Boost库的资源
尽管我不推荐这样,你可以在一个应用中同时使用Asio和Boost.Asio。如果情况允许,这自然会发生,比如,如果你使用Asio,然后一些第三方库是Boost.Asio,反之亦然。
调试
调试同步应用往往比调试异步应用要简单。对于同步应用,如果阻塞了,你会跳转进入调试,然后你会知道你在哪(同步意味着有序的)。然后,如果是异步,事件不是有序发生的,所以在调试中是非常难知道到底发生了什么的。
为了避免这种情况,首先,你需要深入了解协程。如果正确实现了,你基本不会碰到一点异步调试的问题。
以防万一,在做异步编码的时候,Boost.Asio拉了你一把;Boost.Asio允许“handler追踪”,当BOOST_ASIO_ENABLE_HANDLER_TRACKING被定义时,Boost.Asio会写很多辅助的输出到标准错误流,纪录时间,异步操作,以及和完成处理handler的关系。
handler追踪信息
信息不是如此容易就能读懂的,但是还是非常有用。Boost.Asio的输出是@asio|<timestamp>|<action>|<description> 。
第一个标签永远都是@asio,因为其他代码也会输出到标准错误流(和std::error相当),所以你可以非常简单的用这个标签过滤从Boost.Asio打印出来的信息。timestamp实例从1970年1月1号到现在的秒数和毫秒数。action实例可以是下面任何一种:
- >n:这个在我们进入handler n的时候使用。description实力包含了我们发送给handler的参数。
- <n:这个在我们退出handler n的时候使用。
- !n:这个当我们因为异常退出handler n的时候使用。
- -n:这个当handler n在没有调用的情况就退出的时候使用;可能是因为io_service实例被删除地太快了(在n有机会被调用之前)
- n*m:这个当handler n创建了一个新的有完成处理hanlder m的异步操作时被调用。description实例展示的就是异步操作开始的地方。当你看到>m(开始)和<m(结束)时completion句柄被调用了。
- n:就像在description中展示的一样,这个当handler n做了一个操作的时候使用(可能是close或者cancel操作)。你一般可以忽略这些信息。
- 当n是0时,操作是在所有(异步)handler之外被执行的;你经常会在第一个操作时看到这个,或者当你使用信号量的其中一个被触发时。
- 你需要非常注意类型为!n和-n的信息,这些信息大部分都意味着你的代码有错误。在第一种情形中,异步方法没有抛出异常,所以,异常一定是你自己造成的;你不能让异常跑出你的completion句柄。第二种情形中,你可能太早就销毁了io_service实例,在所有完成处理句被调用之前。
一个例子
为了向你展示一个带帮助信息的例子,我们修改了在第六章 Boost.Asio其他特性 中使用的例子。你所需要做的仅仅是在包含boost/asio.hpp之前添加一个#define
-
#define BOOST_ASIO_ENABLE_HANDLER_TRACKING #include <boost/asio.hpp> ...
同时,我们也在用户登录和接受到第一个客户端列表时将信息输出到控制台中。输出会如下所示:
@asio|1355603116.602867|0*1|[email protected]_connect @asio|1355603116.604867|>1|ec=system:0 @asio|1355603116.604867|1*2|[email protected]_send @asio|1355603116.604867|<1| @asio|1355603116.604867|>2|ec=system:0,bytes_transferred=11 @asio|1355603116.604867|2*3|[email protected]_receive @asio|1355603116.604867|<2| @asio|1355603116.605867|>3|ec=system:0,bytes_transferred=9 @asio|1355603116.605867|3*4|[email protected] @asio|1355603116.605867|<3|
@asio|1355603116.605867|>4| John logged in @asio|1355603116.606867|4*5|[email protected] @asio|1355603116.606867|<4| @asio|1355603116.606867|>5| @asio|1355603116.606867|5*6|[email protected]_send @asio|1355603116.606867|<5| @asio|1355603116.606867|>6|ec=system:0,bytes_transferred=12 @asio|1355603116.606867|6*7|[email protected]_receive @asio|1355603116.606867|<6| @asio|1355603116.606867|>7|ec=system:0,bytes_transferred=14 @asio|1355603116.606867|7*8|[email protected] @asio|1355603116.607867|<7| @asio|1355603116.607867|>8| John, new client list: John
让我们一行一行分析:
我们进入async_connect,它创建了句柄1(在这个例子中,所有的句柄都是talk_to_svr::step)
- 句柄1被调用(当成功连接到服务端时)
- 句柄1调用async_send,这创建了句柄2(这里,我们发送登录信息到服务端)
- 句柄1退出
- 句柄2被调用,11个字节被发送出去(login John)
- 句柄2调用async_receive,这创建了句柄3(我们等待服务端返回登录的结果)
- 句柄2退出
- 句柄3被调用,我们收到了9个字节(login ok)
- 句柄3调用on_answer_from_server(这创建了句柄4)
- 句柄3退出
- 句柄4被调用,这会输出John logged in
- 句柄4调用了另外一个step(句柄5),这写ask_clients
- 句柄4退出
- 句柄5进入
- 句柄5,async_send_ask_clients,创建句柄6
- 句柄5退出
- 句柄6调用async_receive,这创建了句柄7(我们等待服务端发送给我们已存在的客户端列表)
- 句柄6退出
- 句柄7被调用,我们接受到了客户端列表
- 句柄7调用on_answer_from_server(这创建了句柄8)
- 句柄7退出
- 句柄8进去,然后输出客户端列表(on_clients)
这需要一会去理解,但是一旦你理解了,你就可以分辨出有问题的输出,从而找出需要被修复的那段代码。