![](/img/trans.png)
[英]How should I find which client I am receiving from in Boost Asio in UDP?
[英]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,但无法检测到任何错误。
问题:
是否存在从父级插槽中删除也是信号发射器的子对象的最佳实践?
关于删除网络客户端对象时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.