4

ネットワーククライアントのstd::setを維持するネットワークサーバークラスを作成しました。ネットワーククライアントは、切断時に(boost :: bindを介して)ネットワークサーバーに信号を送信します。ネットワーククライアントが切断された場合、クライアントインスタンスをセットから削除し、最終的に削除する必要があります。これは一般的なパターンだと思いますが、ASIOに固有の問題がある場合とない場合があります。

私は関連するコードだけに切り詰めようとしました:

/** NetworkServer.hpp **/
class NetworkServices : private boost::noncopyable
{
public:
    NetworkServices(void);
    ~NetworkServices(void);

private:
    void run();
    void onNetworkClientEvent(NetworkClientEvent&); 

private:
    std::set<boost::shared_ptr<const NetworkClient>> clients;
};


/** NetworkClient.cpp **/
void NetworkServices::run()
{
    running = true;

    boost::asio::io_service::work work(io_service); //keeps service running even if no operations

    // This creates just one thread for the boost::asio async network services
    boost::thread iot(boost::bind(&NetworkServices::run_io_service, this));

    while (running)
    {
        boost::system::error_code err;
        try
        {
            tcp::socket* socket = new tcp::socket(io_service);
            acceptor->accept(*socket, err);

            if (!err)
            {
                NetworkClient* networkClient = new NetworkClient(io_service, boost::shared_ptr<tcp::socket>(socket));
                networkClient->networkClientEventSignal.connect(boost::bind(&NetworkServices::onNetworkClientEvent, this, _1));

                clients.insert(boost::shared_ptr<NetworkClient>(networkClient));

                networkClient->init(); //kicks off 1st asynch_read call
            }
        }
        // etc...

    }
}

void NetworkServices::onNetworkClientEvent(NetworkClientEvent& evt)
{
    switch(evt.getType())
    {
        case NetworkClientEvent::CLIENT_ERROR :
        {
            boost::shared_ptr<const NetworkClient> clientPtr = evt.getClient().getSharedPtr();

            // ------ THIS IS THE MAGIC LINE -----
            // If I keep this, the io_service hangs. If I comment it out,
            // everything works fine (but I never delete the disconnected NetworkClient). 

            // If actually deleted the client here I might expect problems because it is the caller
            // of this method via boost::signal and bind.  However, The clientPtr is a shared ptr, and a
            // reference is being kept in the client itself while signaling, so
            // I would the object is not going to be deleted from the heap here. That seems to be the case.
            // Never-the-less, this line makes all the difference, most likely because it controls whether or not the NetworkClient ever gets deleted.
            clients.erase(clientPtr);

            //I should probably put this socket clean-up in NetworkClient destructor. Regardless by doing this,
            // I would expect the ASIO socket stuff to be adequately cleaned-up after this.

            tcp::socket& socket = clientPtr->getSocket();
            try {
                socket.shutdown(boost::asio::socket_base::shutdown_both);
                socket.close();
            }
            catch(...) {
                CommServerContext::error("Error while shutting down and closing socket.");
            }
            break;

        }
        default :
        {
            break;
        }
    }
}



/** NetworkClient.hpp **/
class NetworkClient : public boost::enable_shared_from_this<NetworkClient>, Client
{
    NetworkClient(boost::asio::io_service& io_service,
                  boost::shared_ptr<tcp::socket> socket);
    virtual ~NetworkClient(void);

    inline boost::shared_ptr<const NetworkClient> getSharedPtr() const
    {
        return shared_from_this();
    };

    boost::signal <void (NetworkClientEvent&)>    networkClientEventSignal;

    void onAsyncReadHeader(const boost::system::error_code& error,
                           size_t bytes_transferred);

};

/** NetworkClient.cpp - onAsyncReadHeader method called from io_service.run()
    thread as result of an async_read operation. Error condition usually 
    result of an unexpected client disconnect.**/
void NetworkClient::onAsyncReadHeader(  const boost::system::error_code& error,
                        size_t bytes_transferred)
{
    if (error)
    {

        //Make sure this instance doesn't get deleted from parent/slot deferencing
        //Alternatively, somehow schedule for future delete?
        boost::shared_ptr<const NetworkClient> clientPtr = getSharedPtr();

        //Signal to service that this client is disconnecting
        NetworkClientEvent evt(*this, NetworkClientEvent::CLIENT_ERROR);
        networkClientEventSignal(evt);

        networkClientEventSignal.disconnect_all_slots();

        return;
    }

関数の戻り値が...未定義になるため、スロットハンドラー内からクライアントを削除するのは安全ではないと思います。(興味深いことに、それは私には爆発しないようです。)そこで、私は、boost:shared_ptrとshared_from_thisを使用して、すべてのスロットが通知されるまでクライアントが削除されないようにしました。しかし、それは実際には問題ではないようです。

この質問はASIOに固有のものではないと思いますが、ASIOを使用すると問題が独特の形で現れます。io_service.run()を実行しているスレッドが1つあります。すべてのASIO読み取り/書き込み操作は非同期で実行されます。上記のコードに従ってセットからクライアントオブジェクトを削除しない限り、複数のクライアントが接続/切断してもすべて正常に機能します。クライアントオブジェクトを削除すると、io_serviceは内部でデッドロックしているように見え、別のスレッドを開始しない限り、それ以上の非同期操作は実行されません。io_service.run()呼び出しを試行/キャッチしましたが、エラーを検出できませんでした。

質問:

  1. 親スロット内から、シグナルエミッターでもある子オブジェクトを削除するためのベストプラクティスはありますか?

  2. ネットワーククライアントオブジェクトを削除すると、io_serviceがハングする理由について何か考えはありますか?

4

2 に答える 2

1

weak_ptrをセットに保存できるため、shared_ptrはasioによってのみ保持され、切断時に自動的に解放されます。〜Client()のセットから対応するweak_ptrを削除します

于 2010-12-28T17:51:05.760 に答える
1

私はついにそれを理解しました、そして簡単な答えはそれが私の側のコーディング/スレッドの間違いであったということです。より単純なスタンドアロンコード例を作成することでこれを判断しましたが、同じ動作を示さないことがわかりました。そもそもこれをやるべきだったのに、時間を無駄にしてしまったことをお詫びします。私の元の質問に完全に答えるには:

1-親スロット内から、シグナルエミッターでもある子オブジェクトを削除するためのベストプラクティスはありますか?

誰もこれに本当に答えていません。shared_from_thisを使用する上記の提案とコード例は正常に機能すると思います。

また、上記のコメントでvillintehaspamが指摘しているように、信号の寿命を管理するためのサポートが優れていると思われるboost::signal2を使用する方がよい場合があります。

2-ネットワーククライアントオブジェクトを削除したときにio_serviceがハングする理由に関するアイデア

私の間違いは、NetworkClientのデストラクタが、現在のスレッド(およびasych IO操作を処理するために使用可能なスレッドのみ)を無期限にブロックする操作をトリガーしていたことでした。私はそれが起こっていることに気づいていませんでした。io_service非同期操作とは関係なく、独自のスレッドでアクセプターを処理する方法により、新しいクライアントは引き続き接続できました。もちろん、新しいクライアントの非同期操作をスケジュールしたとしても、io_serviceで使用できるようにした1つのスレッドがまだブロックされているため、それらは起動しませんでした。

これを見てくれた皆さんに感謝します。

于 2011-01-07T02:57:41.710 に答える