boost::asio::async_read 在接收到完整的 Content-Length 之前接收 EOF
Posted
技术标签:
【中文标题】boost::asio::async_read 在接收到完整的 Content-Length 之前接收 EOF【英文标题】:boost::asio::async_read receiving EOF before receiving the complete Content-Length 【发布时间】:2017-08-17 12:15:55 【问题描述】:我正在使用 Boost ASIO 实现 HTTP 文件下载客户端。我正在使用 async_read 操作。我面临的问题是在 async_read 中我在接收完整内容(即 Content-Length)之前接收 EOF,无论内容大小如何,都会发生这种情况。给定的是我的读取操作
void Http::ResumableAsyncDownload::read_content(const boost::system::error_code& err, size_t _size)
try
if ( !err)
received_bytes += _size;
if ( ofs_.is_open() )
// Write all of the data that has been read so far.
ofs_ << &response_;
else
ofs_.open(std::string(params_.tempdir + "/" + params_.partnumber).c_str());
if ( ofs_.is_open() )
ofs_ << &response_;
else
DEBUG_MSG("Error while opening file to store downloaded data. File Path = %s\n", std::string(params_.tempdir + "/" + params_.partnumber).c_str());
std::cout << ("Unable to open local file for storing downloaded data");
// Continue reading remaining data until EOF.
boost::asio::async_read(*ssocket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&ResumableAsyncDownload::read_content, this, _1, _2)
);
else if (err != boost::asio::error::eof)
DEBUG_MSG("[NET] : Exception in ResumableAsyncDownload in read_content : %s\n", err.message().c_str());
std::cout << ("Asynchronous File Download Error: " + err.message());
if(err == boost::asio::error::eof)
std::cout << "[RESPONSE] : EOF: We are not breaking connection\n";
ssocket_->shutdown();
delete ssocket_;
ssocket_ = NULL;
delete ctx;
ctx = NULL;
if ( (content_length != received_bytes) && !(params_.get_size) )
std::cout << "Failed to receive complete data packet. Content Length = " << content_length << " Received Bytes = " << received_bytes << std::endl;
// ofs_.clear();
catch ( std::exception &ex )
std::cout << "We have an exception. Exception = " << std::string(ex.what()) << std::endl;
例如,Content-Length 是 292309324,但我会在 292309324 之前收到 EOF。
为了克服这个问题,我使用 HTTP Range 标头实现了 Chunked 下载,但在这种情况下,对于我请求的每个块,我收到的块都少于请求的块,然后我重新计算下一个范围,它在最后一个之前工作块。我从来没有收到最后一块,通常情况是(即)
Range for last chunk 227376464-227376641/227376641
Requested Bytes = 178
响应标头
X-Powered-By: Undertow/1
Content-Range: bytes 227376464-227376641/227376641
Server: WildFly/9
Content-Length: 178
Accept-Ranges: bytes
OperationId: 4a847024-2348-42bd-af7d-3638e41cba4f
Date: Thu, 17 Aug 2017 11:41:18 GMT
Set-Cookie: SERVERID=04-84FRD2128G0US; path=/
Cache-control: private
如您所见,服务器响应的最后一个块的范围很好,但在 read_content 中给出了 EOF。
所以在这两种方法中,read_content 都没有读取完整的数据并给出 EOF。据我了解,EOF 是服务器关闭的套接字,也可能导致短读,但不是我的分块下载解决方案。我不应该完整收到最后一个分块数据包吗?
对出了什么问题有什么想法吗?另请注意,我正在调用自定义 API 来下载文件,但即使我从某个公共链接(即http://mirror.pnl.gov/releases/16.04/ubuntu-16.04.3-desktop-amd64.iso)下载,我也会看到同样的问题,所以我认为问题不在我的服务器端。另请注意,如果我使用 boost::asio::async_read 的同步版本(即 boost::asio::read),我看不到这个问题。我正在使用为 ARM 编译的 Boost 版本 1.55。
【问题讨论】:
当你得到 eof 时,你还应该检查接收到的字节数。实际上,您应该始终检查接收到的字节数。 eof 告诉你不应该期待更多的字节,但是一些字节可能已经被缓冲了。 当我收到 EOF 时,缓冲区中没有数据。返回的大小为 0。 【参考方案1】:我建议在 Http
类 size_t bytes_transferred_total_;
中添加一个总字节计数器并放弃 err == boost::asio::error::eof
。您知道总大小,因为它是您解析的原始 HTTP 标头 Content-Length: <total_body_size>
的一部分。修改后的代码如下所示:
...
if (!err)
received_bytes += _size;
bytes_transferred_total_ += _size;
...
else
...
boost::asio::async_read(
*ssocket_, response_, boost::asio::transfer_at_least(1),
boost::bind(&ResumableAsyncDownload::read_content, this, _1, _2));
// continue reading until all data has been received
if (bytes_transferred_total_ >= content_length_from_header_)
// you've received it all
在初始化content_length_from_header_
时,请确保您了解它代表正文本身的大小,不包括标题。
脚注:考虑使用 lambdas 而不是 boost::bind
或 std::bind
,因为它们通常性能更高,例如允许编译器内联它们。当然,除非您需要利用 bind
的动态特性。
【讨论】:
试过了,但是当我收到 EOF 时,我得到 0 个数据,如果我的最后一个数据块是 170 字节(在 Content-Length 中报告)但数据的大小为 0,所以我从来没有进入您上面提到的完成条件(即 bytes_transferred_total_ >= content_length_from_header_ ),所以我传输的总字节数总是小于 content-length。 a) 不确定您对“块”一词的使用程度。对于 HTTP,它具有特殊的含义。有几种方法可以让对方知道传输何时结束:使用Content-Length
、分块传输(读取直到收到大小为 0 的块),直到连接关闭。 b) 如果任何一方报告不正确的Content-Length
,这就是您在哪里寻找问题的指示。 c) 确保您拥有正确的content_length_from_header_
,正如我提到的 d) 标头仅在传输开始时发送,如果您在传输过程中收到它们,则说明有些问题
另外,编写自己的 HTTP 解析器并非易事,如果框架允许,请查看 cpp-netlib.org
我在这里松散地使用了块这个词,而不是它在 HTTP 中的含义。我认为我的逻辑没有问题(我可能错了),我的问题是如果我尝试下载 2 MB 文件,async_read 函数以 EOF 终止而不读取所有数据(即 1.9 MB),但是我正在接收响应标头中的良好 Content-Length 但正文为空或正文给我说 1.4 MB 的数据。我曾尝试使用 HTTP 的 Range 功能来解决此问题,但在这种情况下,对于我的最后一个数据块,我从未收到最后一个字节。
我没有在中间收到标题,也没有在 Content-Length 中包含标题。在我看来,问题出在 async_read 方法中。如果我使用 boost::asio::read 函数,我在多个字节范围内分配完整 2MB 的算法非常有效。但我需要使用 boost::asio::async_read 函数来检测网络断开连接。以上是关于boost::asio::async_read 在接收到完整的 Content-Length 之前接收 EOF的主要内容,如果未能解决你的问题,请参考以下文章
boost::asio::async_read 不回调我的处理函数
boost::asio::async_read 无限循环,接收数据为零字节
如何在到达终止字符时返回 boost::asio::async_read