ネットワーククライアントの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()を実行しているスレッドが1つあります。すべてのASIO読み取り/書き込み操作は非同期で実行されます。上記のコードに従ってセットからクライアントオブジェクトを削除しない限り、複数のクライアントが接続/切断してもすべて正常に機能します。クライアントオブジェクトを削除すると、io_serviceは内部でデッドロックしているように見え、別のスレッドを開始しない限り、それ以上の非同期操作は実行されません。io_service.run()呼び出しを試行/キャッチしましたが、エラーを検出できませんでした。
質問:
親スロット内から、シグナルエミッターでもある子オブジェクトを削除するためのベストプラクティスはありますか?
ネットワーククライアントオブジェクトを削除すると、io_serviceがハングする理由について何か考えはありますか?