我编写了一个网络服务器类,它维护一个 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() 调用进行了尝试/捕获,但无法检测到任何错误。
问题:
是否有从父插槽中删除子对象(也是信号发射器)的最佳实践?
关于我删除网络客户端对象时为什么 io_service 挂起的任何想法?