boost::asio 和异步 SSL 流:如何检测数据结束/连接关闭?
我正在努力使 asio 和 SSL 成为朋友。 一切都很顺利,但有一件事造成了不便:如何 检测peer是否关闭连接,并根据情况进行区分 当对方在发送数据时短暂休息,打算继续 几秒钟后?
- boost 1.48
- OpenSSL 1.0.0e
- 使用 VS10 编译为 32 位代码,
- 在 W7 x64 上工作。
我的困惑来自于这样一个事实,asio 的行为是不同的 普通套接字和 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,输出将如下所示:
请求是:
获取/HTTP/1.0
Cookie:Nama-nama=Vala-vala
传输的字节数:1024
回复:HTTP/1.0 200 好的 内容类型:text/html
..... bla-bla-bla ....
正在阅读... 传输的字节数:1024
..... 布拉-布拉-布拉 .... ..... bla-bla-bla ....
正在阅读... 传输的字节数:482
..... 布拉-布拉-布拉 ....
阅读中...
传输的字节数: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。
我知道如果对等,则无法检测断开连接 例如,刚刚从网络中拔出。 但是,如何检测明确的干净对等点断开连接的情况 对等方只是在一段时间内不发送数据,但可能会发送数据 稍后?
或者,可能是我不明白什么?
世界银行, 安德烈
一些附录: SSL/TLS 有符号通知对方关闭连接。 它 close_notify 警报。底层 TCP 套接字也可以关闭。
所以,基本上,我的问题是:为什么在相同的条件下(TCP 套接字已明确关闭),我在 tcp::socket 的情况下收到 EOF,并且没有收到 boost::asio::ssl::stream 的任何内容。
是 bug 还是 asio 功能?
还有一个附录: 由于某些原因,无论 SSL 接收 close_notify 还是底层 TCP 套接字关闭,asio 都没有给我 EOF。
是的,我可以通过超时检测死连接。 但如何检测正确关闭的 SSL 连接呢?通过接收short_read?
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?
- boost 1.48
- OpenSSL 1.0.0e
- Compiled to 32-bit code using VS10
- Working on W7 x64.
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
此处预计会出现
SSL_R_SHORT_READ
错误。当服务器使用SSL_Shutdown
启动干净关闭时,这会发送关闭通知关闭向客户发出警报。 Asio 实现将其映射到类别为error::get_ssl_category()
的SSL_R_SHORT_READ
错误。它通过检测对等方是否已通过SSL_get_shutdown
启动关闭来实现此目的。这可以通过检查
asio/ssl/detail/impl/engine.ipp
标头,特别是函数engine::map_error_code(boost::system::error_code&)
来看到。我相信 ssl 实现在 boost 1.47 中被重写,因此早期版本可能具有不同的行为。
The
SSL_R_SHORT_READ
error is expected here. When the server initiates a clean shutdown withSSL_Shutdown
this sends the close notify shutdown alert to the client. The Asio implementation maps this into anSSL_R_SHORT_READ
error with the category oferror::get_ssl_category()
. It does this by detecting if the peer has initiated shutdown viaSSL_get_shutdown
.This can be seen by inspecting
asio/ssl/detail/impl/engine.ipp
header and specifically the functionengine::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.
您可能对这些讨论感兴趣:
从本质上讲,事实上,有时(甚至大多数时候)您会收到 EOF远程方断开普通 TCP 套接字只是运气好。在一般情况下,您不能依赖它,因为它不可能区分不活动的套接字和突然关闭的套接字而不写入它。
您需要在应用程序协议级别定义一些分隔符,以了解何时停止读取。
在 HTTP 中,这是通过结束标头(对于标头)的空行、定义正文长度的
Content-Length
标头或 分块传输编码 分隔符(当预先未知正文长度时)。You may be interested in these discussions:
Essentially, the fact that you get an EOF sometimes (even most of the time) when the remote party disconnects a plain TCP socket is just luck. You can't rely on it in the general case, since it's not possibly to distinguish between an inactive socket and a socket closed abruptly without writing to it.
You need to define some delimiters, at the application protocol level, to know when to stop reading.
In HTTP, this is done either via the blank line that ends the header (for the header), the
Content-Length
header that defines the body length, or the chunked transfer encoding delimiters when the body length isn't known in advance.