【问题标题】:How do I cleanly reconnect a boost::socket following a disconnect?如何在断开连接后干净地重新连接 boost::socket?
【发布时间】:2011-03-05 00:25:03
【问题描述】:

我的客户端应用程序使用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"。这仅仅是因为我试图关闭一个没有读/写挂起的套接字吗?还是更多?

【问题讨论】:

  • 如果您已经检测到连接的另一端正在关闭,您调用关机的理由是什么?
  • @samm:不推荐吗?我认为套接字上可能有待处理的操作需要用shutdown() 取消。我主要是为了简单起见:如果我想正常断开连接,或者任何异步操作返回错误,则调用相同的 mydisconnect() 方法。
  • 我不确定是否推荐。待处理的数据或操作会去哪里?连接的另一端不存在。
  • @SamMiller 没必要。在一篇被广泛误解的 MSDN 文章中推荐了它,其目的是向您展示如何实现两个对等方的同步关闭,但在这种情况下,您应该只关闭输出。 close() 就是所需要的。仍在传输中的数据仍在传输中。

标签: c++ sockets boost boost-asio


【解决方案1】:

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

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 被调用时,你可以释放socket:

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

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

【讨论】:

  • 试图阻止可移植性绝不是一个好主意,尤其是当您甚至不知道目标操作系统或用于开发的操作系统时!
  • 感谢这种方法对我很有效。我修改了连接以始终创建一个新套接字,然后按照您的描述在shared_ptr 上调用.reset。如果连接尝试失败,我只需调用socket-&gt;close() 并保留它。我离开了 disconnect 方法,就像使用 polite shutdown 调用一样。
  • 我没有看到 boost::asio::ip::tcp::socket 的“重置”方法?请看boost.org/doc/libs/1_55_0/doc/html/boost_asio/reference/ip__tcp/…
  • @Anonymous 是 std::shared_ptr 的一个方法,用参数替换托管对象:en.cppreference.com/w/cpp/memory/shared_ptr/reset
【解决方案2】:

为了清楚起见,这里是我使用的最终方法(但这是基于 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 更好的选择

【讨论】:

  • 我认为建议使用 std::unique_ptr 而不是 boost::scoped_ptr 会很好,因为它是一个更好的选择,而且 C++11 可以广泛使用。
  • @Ivan_Bereziuk:我已经编辑了我的答案。它最初是在 2010 年发布的,当时 C++11 还没有被广泛使用。
【解决方案3】:

从 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

【讨论】:

  • 请考虑在您的答案中添加更多信息
【解决方案4】:

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

【讨论】:

  • 谢谢,那你是不是在检测到错误的时候直接调用socket.close(),然后新建一个?
  • 当 ip::tcp::socket 对象超出范围时,描述符关闭。
【解决方案5】:

我已经尝试过 close() 方法和 shutdown 方法,但它们对我来说很棘手。 Close() 可能会引发您需要捕获的错误,并且是执行您想要的操作的粗鲁方式:) 并且 shutdown() 似乎是最好的,但在多线程软件上,我发现它可能很麻烦。因此,正如 Sam 所说,最好的方法是让它超出范围。如果套接字是该类的成员,您可以 1) 重新设计,以便该类使用“连接”对象来包装套接字并让它超出范围或 2) 将其包装在智能指针中并重置智能指针。如果您使用 boost,则包括 shared_ptr 很便宜,而且效果很好。从来没有使用 shared_ptr 进行套接字清理的问题。只是我的经验。

【讨论】:

  • 必须打电话给close(),'tricky'。否则你有资源泄漏。没有什么“粗鲁”的。而shutdown() 不是close() 的替代品。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-20
  • 1970-01-01
  • 1970-01-01
  • 2019-01-13
  • 1970-01-01
  • 2012-08-07
相关资源
最近更新 更多