简而言之,包装一个处理程序和asio_handler_invoke 完成两个不同的任务:
- 包装处理程序以自定义处理程序的调用。
- 定义
asio_handler_invoke 挂钩以在处理程序的上下文中自定义其他处理程序的调用。
template <typename Handler>
struct custom_handler
{
void operator()(...); // Customize invocation of handler_.
Handler handler_;
};
// Customize invocation of Function within context of custom_handler.
template <typename Function>
void asio_handler_invoke(Function function, custom_handler* context);
// Invoke custom invocation of 'perform' within context of custom_handler.
void perform() {...}
custom_handler handler;
using boost::asio::asio_handler_invoke;
asio_handler_invoke(std::bind(&perform), &handler);
asio_handler_invoke 挂钩的主要原因是允许自定义应用程序可能无法直接访问的处理程序的调用策略。例如,考虑由零个或多个对中间操作的调用组成的组合操作。对于每个中间操作,将代表应用程序创建一个中间处理程序,但应用程序没有直接访问这些处理程序的权限。使用自定义处理程序时,asio_handler_invoke 钩子提供了一种在给定上下文中自定义这些中间处理程序的调用策略的方法。 documentation 声明:
当异步操作由其他异步操作组成时,应使用与最终处理程序相同的方法调用所有中间处理程序。这是为了确保不会以可能违反保证的方式访问用户定义的对象。这个 [asio_handler_invoke] 挂钩函数确保用于最终处理程序的调用方法在每个中间步骤都可访问。
asio_handler_invoke
假设我们希望计算执行的异步操作的数量,包括组合操作中的每个中间操作。为此,我们需要创建一个自定义处理程序类型counting_handler,并计算在其上下文中调用函数的次数:
template <typename Handler>
class counting_handler
{
void operator()(...)
{
// invoke handler
}
Handler handler_;
};
template <typename Function>
void asio_handler_invoke(Function function, counting_handler* context)
{
// increment counter
// invoke function
}
counting_handler handler(&handle_read);
boost::asio::async_read(socket, buffer, handler);
在上面的sn-p中,函数handle_read被counting_handler包裹。由于counting_handler 对计算包装处理程序被调用的次数不感兴趣,所以它的operator() 不会增加计数而只是调用handle_read。但是,counting_handler 对async_read 操作中在其上下文中调用的处理程序的数量感兴趣,因此asio_handler_invoke 中的自定义调用策略将增加计数。
示例
这里是一个基于上面counting_handler类型的具体例子。 operation_counter 类提供了一种使用counting_handler 轻松包装应用程序处理程序的方法:
namespace detail {
/// @brief counting_handler is a handler that counts the number of
/// times a handler is invoked within its context.
template <class Handler>
class counting_handler
{
public:
counting_handler(Handler handler, std::size_t& count)
: handler_(handler),
count_(count)
{}
template <class... Args>
void operator()(Args&&... args)
{
handler_(std::forward<Args>(args)...);
}
template <typename Function>
friend void asio_handler_invoke(
Function intermediate_handler,
counting_handler* my_handler)
{
++my_handler->count_;
// Support chaining custom strategies incase the wrapped handler
// has a custom strategy of its own.
using boost::asio::asio_handler_invoke;
asio_handler_invoke(intermediate_handler, &my_handler->handler_);
}
private:
Handler handler_;
std::size_t& count_;
};
} // namespace detail
/// @brief Auxiliary class used to wrap handlers that will count
/// the number of functions invoked in their context.
class operation_counter
{
public:
template <class Handler>
detail::counting_handler<Handler> wrap(Handler handler)
{
return detail::counting_handler<Handler>(handler, count_);
}
std::size_t count() { return count_; }
private:
std::size_t count_ = 0;
};
...
operation_counter counter;
boost::asio::async_read(socket, buffer, counter.wrap(&handle_read));
io_service.run();
std::cout << "Count of async_read_some operations: " <<
counter.count() << std::endl;
async_read() 组合操作将在零个或多个中间stream.async_read_some() 操作中实现。对于这些中间操作中的每一个,将创建并调用具有未指定类型的处理程序。如果上面的async_read()操作是按照2中间async_read_some()操作实现的,那么counter.count()就是2,并且从counter.wrap()返回的handler被调用一次。
另一方面,如果一个人不提供asio_handler_invoke 钩子,而是仅在包装处理程序的调用中增加计数,那么计数将为1,仅反映包装处理程序的次数调用:
template <class Handler>
class counting_handler
{
public:
...
template <class... Args>
void operator()(Args&&... args)
{
++count_;
handler_(std::forward<Args>(args)...);
}
// No asio_handler_invoke implemented.
};
这是一个完整的示例demonstrating,计算执行的异步操作的数量,包括来自组合操作的中间操作。该示例仅启动三个异步操作(async_accept、async_connect 和 async_read),但 async_read 操作将由 2 中间 async_read_some 操作组成:
#include <functional> // std::bind
#include <iostream> // std::cout, std::endl
#include <utility> // std::forward
#include <boost/asio.hpp>
// This example is not interested in the handlers, so provide a noop function
// that will be passed to bind to meet the handler concept requirements.
void noop() {}
namespace detail {
/// @brief counting_handler is a handler that counts the number of
/// times a handler is invoked within its context.
template <class Handler>
class counting_handler
{
public:
counting_handler(Handler handler, std::size_t& count)
: handler_(handler),
count_(count)
{}
template <class... Args>
void operator()(Args&&... args)
{
handler_(std::forward<Args>(args)...);
}
template <typename Function>
friend void asio_handler_invoke(
Function function,
counting_handler* context)
{
++context->count_;
// Support chaining custom strategies incase the wrapped handler
// has a custom strategy of its own.
using boost::asio::asio_handler_invoke;
asio_handler_invoke(function, &context->handler_);
}
private:
Handler handler_;
std::size_t& count_;
};
} // namespace detail
/// @brief Auxiliary class used to wrap handlers that will count
/// the number of functions invoked in their context.
class operation_counter
{
public:
template <class Handler>
detail::counting_handler<Handler> wrap(Handler handler)
{
return detail::counting_handler<Handler>(handler, count_);
}
std::size_t count() { return count_; }
private:
std::size_t count_ = 0;
};
int main()
{
using boost::asio::ip::tcp;
operation_counter all_operations;
// Create all I/O objects.
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
tcp::socket socket1(io_service);
tcp::socket socket2(io_service);
// Connect the sockets.
// operation 1: acceptor.async_accept
acceptor.async_accept(socket1, all_operations.wrap(std::bind(&noop)));
// operation 2: socket2.async_connect
socket2.async_connect(acceptor.local_endpoint(),
all_operations.wrap(std::bind(&noop)));
io_service.run();
io_service.reset();
// socket1 and socket2 are connected. The scenario below will:
// - write bytes to socket1.
// - initiate a composed async_read operaiton to read more bytes
// than are currently available on socket2. This will cause
// the operation to complete with multple async_read_some
// operations on socket2.
// - write more bytes to socket1.
// Write to socket1.
std::string write_buffer = "demo";
boost::asio::write(socket1, boost::asio::buffer(write_buffer));
// Guarantee socket2 has received the data.
assert(socket2.available() == write_buffer.size());
// Initiate a composed operation to more data than is immediately
// available. As some data is available, an intermediate async_read_some
// operation (operation 3) will be executed, and another async_read_some
// operation (operation 4) will eventually be initiated.
std::vector<char> read_buffer(socket2.available() + 1);
operation_counter read_only;
boost::asio::async_read(socket2, boost::asio::buffer(read_buffer),
all_operations.wrap(read_only.wrap(std::bind(&noop))));
// Write more to socket1. This will cause the async_read operation
// to be complete.
boost::asio::write(socket1, boost::asio::buffer(write_buffer));
io_service.run();
std::cout << "total operations: " << all_operations.count() << "\n"
"read operations: " << read_only.count() << std::endl;
}
输出:
total operations: 4
read operations: 2
组合处理程序
在上面的示例中,async_read() 处理程序由一个包裹两次的处理程序组成。首先是 operation_counter,它只计算读取操作,然后生成的仿函数被 operation_counter 包装,计算所有操作:
boost::asio::async_read(..., all_operations.wrap(read_only.wrap(...)));
counting_handler 的asio_handler_invoke 实现被编写为通过在包装处理程序上下文的上下文中调用函数来支持组合。这会导致对每个 operation_counter 进行适当的计数:
template <typename Function>
void asio_handler_invoke(
Function function,
counting_handler* context)
{
++context->count_;
// Support chaining custom strategies incase the wrapped handler
// has a custom strategy of its own.
using boost::asio::asio_handler_invoke;
asio_handler_invoke(function, &context->handler_);
}
另一方面,如果asio_handler_invoke 显式调用function(),则只会调用最外层包装器的调用策略。在这种情况下,这将导致all_operations.count() 成为4 和read_only.count() 成为0:
template <typename Function>
void asio_handler_invoke(
Function function,
counting_handler* context)
{
++context->count_;
function(); // No chaining.
}
在编写处理程序时,请注意被调用的asio_handler_invoke 钩子是通过argument-dependent lookup 定位的,因此它基于确切的处理程序类型。使用不知道asio_handler_invoke 的类型组合处理程序将阻止调用策略的链接。例如,使用std::bind() 或std::function 将导致调用默认的asio_handler_invoke,从而导致调用自定义调用策略:
// Operations will not be counted.
boost::asio::async_read(..., std::bind(all_operations.wrap(...)));
组合处理程序的正确链接调用策略可能非常重要。例如,从strand.wrap() 返回的未指定处理程序类型提供了由链包装的初始处理程序和在返回处理程序的上下文中调用的函数不会同时运行的保证。这允许在使用组合操作时满足许多 I/O 对象的线程安全要求,因为 strand 可用于与应用程序无权访问的这些中间操作同步。
当多个线程运行io_service时,下面的sn-p可能会调用未定义的行为,因为两个组合操作的中间操作可能会同时运行,因为std::bind()不会调用适当的asio_handler_hook:
boost::asio::async_read(socket, ..., std::bind(strand.wrap(&handle_read)));
boost::asio::async_write(socket, ..., std::bind(strand.wrap(&handle_write)));