所以我一直在努力在Boost.Asio上创建一些抽象层。我想自动处理某些批次的操作,例如tcp::resolver::resolve()
和asio::connect()
。如果我同时使用这两个版本的异步版本,则代码将变得非常讨厌,因为我必须“链接”回调。本质上:
Connect()
包装器方法,该方法接受一个主机和一个服务字符串,并且还提供一个在完成连接后调用的回调。resolver::async_resolve()
。将用户的回调绑定到回调以进行解析(以传递在连接后被调用的回调)asio::async_connect()
。再次,将用户的回调绑定到connect回调。由于大量嵌套的lambda,这是令人讨厌的,还是因为函数是分开的,所以这令人讨厌,现在我有一个装有样板的类。在我看来,仅要做这样的事情(还没有编译,因此暂时将其视为伪代码)会容易得多:
using ConnectCallback = std::function<void(std::shared_ptr<tcp::socket>)>;
void Connect(std::string const& host, std::string const& service, ConnectCallback cb)
{
std::thread{[this, host, service, cb{std::move(cb)}]
{
std::shared_ptr<tcp::socket> socket;
try
{
tcp::resolver r{m_context};
auto endpoints = r.resolve(host, service);
socket = std::make_shared<tcp::socket>(m_context);
asio::connect(*socket, endpoints);
}
catch (std::exception const&)
{
// either resolve or connect failed / timed out
}
cb(std::move(socket));
}}.detach();
}
对我来说,这要简单得多,至少对于启动连接而言,因为我不必担心这么多的回调。唯一的缺点是我不确定如何使用此方法处理超时情况。我在Google上发现的所有与超时相关的解决方案都需要使用async_
方法。
是否建议以这种方式执行操作,还是必须坚持使用异步方法?如果是后者,我可以使用什么技术来简化回调链接样板?
答案 0 :(得分:1)
如果编写处理程序对您来说很烦,您可以考虑使用coroutines。它与异步操作一起使用,使您可以实现超时。
struct Client2 {
Client2(asio::io_context& io)
: io(io) {}
asio::io_context& io;
asio::ip::tcp::resolver resolver{io};
asio::ip::tcp::socket sock{io};
asio::high_resolution_timer timer{io};
atomic_bool stopped{false};
void connect (const string& host, const string& service, int timeoutMs)
{
boost::asio::spawn(io,std::bind(&Client2::establishConnection,this,host,service,timeoutMs,std::placeholders::_1));
boost::asio::spawn(io,std::bind(&Client2::doTimeout,this,std::placeholders::_1));
}
void establishConnection (string host, string service, int timeoutMs,boost::asio::yield_context yield)
{
try {
timer.expires_after(std::chrono::milliseconds(timeoutMs)); // set timeout
auto res = resolver.async_resolve(host,service,yield);
// resume here when handler for resolving was called
if (stopped)
return;
asio::async_connect(sock,res,yield);
timer.cancel(); // connection is established, do sth with sock here, cancel timer
}
catch (std::exception& ex) {
}
}
void doTimeout (boost::asio::yield_context yield)
{
try {
timer.async_wait(yield); // throw exception when was canceled by startConnecting
}
catch (std::exception& ex) {
return;
}
resolver.cancel(); // timeout == timer expired, so cancel resolving and close socket
sock.close();
stopped = true;
}
};
// in main
asio::io_context io;
Client2 client{io};
client.connect("localhost","5444",200);
thread th([&](){ io.run(); }); // call run from at least 2 threads
io.run(); // establishConnection and doTimeout can be executed concurrently
th.join();
我在代码中添加了一些注释。
简而言之:使用了两个协程。在establishConnection
中,执行两个异步操作:async_resolve
和async_connect
。在doTimeout
中启动协程timer
。当计时器在建立连接之前到期时,我们取消解析并关闭套接字。如果在计时器到期之前建立了连接,则我们取消计时器,然后可以使用sock
执行某些操作。
establishConnection
和doTimeout
的主体可以作为asio::spawn
函数的参数移入lambda。因此,我们只能有一个成员函数,而没有执行3个异步操作的代码处理程序。如果您满意,请开始使用协程。