我无法使用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);
该代码产生输出:
> 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重现。此问题还在于提升1.55 + VS2010 操作系统包括:Windows 7和Windows Server 2008R2。
我的问题是:
1.这是boost::asio
或WinAPI
中的已知限制吗?
2.如果是已知限制,读取数据的缓冲区的安全大小是多少?有一个大小为65536的缓冲区是安全的,还是应该更小?
答案 0 :(得分:5)
作为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上也称为&#34;重叠&#34;),必须为每次读取指定偏移量。
这是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->Offset
和op->OffsetHigh
始终为0.缓冲区内的指针将正确前进,但每个块都将从文件的开头读取。
有一个async_read_some_at()
可用,您应该使用它,以及windows::random_access_handle
。这将正确设置Offset
和OffsetHigh
成员。您必须跟踪自己读取的字节数。
OVERLAPPED结构的文档说明了这一点:
Offset和OffsetHigh成员一起代表64位文件位置。它是从文件或类文件设备的开头偏移的字节,由用户指定;系统不会修改这些值。调用进程必须在将OVERLAPPED结构传递给使用偏移量的函数(例如ReadFile或WriteFile(及相关)函数)之前设置此成员。
还有Synchronous and Asynchronous I/O中的这一部分:
系统不会将文件指针保存在支持文件指针的文件和设备的异步句柄上(即寻找设备),因此必须将文件位置传递给相关偏移数据成员中的读写函数。 OVERLAPPED结构。有关更多信息,请参阅WriteFile和ReadFile。
答案 1 :(得分:4)
这既不是Asio,Windows的限制,也不是缓冲区大小。相反,Asio正在执行它在规范中被告知要做的事情:它正在从常规文件读取100000
字节,如果它是流。使用windows::stream_handle
:
async_read()
将由零个或多个中间async_read_some()
操作组成,直到应用程序请求的字节数已传输,或者直到发生错误
此操作是根据对流
async_read_some
函数的零次或多次调用实现的,称为组合操作。
async_read_some()
操作可能读取的内容少于请求的字节数
读操作可能无法读取所有请求的字节数。
每个中间async_read_some()
操作都会从流的开头读取
由于正在使用的文件句柄不是真正的流,而是常规文件,因此请考虑使用windows::random_access_handle
和async_read_at(device, 0, ...)
。 Random-Access HANDLEs文档说明:
Boost.Asio提供特定于Windows的类,允许在引用常规文件的HANDLE上执行异步读写操作。
使用windows::random_access_handle
和async_read_at()
时:
async_read_at()
将由零个或多个中间async_read_some_at()
操作组成,直到应用程序请求的字节数已传输,或者直到发生错误async_read_some_at()
操作可能读取的内容少于请求的字节数async_read_some_at()
操作将使用与从设备读取时前一次读取结束相对应的偏移量(例如,初始偏移量+当前传输的字节数)