如何在断开连接后干净地重新连接boost :: socket?

时间:2010-06-17 14:55:26

标签: c++ sockets boost boost-asio

我的客户端应用程序使用boost::asio::ip::tcp::socket连接到远程服务器。 如果应用程序失去与此服务器的连接(例如由于服务器崩溃或被关闭),我希望它定期尝试重新连接,直到成功为止。

我需要在客户端做些什么来干净地处理断开连接,整理然后重复尝试重新连接?

目前,我的代码中有趣的部分看起来像这样。

connect喜欢这样:

bool MyClient::myconnect()
{
    bool isConnected = false;

    // Attempt connection
    socket.connect(server_endpoint, errorcode);

    if (errorcode)
    {
        cerr << "Connection failed: " << errorcode.message() << endl;
        mydisconnect();
    }
    else
    {
        isConnected = true;

        // Connected so setup async read for an incoming message.
        startReadMessage();

        // And start the io_service_thread
        io_service_thread = new boost::thread(
            boost::bind(&MyClient::runIOService, this, boost::ref(io_service)));
    }
    return (isConnected)
}

runIOServer()方法只是:

void MyClient::runIOService(boost::asio::io_service& io_service)
{
    size_t executedCount = io_service.run();
    cout << "io_service: " << executedCount << " handlers executed." << endl;
    io_service.reset();
}

如果任何异步读取处理程序返回错误,那么他们只需调用此disconnect方法:

void MyClient::mydisconnect(void)
{
    boost::system::error_code errorcode;

    if (socket.is_open())
    {
        // Boost documentation recommends calling shutdown first
        // for "graceful" closing of socket.
        socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, errorcode);
        if (errorcode)
        {
            cerr << "socket.shutdown error: " << errorcode.message() << endl;
        }

        socket.close(errorcode);
        if (errorcode)
        {
            cerr << "socket.close error: " << errorcode.message() << endl;
        }    

        // Notify the observer we have disconnected
        myObserver->disconnected();            
    }

..试图优雅地断开然后通知观察者,观察者将以五秒的间隔开始调用connect(),直到它重新连接。

还有什么我需要做的吗?

目前,似乎可以正常工作。如果我终止它所连接的服务器,我的读取处理程序会出现预期的"End of file"错误,并且mydisconnect()被调用而没有任何问题。

但是当它尝试重新连接并失败时,我会看到它报告"socket.shutdown error: Invalid argument"。这只是因为我试图关闭一个没有读/写挂起的套接字吗?还是更多的东西?

5 个答案:

答案 0 :(得分:26)

每次重新连接时都需要创建一个新的boost::asio::ip::tcp::socket。执行此操作的最简单方法可能是使用boost::shared_ptr在堆上分配套接字(如果套接字完全封装在类中,则可能还可以使用scoped_ptr。) E.g:

bool MyClient::myconnect()
{
    bool isConnected = false;

    // Attempt connection
    // socket is of type boost::shared_ptr<boost::asio::ip::tcp::socket>
    socket.reset(new boost::asio::ip::tcp::socket(...));
    socket->connect(server_endpoint, errorcode);
    // ...
}

然后,当调用mydisconnect时,您可以解除分配套接字:

void MyClient::mydisconnect(void)
{
    // ...
    // deallocate socket.  will close any open descriptors
    socket.reset();
}

您看到的错误可能是操作系统在您调用close后清理文件描述符的结果。当您在同一个套接字上调用close然后尝试connect时,您可能正在尝试连接无效的文件描述符。此时,您应该根据您的逻辑看到以“连接失败:...”开头的错误消息,但是然后调用mydisconnect,然后可能会尝试在无效的文件描述符上调用shutdown 。恶性循环!

答案 1 :(得分:7)

为了清楚起见,这是我使用的最终方法(但这是基于bjlaub的答案,所以请给他任何支持):

我将socket成员声明为scoped_ptr

boost::scoped_ptr<boost::asio::ip::tcp::socket> socket;

然后我将connect方法修改为:

bool MyClient::myconnect()
{
    bool isConnected = false;

    // Create new socket (old one is destroyed automatically)
    socket.reset(new boost::asio::ip::tcp::socket(io_service));

    // Attempt connection
    socket->connect(server_endpoint, errorcode);

    if (errorcode)
    {
        cerr << "Connection failed: " << errorcode.message() << endl;
        socket->close();
    }
    else
    {
        isConnected = true;

        // Connected so setup async read for an incoming message.
        startReadMessage();

        // And start the io_service_thread
        io_service_thread = new boost::thread(
            boost::bind(&MyClient::runIOService, this, boost::ref(io_service)));
    }
    return (isConnected)
}

注意:此问题最初是在2010年提出并回答的,但如果您现在使用的是C ++ 11或更高版本,那么std::unique_ptr通常会比boost::scoped_ptr更好地选择

答案 2 :(得分:2)

我过去曾使用Boost.Asio做过类似的事情。我使用异步方法,因此重新连接通常会让我现有的ip :: tcp :: socket对象超出范围,然后为调用async_connect创建一个新对象。如果async_connect失败,我会使用一个计时器睡一会儿,然后重试。

答案 3 :(得分:0)

我已经尝试了close()方法和关闭方法,它们对我来说只是很棘手。 Close()可以抛出你需要捕获的错误,并且是你想做的事情的粗鲁方式:)和shutdown()似乎是最好的,但在多线程软件上,我发现它可能很挑剔。因此,正如萨姆所说,最好的办法就是让它超出范围。如果套接字是该类的成员,您可以1)重新设计,以便该类使用&#39;连接&#39;对象包装套接字并让它超出范围或2)将其包装在智能指针中并重置智能指针。如果你使用boost,包括shared_ptr很便宜,就像魅力一样。从未使用shared_ptr执行套接字清理问题。只是我的经历。

答案 4 :(得分:0)

自C ++ 11起,您可以编写:

decltype(socket)(std::move(socket));
// reconnect socket.

上面创建了一个套接字类型移动的本地实例,从套接字构造它。

在下一行之前,未命名的本地实例被破坏,从而使套接字处于“由io_service全新构建”状态。

请参阅: https://www.boost.org/doc/libs/1_63_0/doc/html/boost_asio/reference.html#boost_asio.reference.basic_stream_socket.basic_stream_socket.overload5