Как обнаружить, когда разъём tcp boost отключается

Предположим, что у меня есть сокет:

std::shared_ptr<tcp::socket> socket( new tcp::socket(acceptor.get_io_service()) );
acceptor.async_accept( *socket, std::bind( handleAccept, this, std::placeholders::_1, socket, std::ref(acceptor)) );

И я храню weak_ptr в указанном сокете в контейнере. Мне нужно это, потому что я хочу разрешить клиентам запрашивать список других клиентов, чтобы они могли отправлять сообщения друг другу.

clients_.insert(socket);  // pseudocode

Затем я запускаю несколько асинхронных операций

socket->async_receive( boost::asio::buffer(&(*header), sizeof(Header))
    , 0
    , std::bind(handleReceiveHeader, this, std::placeholders::_1, std::placeholders::_2, header, socket));

Как определить, когда соединение закрыто, поэтому я могу удалить мой сокет из контейнера?

clients_.erase(socket); // pseudocode

Ответ 1

Отключение сокета TCP обычно сигнализируется в asio с помощью eof или connection_reset. Например.

  void async_receive(boost::system::error_code const& error,
                     size_t bytes_transferred)
  {
    if ((boost::asio::error::eof == error) ||
        (boost::asio::error::connection_reset == error))
    {
      // handle the disconnect.
    }
    else
    {
       // read the data 
    }
  }

Я использую boost::signals2 для сообщения об отключении, хотя вы всегда можете передать указатель на функцию в свой класс сокетов и затем вызвать это.

Будьте осторожны с временем жизни вашего сокета и обратного вызова, см. boost-async-functions-and-shared-ptrs

Ответ 2

Есть много вариантов, некоторые из них:

  • Когда вы храните weak_ptr в контейнере, это не продлит срок службы сокета, поэтому, когда ваш обработчик получит boost::asio::error::eof (или что-то еще), он не будет копировать/перемещать shared_ptr, и сокет будет удален (если у вас нет других shared_ptr). Итак, вы можете сделать что-то вроде: if(socket.expired()) clients_.erase(socket);

  • Проверьте код ошибки в вашем обработчике - он укажет, когда соединение закрыто. Используя эту информацию - вы можете вызывать clients_.erase из самого обработчика.

Не могли бы вы привести пример # 2?

Это будет что-то вроде:

socket->async_receive
(
    boost::asio::buffer(&(*header), sizeof(Header)), 0,
    [=, &clients_](const system::error_code& error, size_t bytes_transferred)
    {
        if(error) // or some specific code
        {
            clients_.erase(socket); // pseudocode
        }
        else
        {
            // continue, launch new operation, etc
        }
    }
);