I'm trying to make asio and SSL friends. Everything going well, but one thing is causing inconvenience: how to detect if peer close connection, and distinguish it from situation when peer just take a short break in sending data, aiming to continue it few seconds later?
My confusion comes from the fact, that asio behaviour is different for ordinary socket and SSL-stream. If I use tcp::socket - I receive EOF error when peer close connection. But for boost::asio::ssl::stream - it is not the case. Instead, async_read_some returns 0 as bytes transfered, and if I try to continue to read from SSL stream - returns short_error ( http://www.boost.org/doc/libs/1_47_0/doc/html/boost_asio/overview/core/streams.html ).
So, the questions is: is it expected behaviour, or I misconfigure anything?
Client code snippet:
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 we remove if (0 != bytes_transferred), we'll get "short read" :(.
If we'll use code as-ai, output will be something like this:
Request is:
GET / HTTP/1.0
Cookie: Nama-nama=Vala-vala
Bytes transfered: 1024
Reply: HTTP/1.0 200 ok Content-type: text/html
..... bla-bla-bla ....
Reading... Bytes transfered: 1024
..... bla-bla-bla .... ..... bla-bla-bla ....
Reading... Bytes transfered: 482
..... bla-bla-bla ....
Reading...
Bytes transfered: 0
At the same time, if instead async_read_some we write code, what for ordinary socket will return EOF:
boost::asio::async_read(socket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&client::handle_read_content, this,
boost::asio::placeholders::error));
then for SSL-socket we'll get 0 as bytes transfered, and then short_read.
I know that there is not way to detect disconnect in case if peer, for example, was just unplugged from the network. But how to detect explicit clean peer disconnect from situation when peer just do not send data for a some time, but may be will do it little bit later?
Or, may be I do not understant something?
WBR, Andrey
Some addentum: SSL/TLS has notation to inform other party about closing connection. It close_notify alert. Also underlying TCP socket can be closed.
So, basically, my question is: why, in the same conditions (TCP socket was closed clearly) I receive EOF in case of tcp::socket, and do not receive anything for boost::asio::ssl::stream.
Is it bug or asio feature?
Yet another addentum: For some reasons asio didn't give me a EOF neither if SSL receive close_notify nor underlying TCP socket was closed.
Yes, I can detect dead connections by timeout. But how can I detect properly closed SSL-connections? By receiving short_read?
The SSL_R_SHORT_READ
error is expected here. When the server initiates a clean shutdown with SSL_Shutdown
this sends the close notify shutdown alert to the client. The Asio implementation maps this into an SSL_R_SHORT_READ
error with the category of error::get_ssl_category()
. It does this by detecting if the peer has initiated shutdown via SSL_get_shutdown
.
This can be seen by inspecting asio/ssl/detail/impl/engine.ipp
header and specifically the function engine::map_error_code(boost::system::error_code&)
.
I believe the ssl implementation was rewritten in boost 1.47, so earlier versions have potentially different behavior.