C++编程:使用cpp-ipc实现基于共享内存的进程间发布订阅

0. 概要

C++编程: 解读无锁队列(Lock-Free)的原理以及实现 这篇文章中提到了开源进程间通信库cpp-ipc

现在介绍 mutouyun/cpp-ipc 这个库,它为我们提供了一种高效、跨平台且无锁的进程间通信解决方案。本文将基于cpp-ipc 实现两个生产者和消费者实例,特别重点介绍可以传输大文件的示例代码。

1. 该库主要特性

  • 跨平台:支持 Linux、Windows 系统及 x86、x64、ARM 架构稳定运行。
  • 语言标准:支持 C++17/14,仅依赖 STL 库,封装平台相关部分。
  • 无锁机制:用无锁或轻量级 spin-lock 策略,提高并发性能。
  • 底层数据结构:基于循环数据,高效存储和访问数据。
  • 通信模式:ipc::route单写多读,ipc::channel多写多读,默认广播,可自选读写方案。
  • 智能等待:不长时忙等,重试后用信号量等待,支持超时,避免资源浪费。

2. Simple单生产者多消费者示例

#include <chrono>
#include <iostream>
#include <string>
#include <thread>

#include "libipc/ipc.h"

std::vector<char const*> const datas = {"hello!",
                                        "foo",
                                        "bar",
                                        "ISO/IEC",
                                        "14882:2011",
                                        "ISO/IEC 14882:2017 Information technology - Programming languages - C++",
                                        "ISO/IEC 14882:2020",
                                        "Modern C++ Design: Generic Programming and Design Patterns Applied"};

constexpr const char* kIpcRouteName = "my-ipc-route6";

void producer() {
  ipc::route cc{kIpcRouteName, ipc::sender};
  // waiting for connection
  cc.wait_for_recv(1);
  // sending datas
  for (auto str : datas) {
    cc.send(str);
    std::this_thread::sleep_for(std::chrono::microseconds(1));
  }
  // quit
  cc.send(ipc::buff_t('\0'));
}

void consumer(const char* consumer_name) {
  ipc::route cc{kIpcRouteName, ipc::receiver};
  while (1) {
    auto buf = cc.recv();
    auto str = static_cast<char*>(buf.data());
    if (str == nullptr || str[0] == '\0') {
      return;
    }
    std::printf("[%s] recv: %s\n", consumer_name, str);
  }
}

int main() {
  // thread producer
  std::thread t1(producer);
  // thread consumer 1
  std::thread t2(consumer, "consumer1");

  // thread consumer 2
  std::thread t3(consumer, "consumer2");

  t1.join();
  t2.join();
  t3.join();

  return 0;
}

在这个示例中,生产者通过循环发送数据,消费者则不断接收数据,直到接收到空字符为止。

3. 传输二进制大块数据附带 CRC 示例

使用共享内存进行通信的一个主要目的在于解决拷贝大文件数据时的低性能问题,至少共享内存的适当拷贝性能要优于 IPC 管道。

3.1 实现代码

#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <random>
#include <string>
#include <thread>
#include <vector>

#include "libipc/ipc.h"

// 定义大数据块结构体
struct LargeDataBlock {
  uint32_t length;
  uint16_t crc;
  std::vector<uint8_t> data;
};

// 计算 CRC-16 值
uint16_t calculate_crc(const std::vector<uint8_t>& data) {
  uint16_t crc = 0xFFFF;
  for (auto byte : data) {
    crc ^= byte;
    for (int i = 0; i < 8; ++i) {
      if (crc & 0x0001)
        crc = (crc >> 1) ^ 0xA001;
      else
        crc >>= 1;
    }
  }
  return crc;
}

// 定义大数据块大小范围
constexpr size_t kMinDataSize = 1024 * 1024;      // 1 MB
constexpr size_t kMaxDataSize = 4 * 1024 * 1024;  // 4 MB
constexpr const char* kIpcRoute = "my-ipc-route44";

std::vector<uint8_t> large_data;

// 创建一个大字符串并填充随机数据
uint32_t create_large_data() {
  std::random_device rd;
  std::mt19937 gen(rd());
  std::uniform_int_distribution<> dis(kMinDataSize, kMaxDataSize);

  size_t data_size = dis(gen);
  large_data.resize(data_size + sizeof(uint32_t) + sizeof(uint16_t));
  std::generate(large_data.begin() + sizeof(uint32_t) + sizeof(uint16_t), large_data.end(),
                [&]() { return gen() % 256; });
  return data_size;
}

void producer() {
  ipc::route channel{kIpcRoute, ipc::sender};
  // waiting for connection
  channel.wait_for_recv(1);

  uint32_t data_size = create_large_data();
  uint16_t crc = calculate_crc({large_data.begin() + sizeof(uint32_t) + sizeof(uint16_t), large_data.end()});

  std::memcpy(large_data.data(), &data_size, sizeof(data_size));
  std::memcpy(large_data.data() + sizeof(data_size), &crc, sizeof(crc));

  channel.send(large_data.data(), large_data.size());
  std::printf("Producer sent large data of size %lu bytes (data_size: %u, crc: %hx)\n", large_data.size(), data_size,
              crc);
  // quit
  channel.send(ipc::buff_t('\0'));
}

