考虑像这样的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的构建器。这样额外需要的内存缓冲区是常量(即O(max(sizeof(Header),sizeof(Record),sizeof(Footer)))。
查看教程资料我找不到这样的构建器。 MallocMessageBuilder
似乎首先在内存中创建所有内容(然后在其上调用writeMessageToFd
)。
Cap'n'Proto API是否支持这样的用例?
或者Cap'n'Proto是否更适合用于发送之前适合内存的消息?
在这个例子中,可以省略Document结构,然后可以发送一个Header消息,n个Record消息和一个Footer的序列。由于Cap'n'Proto消息是自我分隔的,所以这应该有效。但是你丢失了你的文档根目录 - 也许有时候这不是一个真正的选择。
答案 0 :(得分:8)
您概述的解决方案 - 将文档的各个部分作为单独的消息发送 - 可能最适合您的用例。从根本上说,Cap&n Proto并不是设计用于单个消息的流式块,因为它不适合其随机访问属性(例如,当您尝试跟踪指向某个块的指针时会发生什么?# 39;收到了吗?)。相反,当您想要流式传输时,您应该将大型消息拆分为一系列较小的消息。
也就是说,与其他类似的系统(例如Protobuf)不同,Cap&#Proto并不严格要求消息适合内存。具体来说,您可以使用mmap(2)
做一些技巧。如果您的文档数据来自磁盘上的文件,您可以mmap()
将文件存入内存,然后将其合并到您的消息中。对于mmap()
,操作系统在您尝试访问内存之前实际上并不从磁盘读取数据,并且操作系统也可以在访问后从内存中清除页面,因为它知道它仍然在磁盘上有副本。这通常可以让您编写更简单的代码,因为您不再需要考虑内存管理。
为了将mmap()
ed块添加到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&#39; n Proto是零拷贝,所以最终会将mmap()
内存直接写入套接字,而不会访问它。然后由操作系统根据需要从磁盘读取内容并输出到套接字。
当然,接收端仍然存在问题。您会发现将接收端设计为读入mmap()
内存非常困难。一种策略可能是首先将整个流直接转储到文件中(不涉及Cap&#39; n Proto库),然后mmap()
该文件并使用capnp::FlatArrayMessageReader
来读取mmap()
就地编辑数据。
我描述了这一切,因为它是Cap&#39; n Proto可能实现的一个简洁的事情,但不是大多数其他序列化框架(例如,你无法用Protobuf做到这一点)。使用mmap()
玩弄技巧有时非常有用 - 我已经在Sandstorm,Cap&#Proto的父项目中的几个地方成功使用了这个技巧。但是,我怀疑对于您的用例,将文档拆分为一系列消息可能更有意义。