使用Cap'n'Proto进行序列化时流式传输

时间:2016-01-16 07:44:19

标签: c++ capnproto

考虑像这样的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消息是自我分隔的,所以这应该有效。但是你丢失了你的文档根目录 - 也许有时候这不是一个真正的选择。

1 个答案:

答案 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的父项目中的几个地方成功使用了这个技巧。但是,我怀疑对于您的用例,将文档拆分为一系列消息可能更有意义。