void consumer(const char* consumer_name) {
  ipc::route channel{kIpcRoute, ipc::receiver};
  while (true) {
    auto buffer = channel.recv(1000);
    uint8_t* buffer_header = reinterpret_cast<uint8_t*>(buffer.data());
    const uint32_t buffer_size = buffer.size();

    if (buffer_size < sizeof(uint32_t)) {
      std::printf("[%s] received termination signal\n", consumer_name);
      return;
    }

    uint32_t received_length;
    std::memcpy(&received_length, buffer_header, sizeof(received_length));

    uint16_t received_crc;
    std::memcpy(&received_crc, buffer_header + sizeof(received_length), sizeof(received_crc));

    uint32_t data_length = buffer_size - sizeof(uint32_t) - sizeof(uint16_t);

    std::printf("[%s] buffer_size: %u, received_length: %u, data_length: %u, received_crc: %hx\n", consumer_name,
                buffer_size, received_length, data_length, received_crc);

    // 验证数据长度
    if (received_length!= data_length) {
      std::printf("[%s] data length exceeds buffer size, expected %u, got %u\n", consumer_name, received_length,
                  data_length);
      return;
    }

    // 创建一个临时的 std::vector 来持有数据部分
    std::vector<uint8_t> received_data(buffer_header + sizeof(uint32_t) + sizeof(uint16_t),
                                       buffer_header + sizeof(uint32_t) + sizeof(uint16_t) + received_length);

    // 创建一个 LargeDataBlock 仅用于计算 CRC
    uint16_t calculated_crc = calculate_crc(received_data);

    // 检查 CRC 是否匹配
    if (calculated_crc!= received_crc) {
      std::printf("[%s] CRC mismatch: expected %hx, got %hx\n", consumer_name, received_crc, calculated_crc);
      return;
    }

    std::printf("[%s] received data of size %u bytes with valid CRC\n", consumer_name, received_length);
  }
}

int main() {
  // thread producer
  std::thread producer_thread(producer);
  // thread consumer 1
  std::thread consumer_thread1(consumer, "consumer1");
  // thread consumer 2
  std::thread consumer_thread2(consumer, "consumer2");

  producer_thread.join();
  consumer_thread1.join();
  consumer_thread2.join();

  return 0;
}

这个示例展示了如何在传输二进制文件时附带 CRC 校验,以确保数据的完整性和准确性。

3.2 运行结果

test@t:~/ipc_route/build$./large_data_ipc 
Producer sent large data of size 3275850 bytes (data_size: 3275844, crc: f282)
[consumer2] buffer_size: 3275850, received_length: 3275844, data_length: 3275844, received_crc: f282
[consumer1] buffer_size: 3275850, received_length: 3275844, data_length: 3275844, received_crc: f282
[consumer2] received data of size 3275844 bytes with valid CRC
[consumer2] received termination signal
[consumer1] received data of size 3275844 bytes with valid CRC
[consumer1] received termination signal
test@t:~/ipc_route/build$./large_data_ipc 
Producer sent large data of size 2433129 bytes (data_size: 2433123, crc: c13a)
[consumer1] buffer_size: 2433129, received_length: 2433123, data_length: 2433123, received_crc: c13a
[consumer2] buffer_size: 2433129, received_length: 2433123, data_length: 2433123, received_crc: c13a
[consumer1] received data of size 2433123 bytes with valid CRC
[consumer1] received termination signal
[consumer2] received data of size 2433123 bytes with valid CRC
[consumer2] received termination signal

3.3 结构图

启动线程
启动线程
创建和发送数据
接收和验证数据
计算CRC
创建数据
Main
+main()
Producer
+producer()
Consumer
+consumer(const char*)
LargeDataBlock
uint32_t length
uint16_t crc
std::vector data
calculate_crc
+calculate_crc(const std::vector&)
create_large_data
+create_large_data()

3.4 主流程图

Main函数
启动Producer线程
启动Consumer1线程
启动Consumer2线程
Producer函数
Consumer函数
Consumer函数

3.5 Producer函数详细图

Producer函数
等待连接
创建IPC通道
创建大数据块并填充随机数据
计算大数据块的CRC
将数据大小和CRC值复制到数据块头部
通过IPC通道发送数据
发送终止信号
打印发送的信息

3.6 Consumer函数详细图

Consumer函数
进入循环接收数据
创建IPC通道
接收数据
数据大小小于uint32_t?
打印终止信号并退出
提取数据大小和CRC值
验证接收到的数据长度
创建临时std::vector保存数据部分
计算接收到数据的CRC值
检查CRC是否匹配?
打印接收的信息
打印CRC错误信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值