Boost:: asio не может читать более 65536 байт из файла

Я не могу читать больше, чем 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.

Это воспроизводится с повышением 1.61 + VS2015. Также эта проблема была в повышении 1.55 + VS2010. Операционные системы: Windows 7 и Windows Server 2008R2.

Мои вопросы: 1. Известно ли это ограничение в boost::asio или в WinAPI? 2. Если это известное ограничение, каков будет безопасный размер буфера для чтения данных? Безопасно ли иметь буфер размером 65536, или он должен быть меньше?

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_handle и async_read_at(device, 0, ...). Заметки Случайные обращения 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() будет использовать смещение, соответствующее концу предыдущего чтения при чтении с устройства (например, начальное смещение + текущие байты)


Как 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;
}

Фактическое максимальное количество байтов, которое будет считаться в одном вызове, поступает из 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);
</dword>

op->Offset и op->OffsetHigh всегда равны 0. Указатель внутри вашего буфера будет продвигаться правильно, но каждый фрагмент будет считан с начала файла.

Там есть async_read_some_at(), который вы должны использовать вместо него, а также windows::random_access_handle. Это правильно установит элементы Offset и OffsetHigh. Вам нужно будет следить за количеством прочитанных байтов.

В документации для OVERLAPPED говорится следующее:

Члены Offset и OffsetHigh вместе представляют собой позицию 64-битного файла. Это смещение байта от начала файла или файлового устройства, и оно задается пользователем; система не будет изменять эти значения. Вызывающий процесс должен установить этот элемент перед передачей структуры OVERLAPPED в функции, которые используют смещение, например функции ReadFile или WriteFile (и связанные).

Также эта часть в Синхронный и асинхронный ввод-вывод:

Система не поддерживает указатель на асинхронные дескрипторы файлов и устройств, поддерживающих указатели файлов (т.е. поиск устройств), поэтому позиция файла должна передаваться в функции чтения и записи в соответствующих элементах данных смещения структура OVERLAPPED. Для получения дополнительной информации см. WriteFile и ReadFile.

licensed under cc by-sa 3.0 with attribution.