代码之家  ›  专栏  ›  技术社区  ›  GrahamS

断开连接后,如何干净地重新连接Boost::Socket?

  •  23
  • GrahamS  · 技术社区  · 16 年前

    我的客户端应用程序使用 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() 每隔5秒,直到重新连接。

    我还需要做什么吗?

    目前是这样 似乎 工作。如果我终止它所连接的服务器,我会得到预期的 "End of file" 我的读取处理程序出错,并且 mydisconnect() 调用时没有任何问题。

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

    5 回复  |  直到 8 年前
        1
  •  25
  •   bjlaub    16 年前

    你需要创建一个新的 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 调用时,可以取消分配套接字:

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

    您看到的错误可能是操作系统在调用 close . 当你打电话 关闭 然后尝试 connect 在同一个套接字上,您可能正在尝试连接一个无效的文件描述符。此时,您将看到一条错误消息,根据您的逻辑,以“connection failed:…”开头,但随后您将调用 MyDelink 可能当时正在打电话 shutdown 在无效的文件描述符上。恶性循环!

        2
  •  7
  •   GrahamS    8 年前

    为了清楚起见,这里是我使用的最后一种方法(但这是基于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

        3
  •  2
  •   Sam Miller    16 年前

    我曾经用boost.asio做过类似的事情。我使用异步方法,因此重新连接通常会让我现有的ip::tcp::socket对象超出作用域,然后为异步连接调用创建一个新的对象。如果异步连接失败,我将使用计时器睡眠一段时间,然后重试。

        4
  •  0
  •   William Symionow    13 年前

    我试过close()方法和shutdown方法,它们对我来说都很棘手。close()会抛出一个你需要捕捉的错误,这是一种粗鲁的方式来做你想做的事情:)而shutdown()似乎是最好的方法,但在多线程软件上,我发现它可能很麻烦。所以最好的办法是,正如萨姆所说,让它超出范围。如果套接字是类的成员,您可以1)重新设计以便类使用“connection”对象包装套接字并让其超出范围,或者2)将其包装在智能指针中并重置智能指针。如果你使用Boost,包括共享资源是很便宜的,而且很有魅力。从未有过使用共享资源清理套接字的问题。只是我的经验。

        5
  •  0
  •   user771723    8 年前

    因为C++ 11,你可以写:

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

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

    在下一行之前,将销毁未命名的本地实例,使套接字处于“从IO服务新建”状态。

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

    推荐文章