文章目录
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