我想稍微扩大问题的范围,以涵盖我认为最终目标的替代方案:调试异步处理程序。
示例程序
为了通过示例显示调试,让我们从一个侦听端口 4321 的基本 UDP 回显服务器开始:
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
using boost::asio::ip::udp;
class udp_echo
{
public:
udp_echo(boost::asio::io_service& service,
unsigned int port)
: socket_(service, udp::endpoint(udp::v4(), port))
{
socket_.async_receive_from(
boost::asio::buffer(buffer_), sender_,
boost::bind(&udp_echo::handle_receive, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_receive(const boost::system::error_code& error,
std::size_t bytes_transferred)
{
socket_.async_send_to(
boost::asio::buffer(buffer_, bytes_transferred), sender_,
boost::bind(&udp_echo::handle_send, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_send(const boost::system::error_code& error,
std::size_t bytes_transferred)
{
socket_.close();
}
private:
udp::socket socket_;
boost::array<char, 128> buffer_;
udp::endpoint sender_;
};
int main()
{
boost::asio::io_service service;
udp_echo echo(service, 4321);
service.run();
}
这个简单的程序有一个异步调用链:
udp_echo::udp_echo()
{
socket_.async_receive_from(...); --.
} |
.-----------------------'
v
void udp_echo::handle_receive(...)
{
socket_.async_send_to(...); ------.
} |
.-----------------------'
v
void udp_echo::handle_send()
{
socket_.close();
}
处理程序跟踪
Boost 1.47 引入了处理程序跟踪。只需定义BOOST_ASIO_ENABLE_HANDLER_TRACKING
,Boost.Asio 就会将调试输出(包括时间戳)写入标准错误流。运行程序,并通过 UDP 发送“hello world”产生以下输出:
@asio|1363273821.846895|0*1|socket@0xbf8c4e3c.async_receive_from // 1
@asio|1363273829.288883|>1|ec=system:0,bytes_transferred=12 // 2
@asio|1363273829.288931|1*2|socket@0xbf8c4e3c.async_send_to // 3
@asio|1363273829.289013|<1| // 4
@asio|1363273829.289026|>2|ec=system:0,bytes_transferred=12 // 5
@asio|1363273829.289035|2|socket@0xbf8c4e3c.close // 6
@asio|1363273829.289075|<2| // 7
它可以逐行读取为:
- 调用非处理程序 (0)
socket.async_receive_from()
,创建处理程序 1。
- 输入处理程序 1
socket.async_receive_from()
没有错误并且已收到 12 个字节。
- 处理程序 1
socket.async_receive_from()
调用socket.async_send_to()
,创建处理程序 2。
- 退出处理程序 1
socket.async_receive_from()
。
- 输入处理程序 2
socket.async_send_to()
,没有错误,已发送 12 个字节。
- 处理程序 2 调用
socket.close()
。
- 退出处理程序 2
socket.async_send_to()
。
并在视觉上映射到以下内容:
udp_echo::udp_echo()
{
socket_.async_receive_from(...); --. // 1
} |
.-----------------------'
v
void udp_echo::handle_receive(...)
{ // 2
socket_.async_send_to(...); ------. // 3
} | // 4
.-----------------------'
v
void udp_echo::handle_send()
{ // 5
socket_.close(); // 6
} // 7
广发银行
通过 GDB 进行调试将需要挖掘多个层。它有助于了解 Boost.Asio 的一些实现细节。这里有几个概念:
io_service
仅包含准备运行的处理程序。
- 通常
reactor
将包含工作操作,以及尚未准备好运行的完成处理程序的句柄。
reactor
将向io_service
. _
这是一个调试会话:
(gdb) bt
#0 0x00ab1402 in __kernel_vsyscall ()
#1 0x00237ab8 in __epoll_wait_nocancel () from /lib/libc.so.6
#2 0x080519c3 in boost::asio::detail::epoll_reactor::run (this=0x80560b0,
block=true, ops=...)
at /opt/boost/include/boost/asio/detail/impl/epoll_reactor.ipp:392
#3 0x08051c2d in boost::asio::detail::task_io_service::do_run_one (
this=0x8056030, lock=..., this_thread=..., ec=...)
at /opt/boost/include/boost/asio/detail/impl/task_io_service.ipp:396
#4 0x08051e8a in boost::asio::detail::task_io_service::run (this=0x8056030,
ec=...)
at /opt/boost/include/boost/asio/detail/impl/task_io_service.ipp:153
#5 0x08051f50 in boost::asio::io_service::run (this=0xbfffe818)
at /opt/boost/include/boost/asio/impl/io_service.ipp:59
#6 0x08049a44 in main () at example.cpp:48
(gdb) frame 6
#6 0x08049a44 in main () at example.cpp:48
48 service.run();
首先,需要定位反应堆服务。向下转换需要发生,所以让我们使用调试器来定位一些类型:
(gdb) p service.service_registry_.init_keytab
init_key
init_key<boost::asio::datagram_socket_service<boost::asio::ip::udp> >
init_key< boost::asio::detail::epoll_reactor >
init_key<boost::asio::detail::task_io_service>
每个键都与一个特定的服务相关联,所有服务都维护在一个链表中service.service_registry_
。类型信息与它们相关联,使我们能够识别所需的服务。
(gdb) set $service = service.service_registry_.first_service_
(gdb) p $service.key_.type_info_.__name
$1 = 0x8052b60
"N5boost4asio6detail14typeid_wrapperINS0_23datagram_socket_serviceINS0_2ip3udpEEEEE"
那就是boost::asio::datagram_socket_service<boost::asio::ip::udp>
,所以继续下一个:
(gdb) set $service = $service.next_
(gdb) p $service.key_.type_info_.__name
$2 = 0x8052cc0 "N5boost4asio6detail14typeid_wrapperINS1_13epoll_reactorEEE"
$service
现在指向反应堆服务。根据init_key
type 参数向下转换服务:
(gdb) 设置 $service = *(' boost::asio::detail::epoll_reactor '*) $service
有工作的优秀处理程序位于反应器内的操作链接列表中:
(gdb) 设置 $ops = $service.registered_descriptors_.live_list_.op_queue_
(gdb) 设置 $op = $ops.front_
(gdb) p *$op
$3 = {<boost::asio::detail::task_io_service_operation> = {next_ = 0x0,
func_ = 0x804c256
< boost::asio::detail::reactive_socket_recvfrom_op<
boost::asio::mutable_buffers_1, boost::asio::ip::basic_endpoint<
boost::asio::ip::udp>, boost::_bi::bind_t<void,
boost::_mfi::mf2<void, udp_echo, boost::system::error_code const&,
unsigned int>, boost::_bi::list3<boost::_bi::value<udp_echo*>,
boost::arg<1> (*)(), boost::arg<2> (*)()> > > ::
do_complete(boost::asio::io_service::io_service_impl*,
boost::asio::detail::epoll_reactor::descriptor_state::operation*,
boost::system::error_code const&, size_t)>, task_result_ = 0}, ec_ = {
m_val = 11, m_cat = 0x13b2c8}, bytes_transferred_ = 0, perform_func_ =
0x80514c8 <boost::asio::detail::reactive_socket_recvfrom_op_base<
boost::asio::mutable_buffers_1,
boost::asio::ip::basic_endpoint<boost::asio::ip::udp>
>::do_perform(boost::asio::detail::reactor_op*)>}
需要另一个沮丧。强制$op
转换为func_
成员函数指针所属的类。
(gdb) 设置 $op = *(' boost::asio::detail::reactive_socket_recvfrom_op<
boost::asio::mutable_buffers_1, boost::asio::ip::basic_endpoint<
boost::asio::ip::udp>, boost::_bi::bind_t<void, boost::_mfi::mf2<
无效,udp_echo,boost::system::error_code const&,无符号整数>,
boost::_bi::list3<boost::_bi::value<udp_echo*>,
boost::arg<1> (*)(), boost::arg<2> (*)()> > > '*) $op
此操作包含所需的信息。
缓冲区:
(gdb) p $op.buffers_
$4 = {<boost::asio::mutable_buffer> = {data_ = 0xbfffe77c,
size_ = 128}, <No data fields>}
(gdb) p &echo.buffer_
$5 = (boost::array<char, 128u> *) 0xbfffe77c
this
实例:
(gdb) p $op.handler_.l_.a1_.t_
$6 = (udp_echo *) 0xbfffe768
(gdb) p &echo
$7 = (udp_echo *) 0xbfffe768
成员函数指针:
(gdb) p $op.handler_.f_.f_
$8 = (void (udp_echo::*)(udp_echo *, const boost::system::error_code &,
unsigned int)) 0x80505b0 <
udp_echo::handle_receive(boost::system::error_code const&, size_t)>
套接字信息:
(gdb) p $op.socket_
$9 = 10
(gdb) p echo.socket_.implementation.socket_
$10 = 10
在这种情况下,操作只知道本机套接字表示(文件描述符)。确定它是什么套接字的一种有用方法是查询 lsof。
$/usr/sbin/lsof -i -P | grep a.out
a.out 4265 ghost 10u IPv4 1166143 UDP *:4321
因此,文件描述符 10 正在侦听 UDP 4321。