所以我一直在努力在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_
方法。
是建议以这种方式做事,还是我必须坚持使用异步方法?如果是后者,我可以使用哪些技术来简化回调链式样板?
如果编写处理程序对你很烦,你可以考虑使用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
coroutine timer
开始了。当计时器在建立连接之前到期时,我们取消解析并关闭套接字。如果在计时器到期之前建立了连接,我们取消计时器,我们可以使用sock
执行一些操作。
establishConnection
和doTimeout
的身体可以移动到lambda,作为asio::spawn
函数的参数。因此,我们只能有一个成员函数,并且没有执行3个异步操作的代码的处理程序。如果这满足您,请开始使用协同程序。