Как мне удалить дочерний объект из родительского слота? Возможно повысить :: ASIO - PullRequest
4 голосов
/ 17 декабря 2010

Я написал класс сетевого сервера, который поддерживает набор сетевых клиентов std ::. Сетевые клиенты посылают сигнал сетевому серверу при разъединении (через 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 (). Все операции чтения / записи ASIO выполняются асинхронно. Все отлично работает с несколькими клиентами, подключающимися / отключающимися, ЕСЛИ МЕНЯ не удаляю свой клиентский объект из набора в соответствии с кодом выше. Если я удаляю свой клиентский объект, io_service, по-видимому, блокируется внутри, и никакие другие асинхронные операции не выполняются, пока я не запущу другой поток. Я попытался / ловит вызов io_service.run () и не смог обнаружить никаких ошибок.

Вопросы:

  1. Существуют ли передовые практики для удаления дочерних объектов, которые также являются источниками сигналов, из родительских слотов?

  2. Есть идеи, почему io_service зависает, когда я удаляю свой сетевой клиентский объект?

Ответы [ 2 ]

1 голос
/ 07 января 2011

Я наконец-то понял это, и короткий ответ заключается в том, что это была в первую очередь ошибка кодирования / многопоточности с моей стороны.Я определил это, создав более простой автономный пример кода, и обнаружил, что он не демонстрирует такого же поведения.Я должен был сделать это в первую очередь, и мне жаль, что я потратил впустую время.Чтобы ответить на мои первоначальные вопросы полностью, хотя:

1 - Есть ли лучшие практики для удаления дочерних объектов, которые также являются источниками сигнала, из родительских слотов?

Нетодин действительно ответил на это.Я думаю, что мое предложение и пример кода выше, где я использую shared_from_this, работает нормально.

Кроме того, как указано villintehaspam в комментариях выше, может быть лучше использовать boost :: signal2, который, по-видимому, лучше поддерживает управление сигналомпродолжительность жизни.

2 - Любые идеи относительно того, почему io_service зависает, когда я удаляю свой сетевой клиентский объект

Моя ошибка состояла в том, что деструктор в моем NetworkClient запускал операцию, котораязаставил текущий поток (и единственный поток, доступный для обработки асинхронных операций ввода-вывода) блокировать неопределенно долго.Я не осознавал, что это происходит.Новые клиенты все еще могли подключаться из-за того, как я обрабатываю акцептор в его собственном потоке, независимо от асинхронных операций io_service.Конечно, даже несмотря на то, что я запланировал асинхронные операции для нового клиента, они никогда не запускались, потому что один поток, который я сделал доступным для io_service, все еще был заблокирован.

Спасибо всем, кто нашел время, чтобы взглянуть на это.

1 голос
/ 28 декабря 2010

вы можете хранить в наборе weak_ptr, поэтому shared_ptr будет храниться только в asio и автоматически отключаться при отключении.удалить соответствующий уязвимый_сегмент из набора в ~ Client ()

...