c++ - 如何在C++中使用无锁循环缓冲区实现零拷贝tcp

标签 c++ tcp circular-buffer lockless zero-copy

我有多个线程需要使用 TCP 流中的数据。我希望在共享内存中使用循环缓冲区/队列来读取 TCP 套接字。 TCP 接收将直接写入循环队列。消费者将从队列中读取。

此设计应启用零复制和零锁定。但是这里有两个不同的问题。

  1. 从 TCP 套接字中只读取 1 条逻辑消息是否可能/有效?如果没有,并且我阅读了超过 1 条消息,我将不得不将残差从这条复制到这条->下一条。

  2. 真的可以实现无锁队列吗?我知道有原子操作,但这些也可能很昂贵。因为所有的 CPU 缓存都需要失效。这将影响我所有 24 个核心上的所有操作。

我对低级 TCP 有点生疏,不太清楚如何判断消息何时完成。我是在寻找\0 还是特定于实现?

类型

最佳答案

不幸的是,TCP不能传输消息,只能传输字节流。如果你想传输消息,你必须在上面应用一个协议(protocol)。高性能的最佳协议(protocol)是那些使用可完整性检查的 header 指定消息长度的协议(protocol) - 这允许您将正确数量的数据直接读入合适的缓冲区对象,而无需逐字节迭代数据以寻找结束 -消息字符。然后可以将缓冲区 POINTER 排队到另一个线程,并为下一条消息创建/分离新的缓冲区对象。这避免了任何批量数据的复制,并且对于大型消息来说,效率足够高,以至于对消息对象指针使用非阻塞队列有点毫无意义。

下一个可用的优化是池化对象 *buffers 以避免连续的 new/dispose,回收消费者线程中的 *buffers 以便在网络接收线程中重新使用。使用 ConcurrentQueue 很容易做到这一点,如果池暂时清空,最好是阻塞以允许流量控制而不是数据损坏或段错误/AV。

接下来,在每个 *buffer 数据成员的开头添加一个 [cacheline size] 'dead-zone',以防止任何线程与任何其他线程错误共享数据。

结果应该是高带宽的完整消息流进入消费者线程,延迟、CPU 浪费或缓存抖动极少。您所有的 24 个内核都可以在不同的数据上全力运行。

在多线程应用程序中复制大量数据是对糟糕设计和失败的承认。

跟进..

听起来您因为协议(protocol)不同而无法迭代数据:(

False-sharing-free PDU 缓冲区对象,示例:

typedef struct{
  char deadZone[256];  // anti-false-sharing
  int dataLen;
  char data[8388608]; // 8 meg of data
} SbufferData;

class TdataBuffer: public{
private:
  TbufferPool *myPool; // reference to pool used, in case more than one
  EpduState PDUstate; // enum state variable used to decode protocol
protected:
  SbufferData netData;
public:
  virtual reInit(); // zeros dataLen, resets PDUstate etc. - call when depooling a buffer
  virtual int loadPDU(char *fromHere,int len);  // loads protocol unit
  release(); // pushes 'this' back onto 'myPool'
};

loadPDU 得到一个指向原始网络数据长度的指针。它返回 0 - 意味着它还没有完全组装一个 PDU,或者它从原始网络数据中吃掉的字节数来完全组装一个 PDU,在这种情况下,将它排入队列,分离另一个并调用 loadPDU()使用未使用的剩余原始数据,然后继续下一个原始数据。

如果需要,您可以使用不同的派生缓冲类的不同池来服务不同的协议(protocol)——TbufferPool[Eprotocols] 数组。 TbufferPool 可能只是一个 BlockingCollection 队列。管理变得几乎微不足道 - 缓冲区可以在整个系统的队列中发送,发送到 GUI 以显示统计信息,然后可能发送到记录器,只要在队列链的末尾调用 release()。

显然,“真正的”PDU 对象可能会加载更多方法、数据 union/结构、迭代器和状态引擎来操作协议(protocol),但无论如何这是基本思想。最主要的是易于管理、封装,并且由于没有两个线程可以对同一个缓冲区实例进行操作,因此不需要锁定/同步来解析/访问数据。

哦,是的,并且由于没有队列保持锁定的时间超过压入/弹出一个指针所需的时间,因此实际争用的可能性非常低 - 即使是传统的阻塞队列也几乎不需要使用内核锁定。

关于c++ - 如何在C++中使用无锁循环缓冲区实现零拷贝tcp,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11295474/

相关文章:

c++ - 在 C++ 中访问过度分配的内存

GSM 调制解调器的 ssl 证书

ios - NSStream.close() 是同步的 TCP 吗?

arm - 在小型 ARM 微 Controller 中实现 uart 接收缓冲器的正确方法?

c++ - 用于大数组的无复制线程安全环形缓冲区

c++ - Ubuntu和make中的模板编译错误

c++ - 来自 Vector 的 Direct3D 旋转矩阵,反之亦然

arrays - 将循环缓冲区移位/对齐/旋转到原地零

c++ - 指向 shared_ptr 成员变量的指针

go - 如何获取 IP 数据包并注入(inject)不同的 VM 和接口(interface)