【问题标题】:boost::asio fails to read more than 65536 bytes from fileboost::asio 无法从文件中读取超过 65536 个字节
【发布时间】:2016-08-08 16:01:14
【问题描述】:

我无法从使用 boost::asio::windows::stream_handle 的文件异步读取超过 65536 字节到缓冲区。

65537th 字节开始,缓冲区包含文件开头的数据,而不是预期的数据。

这是一个重现问题的代码示例:

auto handle = ::CreateFile(L"BigFile.xml", GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
boost::asio::io_service ios;

boost::asio::windows::stream_handle streamHandle(ios, handle);

const auto to_read_bytes = 100000;
char buffer[to_read_bytes];

boost::asio::async_read(streamHandle, boost::asio::buffer(buffer, to_read_bytes), [](auto &ec, auto read) {
    std::cout << "Bytes read: " << read << std::endl;
});

ios.run();

auto bufferBegin = std::string(buffer, 38);
auto bufferCorrupted = std::string(buffer + 65536, 38);   // <- it contains bytes from the beginning of the file

std::cout << "offset 0: " << bufferBegin << std::endl;
std::cout << "offset 65536: " << bufferCorrupted << std::endl;   

::CloseHandle(handle);

该代码产生一个输出:

> Bytes read: 100000  
> offset 0: <?xml version="1.0" encoding="UTF-8"?>  
> offset 65536: <?xml version="1.0" encoding="UTF-8"?>

源文件大于 65536。

这可以通过 boost 1.61 + VS2015 重现。这个问题也出现在 boost 1.55 + VS2010 中。
操作系统有:Windows 7 和 Windows Server 2008R2。

我的问题是:
1.这是boost::asioWinAPI的已知限制吗?
2. 如果是已知限制,读取数据的缓冲区的安全大小是多少?缓冲区大小为 65536 是否安全,或者应该更小?

【问题讨论】:

  • 这不是 Windows API 中的限制——那里存在的限制(在旧 API 方法中)是 2^32 而不是 2^16。
  • 您可能需要指定您正在使用的软件版本(boost、windows 和 Visual C++)
  • @nate 是的,我肯定需要指定版本。谢谢提醒。我相应地更新了问题。
  • 问题可能出在堆栈限制上,通常为 100,000。使用在堆上分配内存的std::string,而不是在堆栈上分配的char buffer[100000];。或者只是使用to_read_bytes = 4096,如果增加缓冲区大小,它不会变得更快。
  • @BarmakShemirani 不,这不是堆栈限制。同样的行为是在堆中分配缓冲区时。

标签: c++ winapi boost boost-asio


【解决方案1】:

作为Tanner Sansbury says,您使用FILE_FLAG_OVERLAPPED 打开了一个文件,但您正试图将其用作流。不是。

async_read()基本上就是asio/impl/read.hpp中的这个循环:

for (;;)
{
    stream_.async_read_some(buffers_, ASIO_MOVE_CAST(read_op)(*this));

    buffers_.consume(bytes_transferred);
    total_transferred_ += bytes_transferred;

    if (!ec && bytes_transferred == 0)
        break;
}

一次调用实际读取的最大字节数来自completion_condition.hpp

enum default_max_transfer_size_t { default_max_transfer_size = 65536 };

问题是上面的async_read_some() 调用。你会注意到没有偏移量告诉它从哪里开始阅读。因为您使用的是异步读取(在 Windows 上也称为“重叠”),所以必须为每次读取指定偏移量。

这就是它结束的地方,在asio/detail/impl/win_iocp_handle_service.ipp

DWORD bytes_transferred = 0;
op->Offset = offset & 0xFFFFFFFF;
op->OffsetHigh = (offset >> 32) & 0xFFFFFFFF;
BOOL ok = ::ReadFile(impl.handle_, buffer.data(),
    static_cast<DWORD>(buffer.size()),
    &bytes_transferred, op);

op-&gt;Offsetop-&gt;OffsetHigh 始终为 0。缓冲区内的指针将正确前进,但将从文件开头读取每个块。

有一个可用的async_read_some_at()windows::random_access_handle,您应该改用它。这将正确设置OffsetOffsetHigh 成员。您必须自己跟踪读取的字节数。

OVERLAPPED 结构的文档是这样说的:

Offset 和 OffsetHigh 成员一起表示 64 位文件位置。它是从文件或类文件设备开始的字节偏移量,由用户指定;系统不会修改这些值。调用进程必须在将 OVERLAPPED 结构传递给使用偏移量的函数之前设置此成员,例如 ReadFile 或 WriteFile(及相关)函数。

Synchronous and Asynchronous I/O也有这部分:

系统不会在支持文件指针的文件和设备(即寻找设备)的异步句柄上维护文件指针,因此必须将文件位置传递给相关偏移数据成员中的读写函数重叠的结构。有关详细信息,请参阅 WriteFile 和 ReadFile。

【讨论】:

  • 您的回答绝对值得一票。我在这里提交了错误报告:svn.boost.org/trac/boost/ticket/12383。稍后我将添加一个指向您对错误报告的答案的链接。感谢您的帮助:)
  • @AlexanderStepaniuk 主要 repo 在 github 上,它在发布时只是增强了。如果您提交错误报告on the github tracker,您可能会得到更快的答案。不过不确定。
  • @AlexanderStepaniuk 好吧,我知道如何,但他知道为什么:您的文件不是流。你应该接受他的回答,我会修改我的帖子,让它不再表明它是一个错误。
  • 好的,我会接受另一个答案,因为他提供了有效的解决方案。但是您的回答也很有价值,因为它详细解释了根本原因。
