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, когда длина тела неизвестна заранее.