为什么 boost deadline_timer 用作 async_connect 的超时在 unix 上不起作用?

Why does boost deadline_timer used as timeout for async_connect not work on unix?

我有一个程序使用 boost asio 异步连接到 3 个 TCP 套接字,使用 deadline_timer 作为连接超时。在 windows 上一切正常。连接在 5 秒后超时。然而,在 Unix(Ubuntu on WSL,Linux Mint VM,macOS)上,connectDeadline 永远不会触发。 async_connect 操作永远运行下去。为什么这不起作用,我怎样才能使它在 Unix 上也起作用?

代码: 注意:connect 是从主线程(也是 GUI 线程)调用的。

#include "NetManager.h"

NetManager::NetManager(NetManagerListener& listener) :  listener(listener),
connectDeadline(io),
                                                        socket1(io),
                                                        socket2(io),
                                                        socket3(io),
                                                        asioThread(&NetManager::handleAsioOperations, this){

}
NetManager::~NetManager() {
    running = false;
    io.stop();
    asioThread.join();
}

void NetManager::connect(){
    connectCounter = 0;
    hasHandledConnectError = false;
    socket1.async_connect(
            tcp::endpoint(boost::asio::ip::address::from_string(IP_STRING), PORT_1),
            boost::bind(&NetManager::handleConnect, this, _1));
    socket2.async_connect(
            tcp::endpoint(boost::asio::ip::address::from_string(IP_STRING), PORT_2),
            boost::bind(&NetManager::handleConnect, this, _1));
    socket3.async_connect(
            tcp::endpoint(boost::asio::ip::address::from_string(IP_STRING), PORT_3),
            boost::bind(&NetManager::handleConnect, this, _1));
    connectDeadline.expires_from_now(boost::posix_time::seconds(CONNECT_TIMEOUT));
    connectDeadline.async_wait(boost::bind(&NetManager::handleConnectTimeout, this, _1));
}

void NetManager::disconnect(){
    //NOTE: Close also cancels incomplete async operations
    socket1.close();
    socket2.close();
    socket3.close();
}

////////////////////////////////////////////////////////////////////////
/// ASIO Handlers
////////////////////////////////////////////////////////////////////////
void NetManager::handleAsioOperations(){
    while(running){
        io.run(); // Run any async operations
    }
}

void NetManager::handleConnect(const boost::system::error_code &ec){
    // When connections are canceled the handler is called with operation_aborted. No need to respond to that.
    if(ec && ec != boost::asio::error::operation_aborted && !hasHandledConnectError){
        hasHandledConnectError = true; // Likely to be 3 repeated errors. Make sure to only handle the first one
        cerr << "Connect Failed: " << ec.message() << endl;
        connectDeadline.cancel(); // Don't fire the timeout
        disconnect(); // Disconnect any already connected sockets
        connectedToRobot = false;
        listener.onConnect(false);
    }else if (!ec){
        connectCounter++;
    }

    if(connectCounter == 3){
        cout << "Successful connect" << endl;
        connectDeadline.cancel(); // Don't fire the timeout
        connectedToRobot = true;
        listener.onConnect(true);
    }
}

void NetManager::handleConnectTimeout(const boost::system::error_code &ec){
    if(ec != boost::asio::error::operation_aborted){
        cerr << "Connect timed out." << endl;
        disconnect(); // Disconnect any already connected sockets
        connectedToRobot = false;
        listener.onConnect(false);
    }
}

编辑:

令人困惑的是,这在 Unix 操作系统上运行良好:

#include <boost/asio.hpp>                                                                                                                    
#include <boost/asio/deadline_timer.hpp>                                                                                                     
#include <iostream>                                                                                                                          
#include <thread>                                                                                                                            

using namespace boost::asio;                                                                                                                 
using namespace boost::asio::ip;                                                                                                             

