Рассмотрим схему Cap’n’Proto следующим образом:
struct Document {
header @0 : Header;
records @1 :List(Record); // usually large number of records.
footer @2 :Footer;
}
struct Header { numberOfRecords : UInt32; /* some fields */ };
struct Footer { /* some fields */ };
struct Record {
type : UInt32;
desc : Text;
/* some more fields, relatively large in total */
}
Теперь я хочу сериализовать (т.е. собрать) экземпляр документа и передать его в удаленный пункт назначения.
Поскольку документ обычно очень большой, я не хочу полностью встраивать его в память перед его отправкой. Вместо этого я ищу строителя, который напрямую отправляет struct через struct по проводам. Так что дополнительный необходимый буфер памяти является постоянным (то есть O (max (sizeof (Заголовок), sizeof (Запись), sizeof (Нижний колонтитул))).
Глядя на учебный материал, я не нахожу такого строителя. MallocMessageBuilder
Кажется, сначала создается все в памяти (потом writeMessageToFd
в теме).
Поддерживает ли API-интерфейс Cap’n’Proto такой вариант использования?
Или Cap’n’Proto больше предназначен для сообщений, которые помещаются в память перед отправкой?
В этом примере структура документа может быть опущена, а затем можно просто отправить последовательность из одного сообщения заголовка, n сообщений записи и одного нижнего колонтитула. Поскольку сообщение Cap’n’Proto является саморазграничением, это должно работать. Но вы теряете свой корень документа — возможно, иногда это не вариант.
Решение, которое вы обрисовали в общих чертах — отправка частей документа в виде отдельных сообщений — вероятно, лучше всего подходит для вашего случая использования. По сути, Cap’n Proto не предназначен для потоковой передачи фрагментов одного сообщения, так как это не соответствует его свойствам произвольного доступа (например, что происходит, когда вы пытаетесь следовать указателю, который указывает на блок, который вы не получили) еще?). Вместо этого, когда вы хотите потоковую передачу, вы должны разбить большое сообщение на серию меньших сообщений.
Тем не менее, в отличие от других подобных систем (например, Protobuf), Cap’n Proto не требует строго сообщений, чтобы поместиться в память. В частности, вы можете сделать несколько трюков, используя mmap(2)
. Если данные вашего документа поступают из файла на диске, вы можете mmap()
файл в память, а затем включить его в ваше сообщение. С mmap()
операционная система фактически не считывает данные с диска до тех пор, пока вы не попытаетесь получить доступ к памяти, и ОС также может удалить страницы из памяти после того, как к ним будет осуществлен доступ, поскольку она знает, что все еще имеет копию на диске. Это часто позволяет вам писать гораздо более простой код, так как вам больше не нужно думать об управлении памятью.
Для того, чтобы включить mmap()
Ed Chunk в сообщении Cap’n Proto, вы хотите использовать capnp::Orphanage::referenceExternalData()
, Например, учитывая:
struct MyDocument {
body @0 :Data;
# (other fields)
}
Вы могли бы написать:
// Map file into memory.
void* ptr = (kj::byte*)mmap(
nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (ptr == MAP_FAILED) {
KJ_FAIL_SYSCALL("mmap", errno);
}
auto data = capnp::Data::Reader((kj::byte*)ptr, size);
// Incorporate it into a message.
capnp::MallocMessageBuilder message;
auto root = message.getRoot<MyDocument>();
root.adoptDocumentBody(
message.getOrphanage().referenceExternalData(data));
Поскольку Cap’n Proto является нулевой копией, он в конечном итоге напишет mmap()
Выдал память прямо в сокет, не обращаясь к ней. Затем операционная система может считывать содержимое с диска и, соответственно, из сокета.
Конечно, у вас все еще есть проблема на принимающей стороне. Вам будет гораздо сложнее спроектировать принимающую сторону для чтения в mmap()
Эд память. Одна из стратегий может заключаться в том, чтобы сначала вывести весь поток непосредственно в файл (без использования библиотеки Cap’n Proto), затем mmap()
этот файл и использовать capnp::FlatArrayMessageReader
читать mmap()
редактировать данные на месте.
Я описываю все это, потому что это хорошо, что возможно с Cap’n Proto, но не с большинством других сред сериализации (например, вы не можете сделать это с Protobuf). Трюки с mmap()
иногда действительно полезно — я успешно использовал это в нескольких местах самум, Родительский проект Cap’n Proto. Однако, я подозреваю, что для вашего случая использования разделение документа на серию сообщений, вероятно, имеет больше смысла.
Других решений пока нет …