4

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

问题:

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

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

4

2 回答 2

1

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

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

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

此外,正如 vilintehaspam 在上面的评论中所指出的,使用 boost::signal2 可能会更好,它似乎对管理信号生命周期有更好的支持。

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

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

感谢所有花时间看这个的人。

于 2011-01-07T02:57:41.710 回答
1

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

于 2010-12-28T17:51:05.760 回答