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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3062803/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 11:57:02  来源:igfitidea点击:

How do I cleanly reconnect a boost::socket following a disconnect?

c++socketsboostboost-asio

提问by GrahamS

My client application uses a boost::asio::ip::tcp::socketto connect to a remote server. If the app loses connection to this server (e.g. due to the server crashing or being shutdown) I would like it to attempt a re-connect at regular intervals until it succeeds.

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

What do I need to do on the client-side to cleanly handle a disconnect, tidy up and then repeatedly attempt reconnects?

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

Currently the interesting bits of my code look something like this.

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

I connectlike this:

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)
}

Where the runIOServer()method is just:

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();
}

And if any of the async read handlers return an error then they just call this disconnectmethod:

如果任何异步读取处理程序返回错误,则它们只需调用此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();            
    }

..which attempts to gracefully disconnect and then notifies an observer, which will start calling connect()at five second intervals until it gets reconnected.

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

Is there anything else I need to do?

还有什么我需要做的吗?

Currently this does seemto work. If I kill the server that it is connected to I get the expected "End of file"error at my read handlers and mydisconnect()is called without any issues.

目前这似乎确实有效。如果我杀死它所连接的服务器,我会"End of file"在我的读取处理程序中得到预期的错误,并且mydisconnect()在没有任何问题的情况下被调用。

But when it then attempts to re-connect and fails I see it report "socket.shutdown error: Invalid argument". Is this just because I am attempting to shutdown a socket that has no read/writes pending on it? Or is it something more?

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

采纳答案by bjlaub

You need to create a new boost::asio::ip::tcp::socketeach time you reconnect. The easiest way to do this is probably to just allocate the socket on the heap using a boost::shared_ptr(you could probably also get away with scoped_ptrif your socket is entirely encapsulated within a class). E.g.:

boost::asio::ip::tcp::socket每次重新连接时都需要创建一个新的。最简单的方法可能是使用 a 分配堆上的套接字boost::shared_ptrscoped_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);
    // ...
}

Then, when mydisconnectis called, you could deallocate the socket:

然后,当mydisconnect被调用时,您可以释放套接字:

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

The error you're seeing is probably a result of the OS cleaning up the file descriptor after you've called close. When you call closeand then try to connecton the same socket, you're probably trying to connect an invalid file descriptor. At this point you should see an error message starting with "Connection failed: ..." based on your logic, but you then call mydisconnectwhich is probably then attempting to call shutdownon an invalid file descriptor. Vicious cycle!

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

回答by GrahamS

For the sake of clarity here is the final approach I used (but this is based on bjlaub's answer, so please give any upvotes to him):

为了清楚起见,这里是我使用的最终方法(但这是基于 bjlaub 的回答,所以请给他点赞):

I declared the socketmember as a scoped_ptr:

我宣布该socket成员为scoped_ptr

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

Then I modified my connectmethod to be:

然后我将我的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)
}

Note: this question was originally asked and answered back in 2010, but if you are now using C++11 or later then std::unique_ptrwould normally be a better choice than boost::scoped_ptr

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

回答by Sam Miller

I've done something similar using Boost.Asio in the past. I use the asynchronous methods, so a reconnect is typically letting my existing ip::tcp::socket object go out of scope, then creating a new one for calls to async_connect. If async_connect fails, I use a timer to sleep a bit then retry.

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

回答by user771723

Since C++11 you can write:

从 C++11 开始,您可以编写:

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

The above creates a local instance of socket's type move constructing it from socket.

上面创建了一个 socket 类型 move 的本地实例,从 socket 构造它。

Before the next line, the unnamed local instance is destructed, leaving socket in a "freshly constructed from io_service" state.

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

See: 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

请参阅: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

回答by William Symionow

I have tried both the close() method and the shutdown method and they are just to tricky for me. Close() can throw an error that you need to catch and is the rude way to do what you want :) and shutdown() seems to be best but on multithreaded software, I find it can be fussy. So the best way is, as Sam said, to let it go out of scope. If the socket is a member of the class you can 1) redesign so that the class uses a 'connection' object to wrap the socket and let it go out of scope or 2) wrap it in a smart pointer and reset the smart pointer. If you using boost, including the shared_ptr is cheap and works like a charm. Never had a socket clean up issue doing it with a shared_ptr. Just my experience.

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