web-dev-qa-db-ja.com

切断後にboost :: socketをきれいに再接続するにはどうすればよいですか?

私のクライアントアプリケーションは、_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();            
    }
_

..正常に切断を試みてからオブザーバーに通知します。オブザーバーは、再接続されるまで5秒間隔でconnect()の呼び出しを開始します。

他に何かする必要がありますか?

現在、これは動作するようです。接続されているサーバーを強制終了すると、読み取りハンドラーで予期される_"End of file"_エラーが発生し、mydisconnect()が問題なく呼び出されます。

しかし、再接続を試みて失敗すると、_"socket.shutdown error: Invalid argument"_が報告されます。これは、読み取り/書き込みが保留されていないソケットをシャットダウンしようとしているからですか?それとももっと何かですか?

28
GrahamS

再接続するたびに、新しい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を呼び出した後にOSがファイル記述子をクリーンアップした結果です。 closeを呼び出してから同じソケットでconnectを試みた場合、おそらく無効なファイル記述子を接続しようとしています。この時点で、ロジックに基づいて「接続に失敗しました:...」で始まるエラーメッセージが表示されるはずですが、次にmydisconnectを呼び出します。これは、おそらく無効なものに対してshutdownを呼び出そうとしています。ファイル記述子。悪循環!

26
bjlaub

わかりやすくするために、ここに私が使用したファイナルアプローチを示します(ただし、これは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の方が適しています。

7
GrahamS

私は過去にBoost.Asioを使用して同様のことをしました。私は非同期メソッドを使用しているため、再接続では通常、既存のip :: tcp :: socketオブジェクトがスコープ外になり、async_connectの呼び出し用に新しいオブジェクトが作成されます。 async_connectが失敗した場合は、タイマーを使用して少しスリープしてから再試行します。

2
Sam Miller

C++ 11以降、次のように記述できます。

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

上記は、ソケットからそれを構築するソケットのタイプmoveのローカルインスタンスを作成します。

次の行の前に、名前のないローカルインスタンスが破棄され、ソケットは「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

1
user771723

私はclose()メソッドとshutdownメソッドの両方を試しましたが、それらは私にとってはトリッキーです。 Close()は、キャッチする必要のあるエラーをスローする可能性があり、必要なことを実行するための失礼な方法です:)そしてshutdown()が最善のようですが、マルチスレッドソフトウェアでは面倒なことがあります。したがって、サムが言ったように、最善の方法はそれを範囲外にすることです。ソケットがクラスのメンバーである場合は、1)クラスが「connection」オブジェクトを使用してソケットをラップしてスコープ外に出すように再設計するか、2)スマートポインターでラップしてスマートポインターをリセットできます。ブーストを使用する場合、shared_ptrを含めると安価で、魅力のように機能します。 shared_ptrを使用してソケットのクリーンアップの問題が発生したことはありません。ちょうど私の経験。

0