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】:

我建议在 Httpsize_t bytes_transferred_total_; 中添加一个总字节计数器并放弃 err == boost::asio::error::eof。您知道总大小,因为它是您解析的原始 HTTP 标头 Content-Length: &lt;total_body_size&gt; 的一部分。修改后的代码如下所示:

...
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::bindstd::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

优雅地取消 boost::asio::async_read

boost::asio::async_read 在接收到完整的 Content-Length 之前接收 EOF

Boost::asio async_read 简单文件上传