【问题标题】:Stream while serializing with Cap'n'Proto使用 Cap'n'Proto 序列化时流式传输
【发布时间】:2016-01-17 04:34:53
【问题描述】:

考虑这样的 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 */
}

现在我想序列化(即构建)一个文档实例并将其流式传输到远程目标。

由于文档通常非常大,我不想在发送之前将其完全构建在内存中。相反,我正在寻找一个通过网络直接发送结构的构建器。这样额外需要的内存缓冲区是恒定的(即 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 消息是自定界的,因此应该可以。但是你失去了你的文档根——也许有时这不是一个真正的选择。

【问题讨论】:

    标签: c++ capnproto


    【解决方案1】:

    您概述的解决方案(将文档的各个部分作为单独的消息发送)可能最适合您的用例。从根本上说,Cap'n Proto 并不是为单个消息的流式传输而设计的,因为这不适合其随机访问属性(例如,当您尝试跟随指向您尚未收到的块的指针时会发生什么然而?)。相反,当您想要流式传输时,您应该将一条大消息拆分为一系列较小的消息。

    也就是说,与其他类似系统(例如 Protobuf)不同,Cap'n 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'n Proto 是零拷贝的,它最终会将mmap()ed 内存直接写入套接字而无需访问它。然后由操作系统根据需要从磁盘读取内容并输出到套接字。

    当然,你在接收端还是有问题。您会发现将接收端设计为读入mmap()ed 内存要困难得多。一种策略可能是首先将整个流直接转储到文件(不涉及 Cap'n Proto 库),然后mmap() 该文件并使用capnp::FlatArrayMessageReader 就地读取mmap()ed 数据。

    我之所以描述这一切,是因为 Cap'n Proto 可以做到这一点,但大多数其他序列化框架却不能做到这一点(例如,你不能用 Protobuf 做到这一点)。与mmap() 玩技巧有时真的很有用——我已经在Cap'n Proto 的父项目Sandstorm 的几个地方成功地使用了它。但是,我怀疑对于您的用例,将文档拆分为一系列消息可能更有意义。

    【讨论】:

    • 其他人注意:Java 库仍然缺少 Orphans API,所以上面的方法在那里不起作用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-27
    • 2020-07-17
    • 1970-01-01
    • 2021-03-13
    相关资源
    最近更新 更多