【解决方案2】:

这既不是 Asio、Windows 的限制,也不是缓冲区大小的限制。相反,Asio 正在执行它在其规范中被告知要做的事情:它正在从常规文件中读取100000 字节就像它是一个。与windows::stream_handle

  • async_read() 将由零个或多个中间async_read_some() 操作组成,直到传输应用程序请求的字节数,或直到发生错误

    此操作通过对流的async_read_some 函数的零次或多次调用来实现,称为组合操作

  • async_read_some() 操作读取的字节数可能少于请求的字节数

    读取操作可能不会读取所有请求的字节数。

  • 每个中间 async_read_some() 操作将从流的开头读取

由于使用的文件句柄不是真正的流,而是常规文件,请考虑使用windows::random_access_handleasync_read_at(device, 0, ...)Random-Access HANDLEs 文档说明:

Boost.Asio 提供特定于 Windows 的类,这些类允许在引用常规文件的 HANDLE 上执行异步读写操作。

使用windows::random_access_handleasync_read_at() 时:

  • async_read_at() 将由零个或多个中间 async_read_some_at() 操作组成,直到传输了应用程序请求的字节数,或者直到发生错误
  • async_read_some_at() 操作读取的字节数可能少于请求的字节数
  • 每个中间 async_read_some_at() 操作在从设备读取时将使用与上一次读取结束相对应的偏移量(例如,初始偏移量 + 当前传输的字节数)

【讨论】:

  • 谢谢您的回答。现在很清楚如何异步读取大文件了。但我仍然认为这是 Asio 的一个错误。文档中至少有一个错误,应该提到这种行为。作为一个简单的操作给出了错误的结果,没有任何编译时或运行时通知。
  • @Alexander 我相信该行为已记录在案。我怀疑任何编译时错误都是可能的。但是,通过HANDLE 查询文件信息,stream_handle::assign() 可能会因不支持的错误而失败,假设没有令人信服的用例来支持常规文件的stream_handle
  • 我在文档中找不到任何关于异步读取超过 2^16 字节将不起作用的限制的警告。 boost.org/doc/libs/1_61_0/boost/asio/windows/… - 这里记录了方法 async_read_some() ,它什么也没说。这里提到了async_read() 的用法,当我们需要保证读取的数据量时。
  • @Alexander 没有限制。 async_read() 组合操作可以异步读取超过 65536 个字节。问题中的示例甚至表明它成功读取了 10000:第一个 async_read_some() 读取了 65536 个字节(文件中的字节 0 到 65536),第二个 async_read_some() 读取了 34464 个字节(文件中的字节 0 到 34464)。我知道这不是您的用例所需的行为,但它适用于流。文档明确指出 windows::random_access_handle 应用于常规文件。
猜你喜欢
  • 2023-04-09
  • 2015-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多