boost :: asio无法从文件中读取超过65536个字节

Ale*_*iuk 4 c++ winapi boost boost-asio

我无法65536使用boost::asio::windows::stream_handle异步方式从文件中读取超过字节的字节数.

65537第一个字节开始,缓冲区包含文件最开头的数据,而不是预期的数据.

这是一个代码示例,它重现了这个问题:

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);
Run Code Online (Sandbox Code Playgroud)

该代码产生一个输出:

> Bytes read: 100000  
> offset 0: <?xml version="1.0" encoding="UTF-8"?>  
> offset 65536: <?xml version="1.0" encoding="UTF-8"?>
Run Code Online (Sandbox Code Playgroud)

源文件大于65536.

这可以通过boost 1.61 + VS2015重现.此问题还在于提升1.55 + VS2010.
操作系统包括:Windows 7和Windows Server 2008R2.

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

isa*_*nae 5

正如Tanner Sansbury所说,你打开了一个文件,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;
}
Run Code Online (Sandbox Code Playgroud)

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

enum default_max_transfer_size_t { default_max_transfer_size = 65536 };
Run Code Online (Sandbox Code Playgroud)

问题是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);
Run Code Online (Sandbox Code Playgroud)

op->Offset并且op->OffsetHigh始终为0.缓冲区内的指针将正确前进,但每个块都将从文件的开头读取.

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

OVERLAPPED结构的文档说明了这一点:

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

同步和异步I/O中还有这部分:

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

  • 你的答案肯定值得更多的一个upvote.我在这里提交了错误报告:https://svn.boost.org/trac/boost/ticket/12383.我稍后会添加一个指向错误报告答案的链接.感谢帮助 :) (2认同)