繁体   English   中英

如何从父级插槽中删除子级对象? 可能Boost :: asio特定

[英]How should I delete a child object from within a parent's slot? Possibly boost::asio specific

我编写了一个网络服务器类,该类维护网络客户端的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()。 所有ASIO读/写操作都是异步执行的。 多个客户端连接/断开时一切正常,除非我按照上述代码从Set中删除客户端对象。 如果删除客户端对象,则io_service似乎在内部死锁,除非启动另一个线程,否则不会执行进一步的异步操作。 我在io_service.run()调用周围遇到了try / catching,但无法检测到任何错误。

问题:

  1. 是否存在从父级插槽中删除也是信号发射器的子对象的最佳实践?

  2. 关于删除网络客户端对象时io_service为什么挂起的任何想法?

您可以将weak_ptr存储在集合中,因此shared_ptr仅由asio保留,并在断开连接时自动释放。 从〜Client()中的设置中删除相应的weak_ptr

我终于弄清楚了,简短的答案是,这主要是我的编码/线程错误。 我通过创建一个更简单的独立代码示例来确定这一点,并发现它没有表现出相同的行为。 我本来应该这样做的,但很抱歉浪费任何人的时间。 要完整回答我的原始问题:

1-是否有从父级插槽中删除也是信号发射器的子对象的最佳实践?

没有人真正回答过这个问题。 我认为上面的建议和代码示例使用了shared_from_this,效果很好。

而且,正如villintehaspam在上面的评论中指出的那样,最好使用boost :: signal2,它似乎对管理信号寿命具有更好的支持。

2-关于为什么删除网络客户端对象时io_service挂起的任何想法

我的错误是我的NetworkClient中的析构函数正在触发一个操作,该操作导致当前线程(并且仅可用于处理异步IO操作的线程)无限期阻塞。 我没有意识到那件事正在发生。 由于我如何在独立于io_service异步操作的自有线程中处理接受器,因此新客户端仍然能够连接。 当然,即使我为新客户端安排了异步操作,但它们从未触发,因为我对io_service可用的一个线程仍然被阻止。

感谢所有花时间研究此问题的人。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM