Boost:: asio и async SSL-поток: как определить конец закрытия данных/соединения?

Я пытаюсь сделать друзей asio и SSL. Все идет хорошо, но одна вещь вызывает неудобства: как обнаружить, если соединение с близким соединением, и отличить его от ситуации когда сверстник просто сделает короткий перерыв в отправке данных, стремясь продолжить через несколько секунд?

  • boost 1.48
  • OpenSSL 1.0.0e
  • Скомпилировано в 32-разрядный код с помощью VS10
  • Работает на W7 x64.

Моя путаница исходит из того факта, что поведение асио обычный сокет и SSL-поток. Если я использую tcp:: socket - я получаю ошибку EOF при соединении с близким соединением. Но для boost:: asio:: ssl:: stream - это не тот случай. Вместо этого async_read_some возвращает 0 в качестве переданных байтов, и если я попытаюсь продолжить чтение из потока SSL - возвращает short_error (http://www.boost.org/doc/libs/1_47_0/doc/html/boost_asio/overview/core/streams.html).

Итак, вопросы: это ожидаемое поведение, или я неправильно настроил что-нибудь?

фрагмент кода клиента:

class client
{
public:

    // bla-bla-bla-bla-bla ....
    //
   void handle_write(const boost::system::error_code& error)
   {
       if (!error)
       {
           socket_.async_read_some(boost::asio::buffer(reply_, max_length),
               boost::bind(&client::handle_read, this,
               boost::asio::placeholders::error,
               boost::asio::placeholders::bytes_transferred));
       }
       else
       {
           std::cout << "Write failed: " << error.message() << "\n";
       }
   }

   void handle_read(const boost::system::error_code& error,
                   size_t bytes_transferred)
   {

       std::cout << "Bytes transfered: " << bytes_transferred << "\n";
       if (!error)
       {
           std::cout << "Reply: ";
           std::cout.write(reply_, bytes_transferred);
           std::cout << "\n";

           std::cout << "Reading...\n";
           socket_.async_read_some(boost::asio::buffer(reply_, max_length),
               boost::bind(&client::handle_read, this,
               boost::asio::placeholders::error,
               boost::asio::placeholders::bytes_transferred));
       }
       else if (0 != bytes_transferred)
       {
           std::cout << "Read failed: " << error.message() << ":" 
                     << error.value() <<  "\n";
       }
   }

private:
   boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
   boost::asio::streambuf request_;
   char reply_[max_length];
};

Если мы удалим if (0!= bytes_transferred), мы получим "короткое чтение": (.

Если мы будем использовать код as-ai, вывод будет примерно таким:

Запрос:

GET/HTTP/1.0

Cookie: Nama-nama = Vala-vala

Байт передан: 1024

Ответить: HTTP/1.0 200 ok Content-type: text/html

..... бла-бла-бла....

Reading... Байты переданы: 1024

..... бла-бла-бла......... бла-бла-бла....

Reading... Байты переданы: 482

..... бла-бла-бла....

Reading...

Байт передан: 0

В то же время, если вместо async_read_some мы пишем код, зачем обычный сокет вернет EOF:

boost::asio::async_read(socket_, response_,
    boost::asio::transfer_at_least(1),
    boost::bind(&client::handle_read_content, this,
    boost::asio::placeholders::error));

то для SSL-сокета мы получим 0 в качестве переданных байтов, а затем short_read.

Я знаю, что нет способа обнаружить разъединение в случае, если peer, для пример, был просто отключен от сети. Но как обнаружить явное чистое одноранговое отсоединение от ситуации, когда peer просто не отправляет данные в течение некоторого времени, но может быть, это сделает это немного позже?

Или, может быть, я что-то не понимаю?

WBR, Андрей

Некоторая добавка: SSL/TLS имеет указание информировать другую сторону о закрытии соединения. Это close_notify alert. Также можно закрыть основной сокет TCP.

Итак, в основном, мой вопрос: почему, в тех же условиях (TCP-сокет был закрыт явно), я получаю EOF в случае tcp:: socket и ничего не получаю для boost:: asio:: ssl:: поток.

Является ли это ошибкой или функцией asio?

Еще один добавочный элемент: По некоторым причинам asio не предоставил мне EOF, ни при условии, что SSL получил close_notify, и не был закрыт основной сокет TCP.

Да, я могу обнаружить мертвые соединения по таймауту. Но как я могу обнаружить правильно закрытые SSL-соединения? Получив short_read?

Ответы

Ответ 1

Здесь ожидается ошибка SSL_R_SHORT_READ. Когда сервер инициирует чистое завершение работы с помощью SSL_Shutdown, он отправляет клиенту сообщение о закрытии уведомления о завершении работы. Реализация Asio сопоставляет это с ошибкой SSL_R_SHORT_READ с категорией error::get_ssl_category(). Он делает это, обнаружив, что сверстник инициировал выключение через SSL_get_shutdown.

Это можно увидеть, просмотрев заголовок asio/ssl/detail/impl/engine.ipp и, в частности, функцию engine::map_error_code(boost::system::error_code&).

Я считаю, что реализация ssl была переписана в boost 1.47, поэтому более ранние версии имеют потенциально другое поведение.

Ответ 2

Вам могут быть интересны следующие обсуждения:

По сути, тот факт, что вы иногда получаете EOF (даже большую часть времени), когда удаленная сторона отключает простой TCP-сокет, - это просто удача. Вы не можете полагаться на него в общем случае, так как он не может различать неактивный сокет и сокет, закрытые внезапно, не записывая его.

Вам нужно определить некоторые разделители на уровне протокола приложений, чтобы узнать, когда прекратить чтение. В HTTP это делается либо через пустую строку, которая заканчивает заголовок (для заголовка), заголовок Content-Length, определяющий длину тела, либо chunked transfer encoding, когда длина тела неизвестна заранее.