Я написал класс сетевого сервера, который поддерживает набор сетевых клиентов std ::. Сетевые клиенты посылают сигнал сетевому серверу при разъединении (через 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 выполняются асинхронно. Все отлично работает с несколькими клиентами, подключающимися / отключающимися, ЕСЛИ МЕНЯ не удаляю свой клиентский объект из набора в соответствии с кодом выше. Если я удаляю свой клиентский объект, io_service, по-видимому, блокируется внутри, и никакие другие асинхронные операции не выполняются, пока я не запущу другой поток. Я попытался / ловит вызов io_service.run () и не смог обнаружить никаких ошибок.
Вопросы:
Существуют ли передовые практики для удаления дочерних объектов, которые также являются источниками сигналов, из родительских слотов?
Есть идеи, почему io_service зависает, когда я удаляю свой сетевой клиентский объект?