first commit
This commit is contained in:
commit
3342020ecf
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Xmake cache
|
||||||
|
.xmake/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# MacOS Cache
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
|
||||||
|
.vscode/
|
||||||
24
README.md
Normal file
24
README.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Node Communication
|
||||||
|
|
||||||
|
## 准备
|
||||||
|
|
||||||
|
```shell
|
||||||
|
xmake f -m release -cv
|
||||||
|
```
|
||||||
|
|
||||||
|
## 运行
|
||||||
|
|
||||||
|
### TCP
|
||||||
|
|
||||||
|
1. 接收端执行 `xmake run ipc_interface tcp receive`
|
||||||
|
1. 发送端执行 `xmake run ipc_interface tcp send --data test_data`
|
||||||
|
|
||||||
|
### Share Memory
|
||||||
|
|
||||||
|
1. 接收端执行 `xmake run ipc_interface shm receive`
|
||||||
|
1. 发送端执行 `xmake run ipc_interface shm send --data test_data`
|
||||||
|
|
||||||
|
### Unix Domain
|
||||||
|
|
||||||
|
1. 接收端执行 `xmake run ipc_interface unix_domain receive`
|
||||||
|
1. 发送端执行 `xmake run ipc_interface unix_domain send --data test_data`
|
||||||
31
include/context.h
Normal file
31
include/context.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "core.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class IpcRequestReplyContext {
|
||||||
|
private:
|
||||||
|
std::unique_ptr<IpcRequestReplyInterface> strategy;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setStrategy(std::unique_ptr<IpcRequestReplyInterface> newStrategy) {
|
||||||
|
strategy = std::move(newStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> send(const std::string& message) {
|
||||||
|
if (strategy) {
|
||||||
|
return strategy->send(message);
|
||||||
|
}
|
||||||
|
return std::unexpected("Not Found Strategy");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<std::string, std::string> receive() {
|
||||||
|
if (strategy) {
|
||||||
|
return strategy->receive();
|
||||||
|
}
|
||||||
|
return std::unexpected("Not Found Strategy");
|
||||||
|
}
|
||||||
|
};
|
||||||
33
include/core.h
Normal file
33
include/core.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <expected>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发布,订阅
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class IpcPubSubInterface {
|
||||||
|
public:
|
||||||
|
virtual ~IpcPubSubInterface() = default;
|
||||||
|
|
||||||
|
virtual std::expected<void, std::string> publish(
|
||||||
|
const std::string& topic, const std::string& message) = 0;
|
||||||
|
virtual std::expected<void, std::string> subscribe(
|
||||||
|
const std::string& topic) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 请求,回复
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class IpcRequestReplyInterface {
|
||||||
|
public:
|
||||||
|
virtual ~IpcRequestReplyInterface() = default;
|
||||||
|
|
||||||
|
virtual std::expected<void, std::string> send(const std::string& message) = 0;
|
||||||
|
virtual std::expected<std::string, std::string> receive() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 缓冲区大小
|
||||||
|
constexpr size_t BUFFER_SIZE = 4096;
|
||||||
36
include/shm.h
Normal file
36
include/shm.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 共享内存 请求,回复 实现
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class ShmRequestReply : public IpcRequestReplyInterface {
|
||||||
|
public:
|
||||||
|
ShmRequestReply();
|
||||||
|
~ShmRequestReply();
|
||||||
|
|
||||||
|
std::expected<std::string, std::string> receive() override;
|
||||||
|
std::expected<void, std::string> send(const std::string& message) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 共享内存 发布,订阅 实现
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class ShmPubSub : public IpcPubSubInterface {
|
||||||
|
public:
|
||||||
|
ShmPubSub();
|
||||||
|
~ShmPubSub();
|
||||||
|
|
||||||
|
std::expected<void, std::string> publish(const std::string& topic,
|
||||||
|
const std::string& message) override;
|
||||||
|
std::expected<void, std::string> subscribe(const std::string& topic) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 共享内存 请求,回复 地址
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
constexpr const char* SHM_REQUEST_REPLY_NAME = "/shm_req";
|
||||||
19
include/tcp.h
Normal file
19
include/tcp.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief TCP 请求与回复 实现
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class TcpRequestReply : public IpcRequestReplyInterface {
|
||||||
|
public:
|
||||||
|
TcpRequestReply();
|
||||||
|
~TcpRequestReply();
|
||||||
|
|
||||||
|
std::expected<std::string, std::string> receive() override;
|
||||||
|
std::expected<void, std::string> send(const std::string& message) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
constexpr int PORT = 12345;
|
||||||
|
constexpr const char* LOCALHOST = "127.0.0.1";
|
||||||
23
include/unix_domain.h
Normal file
23
include/unix_domain.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief unix-domain 请求,回复 实现
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class UnixDomainRequestReply : public IpcRequestReplyInterface {
|
||||||
|
public:
|
||||||
|
UnixDomainRequestReply();
|
||||||
|
~UnixDomainRequestReply();
|
||||||
|
|
||||||
|
std::expected<std::string, std::string> receive() override;
|
||||||
|
std::expected<void, std::string> send(const std::string& message) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// unix-domain 地址
|
||||||
|
constexpr const char* SOCKET_PATH = "/tmp/unix_domain_socket";
|
||||||
|
|
||||||
|
// 默认重试次数
|
||||||
|
const int RETRY_COUNT = 10;
|
||||||
|
// 重试间隔(毫秒)
|
||||||
|
const int RETRY_DELAY_MS = 500;
|
||||||
19
include/zmq_ipc.h
Normal file
19
include/zmq_ipc.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "core.h"
|
||||||
|
#include <zmq.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
class ZMQRequestReply : public IpcRequestReplyInterface {
|
||||||
|
public:
|
||||||
|
ZMQRequestReply();
|
||||||
|
~ZMQRequestReply();
|
||||||
|
|
||||||
|
std::expected<std::string, std::string> receive() override;
|
||||||
|
std::expected<void, std::string> send(const std::string& message) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
zmq::context_t ctx_;
|
||||||
|
zmq::socket_t receiver_;
|
||||||
|
zmq::socket_t sender_;
|
||||||
|
static constexpr const char* endpoint_ = "inproc://test";
|
||||||
|
};
|
||||||
0
src/core.cpp
Normal file
0
src/core.cpp
Normal file
87
src/main.cpp
Normal file
87
src/main.cpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#include <context.h>
|
||||||
|
#include <shm.h>
|
||||||
|
#include <tcp.h>
|
||||||
|
#include <unix_domain.h>
|
||||||
|
#include <zmq_ipc.h>
|
||||||
|
|
||||||
|
#include <argparse/argparse.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
#include <optional>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
int parse_cmd_args(int argc, char** argv, std::string& communication,
|
||||||
|
std::string& method, std::string& data) {
|
||||||
|
argparse::ArgumentParser program("IPC_Interface");
|
||||||
|
|
||||||
|
program.add_argument("communication")
|
||||||
|
.help("zmq or shm or unix_domain")
|
||||||
|
.store_into(communication);
|
||||||
|
|
||||||
|
program.add_argument("method").help("send or receive").store_into(method);
|
||||||
|
|
||||||
|
program.add_argument("--data").default_value(std::string("")).nargs(1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
program.parse_args(argc, argv);
|
||||||
|
} catch (const std::exception& err) {
|
||||||
|
std::cerr << err.what() << std::endl;
|
||||||
|
std::cerr << program;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method == "send") {
|
||||||
|
std::cout << method << std::endl;
|
||||||
|
}
|
||||||
|
data = program.get<std::string>("--data");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int run_main(int argc, char** argv) {
|
||||||
|
std::string communication;
|
||||||
|
std::string method;
|
||||||
|
std::string data;
|
||||||
|
|
||||||
|
if (auto res = parse_cmd_args(argc, argv, communication, method, data);
|
||||||
|
res != 0) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcRequestReplyContext context;
|
||||||
|
|
||||||
|
// 使用共享内存策略
|
||||||
|
if (communication == "zmq") {
|
||||||
|
context.setStrategy(std::make_unique<ZMQRequestReply>());
|
||||||
|
} else if (communication == "shm") {
|
||||||
|
context.setStrategy(std::make_unique<ShmRequestReply>());
|
||||||
|
} else if (communication == "unix_domain") {
|
||||||
|
context.setStrategy(std::make_unique<UnixDomainRequestReply>());
|
||||||
|
} else if (communication == "tcp") {
|
||||||
|
context.setStrategy(std::make_unique<TcpRequestReply>());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method == "send") {
|
||||||
|
if (!data.empty()) {
|
||||||
|
auto res = context.send(data);
|
||||||
|
if (!res.has_value()) {
|
||||||
|
std::cerr << "send error: " << res.error() << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
std::cout << "send success" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (method == "receive") {
|
||||||
|
auto read_msg = context.receive();
|
||||||
|
if (!read_msg.has_value()) {
|
||||||
|
std::cerr << "receive error: " << read_msg.error() << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
std::cout << "Received: " << *read_msg << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
return run_main(argc, argv);
|
||||||
|
// return 0;
|
||||||
|
}
|
||||||
161
src/shm.cpp
Normal file
161
src/shm.cpp
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <expected>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "shm.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Shm Request Reply:: Shm Request Reply object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
ShmRequestReply::ShmRequestReply() {
|
||||||
|
// 初始化共享内存(创建端或者连接端)
|
||||||
|
// 此处省略具体实现,可用 shm_open + mmap 等
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the Shm Request Reply:: Shm Request Reply object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
ShmRequestReply::~ShmRequestReply() {
|
||||||
|
// 清理共享内存资源
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 接收数据
|
||||||
|
*
|
||||||
|
* @return std::expected<std::string, std::string>
|
||||||
|
*/
|
||||||
|
std::expected<std::string, std::string> ShmRequestReply::receive() {
|
||||||
|
// 以只读方式打开请求共享内存区域
|
||||||
|
int fd = shm_open(SHM_REQUEST_REPLY_NAME, O_RDONLY, 0666);
|
||||||
|
if (fd == -1)
|
||||||
|
return std::unexpected("shm_open failed: " + std::string(strerror(errno)));
|
||||||
|
// 将共享内存映射到进程地址空间
|
||||||
|
void* ptr = mmap(nullptr, BUFFER_SIZE, PROT_READ, MAP_SHARED, fd, 0);
|
||||||
|
if (ptr == MAP_FAILED) return std::unexpected("mmap failed");
|
||||||
|
// 将共享内存内容转换为字符串
|
||||||
|
std::string data(static_cast<char*>(ptr));
|
||||||
|
// 清理资源:取消映射、关闭文件描述符
|
||||||
|
munmap(ptr, BUFFER_SIZE);
|
||||||
|
close(fd);
|
||||||
|
// 返回读取到的数据
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发送数据
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* @return std::expected<void, std::string>
|
||||||
|
*/
|
||||||
|
std::expected<void, std::string> ShmRequestReply::send(
|
||||||
|
const std::string& message) {
|
||||||
|
// 创建或打开响应共享内存区域(可读写)
|
||||||
|
int fd = shm_open(SHM_REQUEST_REPLY_NAME, O_CREAT | O_RDWR, 0666);
|
||||||
|
if (fd == -1)
|
||||||
|
return std::unexpected("shm_open failed: " + std::string(strerror(errno)));
|
||||||
|
// 调整共享内存大小
|
||||||
|
auto res = ftruncate(fd, BUFFER_SIZE);
|
||||||
|
if (res != 0) {
|
||||||
|
return std::unexpected("ftruncate failed: " + std::string(strerror(errno)));
|
||||||
|
}
|
||||||
|
// 将共享内存映射到进程地址空间(可写)
|
||||||
|
void* ptr = mmap(nullptr, BUFFER_SIZE, PROT_WRITE, MAP_SHARED, fd, 0);
|
||||||
|
if (ptr == MAP_FAILED) return std::unexpected("mmap failed");
|
||||||
|
// 清空共享内存区域
|
||||||
|
std::memset(ptr, 0, BUFFER_SIZE);
|
||||||
|
// 将消息内容拷贝到共享内存(确保不超过大小限制)
|
||||||
|
std::memcpy(ptr, message.data(), std::min(message.size(), BUFFER_SIZE - 1));
|
||||||
|
// 清理资源:取消映射、关闭文件描述符
|
||||||
|
munmap(ptr, BUFFER_SIZE);
|
||||||
|
close(fd);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 构造topic
|
||||||
|
*
|
||||||
|
* @param topic
|
||||||
|
* @return std::string
|
||||||
|
*/
|
||||||
|
std::string make_topic_name(const std::string& topic) {
|
||||||
|
return "/shm_topic_" + topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Shm Pub Sub:: Shm Pub Sub object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
ShmPubSub::ShmPubSub() {
|
||||||
|
// 初始化
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the Shm Pub Sub:: Shm Pub Sub object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
ShmPubSub::~ShmPubSub() {
|
||||||
|
// 清理
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发布
|
||||||
|
*
|
||||||
|
* @param topic
|
||||||
|
* @param message
|
||||||
|
* @return std::expected<void, std::string>
|
||||||
|
*/
|
||||||
|
std::expected<void, std::string> ShmPubSub::publish(
|
||||||
|
const std::string& topic, const std::string& message) {
|
||||||
|
auto shm_name = make_topic_name(topic);
|
||||||
|
// 创建或打开响应共享内存区域(可读写)
|
||||||
|
int fd = shm_open(shm_name.c_str(), O_CREAT | O_RDWR, 0666);
|
||||||
|
if (fd == -1)
|
||||||
|
return std::unexpected("shm_open failed: " + std::string(strerror(errno)));
|
||||||
|
// 调整共享内存大小
|
||||||
|
auto res = ftruncate(fd, BUFFER_SIZE);
|
||||||
|
if (res != 0) {
|
||||||
|
return std::unexpected("ftruncate failed: " + std::string(strerror(errno)));
|
||||||
|
}
|
||||||
|
// 将共享内存映射到进程地址空间(可写)
|
||||||
|
void* ptr = mmap(nullptr, BUFFER_SIZE, PROT_WRITE, MAP_SHARED, fd, 0);
|
||||||
|
if (ptr == MAP_FAILED) return std::unexpected("mmap failed");
|
||||||
|
// 清空共享内存区域
|
||||||
|
std::memset(ptr, 0, BUFFER_SIZE);
|
||||||
|
std::memcpy(ptr, message.data(), std::min(message.size(), BUFFER_SIZE - 1));
|
||||||
|
// 清理资源:取消映射、关闭文件描述符
|
||||||
|
munmap(ptr, BUFFER_SIZE);
|
||||||
|
close(fd);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 订阅
|
||||||
|
*
|
||||||
|
* @param topic
|
||||||
|
* @return std::expected<void, std::string>
|
||||||
|
*/
|
||||||
|
std::expected<void, std::string> ShmPubSub::subscribe(
|
||||||
|
const std::string& topic) {
|
||||||
|
auto shm_name = make_topic_name(topic);
|
||||||
|
// 以只读方式打开请求共享内存区域
|
||||||
|
int fd = shm_open(shm_name.c_str(), O_RDONLY, 0666);
|
||||||
|
if (fd == -1)
|
||||||
|
return std::unexpected("shm_open failed: " + std::string(strerror(errno)));
|
||||||
|
// 将共享内存映射到进程地址空间
|
||||||
|
void* ptr = mmap(nullptr, BUFFER_SIZE, PROT_READ, MAP_SHARED, fd, 0);
|
||||||
|
if (ptr == MAP_FAILED) return std::unexpected("mmap failed");
|
||||||
|
// 将共享内存内容转换为字符串
|
||||||
|
std::string data(static_cast<char*>(ptr));
|
||||||
|
std::cout << "Received [" << topic << "]: " << data << "\n";
|
||||||
|
// 清理资源:取消映射、关闭文件描述符
|
||||||
|
munmap(ptr, BUFFER_SIZE);
|
||||||
|
close(fd);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
91
src/tcp.cpp
Normal file
91
src/tcp.cpp
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <expected>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "tcp.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TcpRequestReply::TcpRequestReply() {}
|
||||||
|
|
||||||
|
TcpRequestReply::~TcpRequestReply() {}
|
||||||
|
|
||||||
|
std::expected<std::string, std::string> TcpRequestReply::receive() {
|
||||||
|
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (server_fd < 0) {
|
||||||
|
return std::unexpected("socket() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr_in server_addr{};
|
||||||
|
server_addr.sin_family = AF_INET;
|
||||||
|
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
server_addr.sin_port = htons(PORT);
|
||||||
|
|
||||||
|
int opt = 1;
|
||||||
|
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||||
|
|
||||||
|
if (bind(server_fd, reinterpret_cast<sockaddr*>(&server_addr),
|
||||||
|
sizeof(server_addr)) < 0) {
|
||||||
|
close(server_fd);
|
||||||
|
return std::unexpected("bind() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen(server_fd, 1) < 0) {
|
||||||
|
close(server_fd);
|
||||||
|
return std::unexpected("listen() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
int client_fd = accept(server_fd, nullptr, nullptr);
|
||||||
|
if (client_fd < 0) {
|
||||||
|
close(server_fd);
|
||||||
|
return std::unexpected("accept() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[BUFFER_SIZE] = {0};
|
||||||
|
ssize_t bytes_read = read(client_fd, buffer, BUFFER_SIZE);
|
||||||
|
if (bytes_read < 0) {
|
||||||
|
close(client_fd);
|
||||||
|
close(server_fd);
|
||||||
|
return std::unexpected("read() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
close(client_fd);
|
||||||
|
close(server_fd);
|
||||||
|
return std::string(buffer, static_cast<size_t>(bytes_read));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> TcpRequestReply::send(
|
||||||
|
const std::string& message) {
|
||||||
|
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (sock < 0) {
|
||||||
|
return std::unexpected("socket() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr_in server_addr{};
|
||||||
|
server_addr.sin_family = AF_INET;
|
||||||
|
server_addr.sin_port = htons(PORT);
|
||||||
|
|
||||||
|
if (inet_pton(AF_INET, LOCALHOST, &server_addr.sin_addr) <= 0) {
|
||||||
|
close(sock);
|
||||||
|
return std::unexpected("inet_pton() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connect(sock, reinterpret_cast<sockaddr*>(&server_addr),
|
||||||
|
sizeof(server_addr)) < 0) {
|
||||||
|
close(sock);
|
||||||
|
return std::unexpected("connect() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (write(sock, message.c_str(), message.size()) < 0) {
|
||||||
|
close(sock);
|
||||||
|
return std::unexpected("write() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
close(sock);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
139
src/unix_domain.cpp
Normal file
139
src/unix_domain.cpp
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstring>
|
||||||
|
#include <expected>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "unix_domain.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Unix Domain Request Reply:: Unix Domain Request Reply
|
||||||
|
* object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UnixDomainRequestReply::UnixDomainRequestReply() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the Unix Domain Request Reply:: Unix Domain Request Reply
|
||||||
|
* object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UnixDomainRequestReply::~UnixDomainRequestReply() {
|
||||||
|
unlink(SOCKET_PATH); // 清理 socket 文件
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 接收消息
|
||||||
|
*
|
||||||
|
* @return std::expected<std::string, std::string>
|
||||||
|
*/
|
||||||
|
std::expected<std::string, std::string> UnixDomainRequestReply::receive() {
|
||||||
|
// 1. 创建UNIX域流式socket
|
||||||
|
int server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (server_fd < 0) {
|
||||||
|
return std::unexpected("创建socket失败: " + std::string(strerror(errno)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 绑定socket到指定路径
|
||||||
|
sockaddr_un addr{};
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
|
||||||
|
|
||||||
|
// 确保之前的socket文件被删除
|
||||||
|
unlink(SOCKET_PATH);
|
||||||
|
|
||||||
|
if (bind(server_fd, (sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||||
|
close(server_fd);
|
||||||
|
return std::unexpected("绑定socket失败: " + std::string(strerror(errno)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 开始监听连接(队列长度为1)
|
||||||
|
if (listen(server_fd, 1) < 0) {
|
||||||
|
close(server_fd);
|
||||||
|
return std::unexpected("监听失败: " + std::string(strerror(errno)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 接受客户端连接
|
||||||
|
int client_fd = accept(server_fd, nullptr, nullptr);
|
||||||
|
if (client_fd < 0) {
|
||||||
|
close(server_fd);
|
||||||
|
return std::unexpected("接受连接失败: " + std::string(strerror(errno)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 读取客户端数据
|
||||||
|
char buffer[BUFFER_SIZE] = {0};
|
||||||
|
ssize_t bytes_read = read(client_fd, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
// 6. 关闭连接(使用RAII会更好)
|
||||||
|
close(client_fd);
|
||||||
|
close(server_fd);
|
||||||
|
|
||||||
|
if (bytes_read < 0) {
|
||||||
|
return std::unexpected("读取数据失败: " + std::string(strerror(errno)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::string(buffer, bytes_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发送消息(一般要先接收,在发送,这里做了轮询尝试,可以将他反过来)
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* @return std::expected<void, std::string>
|
||||||
|
*/
|
||||||
|
std::expected<void, std::string> UnixDomainRequestReply::send(
|
||||||
|
const std::string& message) {
|
||||||
|
int sock = -1;
|
||||||
|
|
||||||
|
// 1. 创建socket(放在循环外避免重复创建)
|
||||||
|
sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (sock < 0) {
|
||||||
|
return std::unexpected("创建socket失败: " + std::string(strerror(errno)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 准备地址结构
|
||||||
|
sockaddr_un addr{};
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
|
||||||
|
|
||||||
|
// 3. 带重试的连接逻辑
|
||||||
|
bool connected = false;
|
||||||
|
for (int attempt = 0; attempt < RETRY_COUNT; ++attempt) {
|
||||||
|
if (connect(sock, (sockaddr*)&addr, sizeof(addr)) == 0) {
|
||||||
|
connected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最后一次尝试不等待
|
||||||
|
if (attempt < RETRY_COUNT - 1) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(RETRY_DELAY_MS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connected) {
|
||||||
|
close(sock);
|
||||||
|
return std::unexpected("连接服务端失败: " + std::string(strerror(errno)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 设置发送超时
|
||||||
|
timeval timeout{.tv_sec = 5, .tv_usec = 0};
|
||||||
|
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout))) {
|
||||||
|
close(sock);
|
||||||
|
return std::unexpected("设置超时失败: " + std::string(strerror(errno)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 发送数据
|
||||||
|
ssize_t bytes_sent = write(sock, message.c_str(), message.size());
|
||||||
|
if (bytes_sent < 0) {
|
||||||
|
close(sock);
|
||||||
|
return std::unexpected("发送数据失败: " + std::string(strerror(errno)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 关闭连接
|
||||||
|
close(sock);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
27
src/zmq_ipc.cpp
Normal file
27
src/zmq_ipc.cpp
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
|
||||||
|
#include "zmq_ipc.h"
|
||||||
|
|
||||||
|
ZMQRequestReply::ZMQRequestReply()
|
||||||
|
: ctx_(1), // 1个IO线程
|
||||||
|
receiver_(ctx_, zmq::socket_type::pull),
|
||||||
|
sender_(ctx_, zmq::socket_type::push) {
|
||||||
|
// 绑定接收端
|
||||||
|
receiver_.bind(endpoint_);
|
||||||
|
// 连接发送端
|
||||||
|
sender_.connect(endpoint_);
|
||||||
|
}
|
||||||
|
|
||||||
|
ZMQRequestReply::~ZMQRequestReply() {}
|
||||||
|
|
||||||
|
std::expected<std::string, std::string> ZMQRequestReply::receive() {
|
||||||
|
zmq::message_t msg;
|
||||||
|
auto res = receiver_.recv(msg, zmq::recv_flags::none);
|
||||||
|
return std::string(msg.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> ZMQRequestReply::send(
|
||||||
|
const std::string& message) {
|
||||||
|
sender_.send(zmq::buffer(message), zmq::send_flags::none);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
12
tests/test_shm.cpp
Normal file
12
tests/test_shm.cpp
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <utils.hpp>
|
||||||
|
#include <shm.h>
|
||||||
|
|
||||||
|
|
||||||
|
INSTANTIATE_TYPED_TEST_SUITE_P(ShmIPCTest, IPCTest, ShmRequestReply);
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
11
tests/test_unix_domain.cpp
Normal file
11
tests/test_unix_domain.cpp
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <unix_domain.h>
|
||||||
|
|
||||||
|
#include <utils.hpp>
|
||||||
|
|
||||||
|
INSTANTIATE_TYPED_TEST_SUITE_P(UnixDomainTest, IPCTest, UnixDomainRequestReply);
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
12
tests/test_zmq.cpp
Normal file
12
tests/test_zmq.cpp
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <utils.hpp>
|
||||||
|
#include <zmq_ipc.h>
|
||||||
|
|
||||||
|
|
||||||
|
INSTANTIATE_TYPED_TEST_SUITE_P(ZMQIPCTest, IPCTest,ZMQRequestReply);
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
48
tests/utils.hpp
Normal file
48
tests/utils.hpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#include <context.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <shm.h>
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
class IPCTest : public testing::Test {
|
||||||
|
public:
|
||||||
|
void send(auto msg) {
|
||||||
|
IpcRequestReplyContext ctx;
|
||||||
|
|
||||||
|
ctx.setStrategy(std::make_unique<T>());
|
||||||
|
auto res = ctx.send(msg);
|
||||||
|
if (!res) FAIL() << "send failed!" << res.error();
|
||||||
|
}
|
||||||
|
|
||||||
|
void receive(auto msg) {
|
||||||
|
IpcRequestReplyContext ctx;
|
||||||
|
|
||||||
|
ctx.setStrategy(std::make_unique<T>());
|
||||||
|
auto res = ctx.receive();
|
||||||
|
if (!res) FAIL() << "receive failed!" << res.error();
|
||||||
|
EXPECT_EQ(res, msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// +++++++++++++++++++++++++++++++++ Test +++++++++++++++++++++++++++++++++
|
||||||
|
TYPED_TEST_SUITE_P(IPCTest);
|
||||||
|
// 单线程发送接收
|
||||||
|
TYPED_TEST_P(IPCTest, SendAndReceiveTest) {
|
||||||
|
auto msg = "Hello IPC";
|
||||||
|
this->send(msg);
|
||||||
|
this->receive(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多线程发送接收
|
||||||
|
TYPED_TEST_P(IPCTest, MutliThreadSendAndReceiveTest) {
|
||||||
|
auto msg = "Hello IPC";
|
||||||
|
std::thread send_t([this, msg]() { this->send(msg); });
|
||||||
|
std::thread receive_t([this, msg]() { this->receive(msg); });
|
||||||
|
|
||||||
|
send_t.join();
|
||||||
|
receive_t.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_TYPED_TEST_SUITE_P(IPCTest, SendAndReceiveTest,
|
||||||
|
MutliThreadSendAndReceiveTest);
|
||||||
104
xmake.lua
Normal file
104
xmake.lua
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
add_rules("mode.debug", "mode.release")
|
||||||
|
|
||||||
|
-- c++ 23 标准
|
||||||
|
add_languages("c++23")
|
||||||
|
-- 头文件目录
|
||||||
|
add_includedirs("include")
|
||||||
|
-- GoogleTest
|
||||||
|
add_requires("gtest")
|
||||||
|
-- cpp-httplib
|
||||||
|
add_requires("cpp-httplib")
|
||||||
|
-- zmq
|
||||||
|
add_requires("cppzmq")
|
||||||
|
-- argparse
|
||||||
|
add_requires("argparse")
|
||||||
|
|
||||||
|
target("ipc_interface")
|
||||||
|
set_kind("binary")
|
||||||
|
add_files("src/*.cpp")
|
||||||
|
add_packages("cpp-httplib")
|
||||||
|
add_packages("cppzmq")
|
||||||
|
add_packages("argparse")
|
||||||
|
|
||||||
|
|
||||||
|
for _, file in ipairs(os.files("tests/test_*.cpp")) do
|
||||||
|
local name = path.basename(file)
|
||||||
|
target(name)
|
||||||
|
set_kind("binary")
|
||||||
|
add_packages("gtest")
|
||||||
|
add_packages("cpp-httplib")
|
||||||
|
add_packages("cppzmq")
|
||||||
|
set_default(false)
|
||||||
|
add_includedirs("tests")
|
||||||
|
add_files(file,"src/*.cpp|main.cpp")
|
||||||
|
add_tests("default")
|
||||||
|
end
|
||||||
|
--
|
||||||
|
-- If you want to known more usage about xmake, please see https://xmake.io
|
||||||
|
--
|
||||||
|
-- ## FAQ
|
||||||
|
--
|
||||||
|
-- You can enter the project directory firstly before building project.
|
||||||
|
--
|
||||||
|
-- $ cd projectdir
|
||||||
|
--
|
||||||
|
-- 1. How to build project?
|
||||||
|
--
|
||||||
|
-- $ xmake
|
||||||
|
--
|
||||||
|
-- 2. How to configure project?
|
||||||
|
--
|
||||||
|
-- $ xmake f -p [macosx|linux|iphoneos ..] -a [x86_64|i386|arm64 ..] -m [debug|release]
|
||||||
|
--
|
||||||
|
-- 3. Where is the build output directory?
|
||||||
|
--
|
||||||
|
-- The default output directory is `./build` and you can configure the output directory.
|
||||||
|
--
|
||||||
|
-- $ xmake f -o outputdir
|
||||||
|
-- $ xmake
|
||||||
|
--
|
||||||
|
-- 4. How to run and debug target after building project?
|
||||||
|
--
|
||||||
|
-- $ xmake run [targetname]
|
||||||
|
-- $ xmake run -d [targetname]
|
||||||
|
--
|
||||||
|
-- 5. How to install target to the system directory or other output directory?
|
||||||
|
--
|
||||||
|
-- $ xmake install
|
||||||
|
-- $ xmake install -o installdir
|
||||||
|
--
|
||||||
|
-- 6. Add some frequently-used compilation flags in xmake.lua
|
||||||
|
--
|
||||||
|
-- @code
|
||||||
|
-- -- add debug and release modes
|
||||||
|
-- add_rules("mode.debug", "mode.release")
|
||||||
|
--
|
||||||
|
-- -- add macro definition
|
||||||
|
-- add_defines("NDEBUG", "_GNU_SOURCE=1")
|
||||||
|
--
|
||||||
|
-- -- set warning all as error
|
||||||
|
-- set_warnings("all", "error")
|
||||||
|
--
|
||||||
|
-- -- set language: c99, c++11
|
||||||
|
-- set_languages("c99", "c++11")
|
||||||
|
--
|
||||||
|
-- -- set optimization: none, faster, fastest, smallest
|
||||||
|
-- set_optimize("fastest")
|
||||||
|
--
|
||||||
|
-- -- add include search directories
|
||||||
|
-- add_includedirs("/usr/include", "/usr/local/include")
|
||||||
|
--
|
||||||
|
-- -- add link libraries and search directories
|
||||||
|
-- add_links("tbox")
|
||||||
|
-- add_linkdirs("/usr/local/lib", "/usr/lib")
|
||||||
|
--
|
||||||
|
-- -- add system link libraries
|
||||||
|
-- add_syslinks("z", "pthread")
|
||||||
|
--
|
||||||
|
-- -- add compilation and link flags
|
||||||
|
-- add_cxflags("-stdnolib", "-fno-strict-aliasing")
|
||||||
|
-- add_ldflags("-L/usr/local/lib", "-lpthread", {force = true})
|
||||||
|
--
|
||||||
|
-- @endcode
|
||||||
|
--
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user