使用调度程序的 C++ NTOH 转换 - 事件队列

C++ NTOH conversion with dispatcher - event queue

我们正在将 C 语言的遗留代码重写为 C++。在我们系统的核心,我们有一个连接到 master 的 TCP 客户端。主人将不断地流式传输消息。每个套接字读取都会产生 N 条格式为 - {type, size, data[0]} 的消息。

现在我们不将这些消息复制到单独的缓冲区中 - 而是将指针传递给消息的开头、长度和 shared_ptr 到底层缓冲区给工人。

旧版 C 版本是单线程的,会像下面这样进行就地 NTOH 转换:

struct Message {
   uint32_t something1; 
   uint16_t something2; 
};

process (char *message)
Message *m = (message);
m->something1 = htonl(m->something1);
m->something2 = htons(m->something2);

然后使用消息

在登录新代码时遇到一些问题。

  1. 由于我们将消息分派给不同的工作人员,每个进行 ntoh 转换的工作人员都会导致缓存未命中问题,因为消息未在缓存中对齐 - 即没有填充 b/w留言。

  2. 同一条消息可以由不同的工作人员处理 - 这种情况下消息需要在本地处理并中继到另一个进程。这里中继工作者需要原始网络顺序的消息,本地工作需要转换为主机顺序。显然由于消息不重复,两者都不能满足。

我想到的解决方案是 -

  1. 复制该消息并发送一份给所有中继工作者(如果有的话)。在调度之前对属于同一缓冲区的所有消息在调度程序中执行 ntoh 转换 - 例如通过调用 handler->ntoh(message); 以便解决缓存未命中问题。

  2. 向每位工人发送原件。每个worker将消息复制到本地缓冲区,然后进行ntoh转换并使用它。这里每个工作人员都可以使用线程特定的 (thread_local) 静态缓冲区作为暂存器来复制消息。

现在我的问题是

  1. 选项 1 是进行 ntoh 转换的方法 - C++sy 吗?我的意思是结构的对齐要求将不同于 char 缓冲区。 (我们还没有遇到任何问题。)。在这种情况下使用方案 2 应该没问题,因为临时缓冲区可以对齐 max_align_t,因此应该可以类型转换为任何结构。但这会导致复制整个消息 - 这可能会很大(比如几 K 大小)

  2. 有更好的处理方法吗?

您的主要问题似乎是如何处理未对齐的消息。也就是说,如果每个消息结构在其末尾没有足够的填充以使后续消息正确对齐,您可以通过将指向消息开头的指针重新解释为 object 来触发未对齐的读取。

我们可以通过多种方式解决这个问题,也许最简单的方法是 ntoh 基于 single-byte 指针,它实际上总是对齐的。

我们可以隐藏包装器 classes 背后令人讨厌的细节,它将采用指向消息开头的指针并具有将 ntoh 适当字段的访问器。

如评论中所述,要求偏移量由 C++ 结构确定,因为消息最初是这样创建的,可能不会打包。

首先,我们的 ntoh 实现是模板化的,因此我们可以 select 一个类型:

template <typename R>
struct ntoh_impl;

template <>
struct ntoh_impl<uint16_t>
{
    static uint16_t ntoh(uint8_t const *d)
    {
        return (static_cast<uint16_t>(d[0]) << 8) |
                d[1];
    }
};

template <>
struct ntoh_impl<uint32_t>
{
    static uint32_t ntoh(uint8_t const *d)
    {
        return (static_cast<uint32_t>(d[0]) << 24) |
               (static_cast<uint32_t>(d[1]) << 16) |
               (static_cast<uint32_t>(d[2]) <<  8) |
               d[3];
    }
};

template<>
struct ntoh_impl<uint64_t>
{
    static uint64_t ntoh(uint8_t const *d)
    {
        return (static_cast<uint64_t>(d[0]) << 56) |
               (static_cast<uint64_t>(d[1]) << 48) |
               (static_cast<uint64_t>(d[2]) << 40) |
               (static_cast<uint64_t>(d[3]) << 32) |
               (static_cast<uint64_t>(d[4]) << 24) |
               (static_cast<uint64_t>(d[5]) << 16) |
               (static_cast<uint64_t>(d[6]) <<  8) |
               d[7];
    }
};

现在我们将定义一组讨厌的宏,通过在结构 proto 中查找具有匹配名称的成员(每个 [=44 的私有结构)自动实现给定名称的访问器=]):

#define MEMBER_TYPE(MEMBER) typename std::decay<decltype(std::declval<proto>().MEMBER)>::type

#define IMPL_GETTER(MEMBER) MEMBER_TYPE(MEMBER) MEMBER() const { return ntoh_impl<MEMBER_TYPE(MEMBER)>::ntoh(data + offsetof(proto, MEMBER)); }

最后,我们有一个您给出的消息结构的示例实现:

class Message
{
private:
    struct proto
    {
        uint32_t something1;
        uint16_t something2;
    };

public:
    explicit Message(uint8_t const *p) : data(p) {}
    explicit Message(char const *p) : data(reinterpret_cast<uint8_t const *>(p)) {}

    IMPL_GETTER(something1)
    IMPL_GETTER(something2)

private:
    uint8_t const *data;
};

现在 Message::something1()Message::something2() 已实现,并且将从 data 指针读取它们最终在 Message::proto.

中相同的偏移量

提供 header 中的实现(有效内联)有可能在每个访问器的调用站点内联整个 ntoh 序列!

此 class 不拥有构建它的数据分配。如果这里有 ownership-maintaining 详细信息,您大概可以写一个基础 class。