我应该如何从父对象的槽中删除子对象?可能是 boost::asio 特定的

发布于 2024-10-08 00:56:29 字数 5257 浏览 6 评论 0原文

我编写了一个网络服务器类,它维护一组网络客户端的 std::set。网络客户端在断开连接时向网络服务器发出信号(通过 boost::bind)。当网络客户端断开连接时,客户端实例需要从 Set 中移除并最终删除。我认为这是一种常见的模式,但我遇到的问题可能是或可能不是 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 挂起有什么想法吗?

I have written a network server class that maintains a std::set of network clients. The network clients emit a signal to the network server on disconnect (via boost::bind). When a network client disconnects, the client instance needs to be removed from the Set and eventually deleted. I would think this is a common pattern, but I am having problems that might, or might not, be specific to ASIO.

I've tried to trim down to just the relevant code:

/** 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;
    }

I believe it's not safe to delete the client from within the slot handler because the function return would be ... undefined? (Interestingly, it doesn't seem to blow up on me though.) So I've used boost:shared_ptr along with shared_from_this to make sure the client doesn't get deleted until all slots have been signaled. It doesn't seem to really matter though.

I believe this question is not specific to ASIO, but the problem manifests in a peculiar way when using ASIO. I have one thread executing io_service.run(). All ASIO read/write operations are performed asynchronously. Everything works fine with multiple clients connecting/disconnecting UNLESS I delete my client object from the Set per the code above. If I delete my client object, the io_service seemingly deadlocks internally and no further asynchronous operations are performed unless I start another thread. I have try/catches around the io_service.run() call and have not been able to detect any errors.

Questions:

  1. Are there best practices for deleting child objects, that are also signal emitters, from within parent slots?

  2. Any ideas as to why the io_service is hanging when I delete my network client object?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

指尖上的星空 2024-10-15 00:56:29

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

you can store weak_ptr's in the set, so shared_ptr will be kept only by asio and released on disconnection automatically. remove corresponding weak_ptr from set in ~Client()

≈。彩虹 2024-10-15 00:56:29

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

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

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

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

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

我的错误是 NetworkClient 中的析构函数触发了导致当前线程(并且只有线程可处理)的操作异步 IO 操作)无限期地阻塞。我没有意识到发生了这种事。新客户端仍然能够连接,因为我如何在独立于 io_service 异步操作的线程中处理接受器。当然,即使我为新客户端安排了异步操作,它们也从未触发,因为我向 io_service 提供的一个线程仍然被阻塞。

感谢所有花时间查看此内容的人。

I've finally figured it out, and the short answer is that it was primarily a coding/threading mistake on my part. I determined this by creating a simpler standalone code example, and found that it didn't exhibit the same behavior. I should've done this in the first place and I'm sorry to have wasted anyone's time. To answer my original questions in full though:

1 - Are there best practices for deleting child objects, that are also signal emitters, from within parent slots?

No one has really answered this. I think my suggestion and code example above where I use shared_from_this works fine.

Also, as pointed out by villintehaspam in comments above, it might be better to use boost::signal2 which seemingly has better support for managing signal lifetime.

2 - Any ideas as to why the io_service is hanging when I delete my network client object

My mistake was that the destructor in my NetworkClient was triggering an operation that caused the current thread (and only thread available to handle asych IO operations) to block indefinitely. I didn't realize that was happening. New clients were still able to connect because of how I handle the acceptor in it's own thread independent of the io_service asynch operations. Of course even though I scheduled asynch operations for the new client, they never fired because the one thread I made available to the io_service was still blocked.

Thanks to everyone that took the time to look at this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文