int main(){                                                                                                                                  
        io_service io;                                                                                                                       
        deadline_timer timer1(io);                                                                                                           
        tcp::socket sock(io);                                                                                                                

        timer1.expires_from_now(boost::posix_time::seconds(3));                                                                              
        sock.async_connect(tcp::endpoint(boost::asio::ip::address::from_string("10.50.30.1"), 8090), [](const boost::system::error_code &ec){
                std::cout << "SocketError: " << ec.message() << std::endl;                                                                   
        });                                                                                                                                  
        timer1.async_wait([&](const boost::system::error_code &ec){                                                                          
                std::cout << "First timer" << std::endl;                                                                                     
                sock.close();                                                                                                                
        });                                                                                                                                  
        std::thread worker([&](){                                                                                                            
                while(true){                                                                                                                 
                        io.run();                                                                                                            
                }                                                                                                                            
        });                                                                                                                                  
        worker.detach();                                                                                                                     
        while(true){} // Simulate the unavailable main (GUI) thread                                                                          
}  

输出:

First timer
SocketError: Operation canceled

好的,我在 Ubuntu 和 Windows 上测试了许多不同的场景后找到了答案。事实证明,如果调用 io_service.run 的线程是在异步任务(async_waitasync_connect)创建之前创建的,则使用线程连续调用 io_service::run 在 Unix 操作系统上不起作用开始了。如果工作线程是在调用 async_connectasync_wait 之前创建的,我发布的第二个示例会中断(在 unix 上不是 windows)。我刚刚修改了我的 NetManger class 以删除始终 运行 线程。

#include <boost/asio.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <thread>

using namespace boost::asio;
using namespace boost::asio::ip;

class NetManager{
private:
    io_service io;
    tcp::socket socket1, socket2, socket3;
    deadline_timer connectDeadline;
    int connectCounter = 0;

    void handleConnect(const boost::system::error_code &ec){
        if(ec){
            std::cout << "Connect error: " << ec.message() << std::endl;
            connectDeadline.cancel();
            disconnect();
        }else
            connectCounter++;
        if(connectCounter == 3)
            std::cout << "Connected" << std::endl;

    }
    void handleConnectTimeout(const boost::system::error_code &ec){
        std::cout << "Timeout fired." << std::endl;
        disconnect();
    }
public:
    void connect(){
        connectCounter = 0;
        connectDeadline.expires_from_now(boost::posix_time::seconds(5));
        socket1.async_connect(tcp::endpoint(boost::asio::ip::address::from_string("10.50.30.1"), 8090), boost::bind(&NetManager::handleConnect, this, _1));
        socket2.async_connect(tcp::endpoint(boost::asio::ip::address::from_string("10.50.30.1"), 8091), boost::bind(&NetManager::handleConnect, this, _1));
        socket3.async_connect(tcp::endpoint(boost::asio::ip::address::from_string("10.50.30.1"), 8092), boost::bind(&NetManager::handleConnect, this, _1));
        connectDeadline.async_wait(boost::bind(&NetManager::handleConnectTimeout, this, _1));
        std::thread([&]{io.run();}).detach(); // Run the async operations on a separate thread
    }
    void disconnect(){
        socket1.close();
        socket2.close();
        socket3.close();
    }
    NetManager(): connectDeadline(io),
                  socket1(io),
                  socket2(io),
                  socket3(io){

    }
    ~NetManager(){
        io.stop();
    }
};

int main(){
    NetManager manager;
    manager.connect();
    std::cout << "Trying to connect..." << std::endl;
    while(true); // Simulate the busy main (GUI) thread.
}

boost::io_service::run 如果没有工作则停止。您不应该像这样在循环中调用它,因为正如文档所述:

A normal exit from the run() function implies that the io_service object is stopped (the stopped() function returns true). Subsequent calls to run(), run_one(), poll() or poll_one() will return immediately unless there is a prior call to reset().

在第一个示例中调用 connect() 时没有显示,我假设不久之后,所以它是否有效取决于线程启动的速度和 run()调用已执行。

您可以使用 boost::io_service::work 来规避此行为,这将防止 io_service 运行 停止工作。