add old lci module
This commit is contained in:
1 parent
38a6e815f0
commit
8499631091
171 files changed
+25562
No files matched your search
@@ -0,0 +1,145 @@
|
||||
#include "Crypto_Basic.hpp"
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
#define CRYPTO_CHECK(condition, message) \
|
||||
if (!(condition)) { \
|
||||
throw std::runtime_error(message); \
|
||||
}
|
||||
namespace qing {
|
||||
class AES_CBC: public qing::Crypto_Basic {
|
||||
public:
|
||||
/* 构造函数 初始化一个随即的密钥和初始化向量 */
|
||||
AES_CBC() {
|
||||
generate_key_iv(key, iv);
|
||||
}
|
||||
AES_CBC(std::vector<unsigned char> &key, std::vector<unsigned char> &iv) {
|
||||
this->key = key;
|
||||
this->iv = iv;
|
||||
}
|
||||
std::string get_key() {
|
||||
return bytes_to_hex(key);
|
||||
}
|
||||
std::string get_iv() {
|
||||
return bytes_to_hex(iv);
|
||||
}
|
||||
|
||||
std::string encrypt(std::string original_text) {
|
||||
std::vector<unsigned char> plaintext(original_text.begin(), original_text.end());
|
||||
std::vector<unsigned char> ciphertext = aes_encrypt(plaintext, key, iv);
|
||||
|
||||
// hex-bytes转换测试
|
||||
/*std::string hex = bytes_to_hex(ciphertext);
|
||||
std::vector<unsigned char> cipher2 = hex_to_bytes(hex);
|
||||
print_bytes(ciphertext);
|
||||
print_bytes(cipher2);*/
|
||||
|
||||
return bytes_to_hex(ciphertext);
|
||||
}
|
||||
|
||||
std::string decrypt(std::string cipher_hex) {
|
||||
std::vector<unsigned char> ciphertext = hex_to_bytes(cipher_hex);
|
||||
std::vector<unsigned char> decrypted = aes_decrypt(ciphertext, key, iv);
|
||||
return std::string(decrypted.begin(), decrypted.end());
|
||||
}
|
||||
|
||||
static void print_bytes(std::vector<unsigned char>& bytes) {
|
||||
for (unsigned char byte : bytes) {
|
||||
std::cerr << std::setw(2) << static_cast<int>(byte);
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<unsigned char> key;
|
||||
std::vector<unsigned char> iv;
|
||||
// 生成随机密钥和IV
|
||||
static void generate_key_iv(std::vector<unsigned char>& key,
|
||||
std::vector<unsigned char>& iv) {
|
||||
key.resize(32); // AES-256 需要 32 字节密钥
|
||||
iv.resize(16); // AES 块大小 16 字节(128-bit)
|
||||
|
||||
// 生成随即密钥
|
||||
CRYPTO_CHECK(RAND_bytes(key.data(), key.size()) == 1, "密钥生成失败");
|
||||
|
||||
// 生成随即IV
|
||||
CRYPTO_CHECK(RAND_bytes(iv.data(), iv.size()) == 1, "IV生成失败");
|
||||
}
|
||||
|
||||
// AES-256-CBC 加密
|
||||
static std::vector<unsigned char> aes_encrypt(const std::vector<unsigned char>& plaintext,
|
||||
const std::vector<unsigned char>& key,
|
||||
const std::vector<unsigned char>& iv) {
|
||||
// 创建上下文
|
||||
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
|
||||
CRYPTO_CHECK(ctx != nullptr, "无法创建加密上下文");
|
||||
|
||||
// 初始化加密操作
|
||||
CRYPTO_CHECK(EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr,
|
||||
key.data(), iv.data()) , "加密初始化失败");
|
||||
|
||||
// 输出缓冲区 (密文可能比明文长一个块)
|
||||
std::vector<unsigned char> ciphertext(plaintext.size() + EVP_CIPHER_CTX_block_size(ctx));
|
||||
int len = 0;
|
||||
int ciphertext_len = 0;
|
||||
|
||||
// 提供明文数据
|
||||
CRYPTO_CHECK(EVP_EncryptUpdate(ctx, ciphertext.data(), &len,
|
||||
plaintext.data(), plaintext.size()) == 1, "加载更新失败");
|
||||
ciphertext_len = len;
|
||||
|
||||
// 完成加密(处理填充)
|
||||
CRYPTO_CHECK(EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len) == 1, "加密结束失败");
|
||||
ciphertext_len += len;
|
||||
|
||||
// 调整到实际密文长度
|
||||
ciphertext.resize(ciphertext_len);
|
||||
|
||||
// 处理上下文
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
return ciphertext;
|
||||
|
||||
}
|
||||
|
||||
// AES-256-CBC
|
||||
static std::vector<unsigned char> aes_decrypt(const std::vector<unsigned char>& ciphertext,
|
||||
const std::vector<unsigned char>& key,
|
||||
const std::vector<unsigned char>& iv) {
|
||||
// 创建上下文
|
||||
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
|
||||
CRYPTO_CHECK(ctx != nullptr, "无法创建解密上下文");
|
||||
|
||||
// 初始化解密操作
|
||||
CRYPTO_CHECK(EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr,
|
||||
key.data(), iv.data()) == 1, "解密初始化失败");
|
||||
|
||||
// 输出缓冲区(明文可能比密文短,但最大为密文长度)
|
||||
std::vector<unsigned char> plaintext(ciphertext.size());
|
||||
int len = 0;
|
||||
int plaintext_len = 0;
|
||||
|
||||
// 提供密文数据
|
||||
CRYPTO_CHECK(EVP_DecryptUpdate(ctx, plaintext.data(), &len,
|
||||
ciphertext.data(), ciphertext.size()) == 1, "解密更新失败");
|
||||
plaintext_len = len;
|
||||
|
||||
// 完成解密(处理填充)
|
||||
CRYPTO_CHECK(EVP_DecryptFinal_ex(ctx, plaintext.data() + len, &len) == 1, "解密结束失败");
|
||||
plaintext_len += len;
|
||||
|
||||
// 调整到实际明文长度
|
||||
plaintext.resize(plaintext_len);
|
||||
|
||||
// 清理上下文
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
namespace qing {
|
||||
class Crypto_Basic {
|
||||
public:
|
||||
class CryptoExp: public std::runtime_error {
|
||||
public:
|
||||
CryptoExp(const std::string& msg): std::runtime_error(msg) {
|
||||
;
|
||||
}
|
||||
};
|
||||
protected:
|
||||
/* 字节码转十六进制字符串 */
|
||||
static std::string bytes_to_hex(const std::vector<unsigned char>& data) {
|
||||
static const char hex_chars[] = "0123456789abcdef";
|
||||
std::string hex_string;
|
||||
hex_string.reserve(data.size() * 2);
|
||||
|
||||
for (unsigned char byte: data) {
|
||||
hex_string.push_back(hex_chars[byte >> 4]); // 高4位
|
||||
hex_string.push_back(hex_chars[byte & 0x0F]); // 低4位
|
||||
}
|
||||
|
||||
return hex_string;
|
||||
}
|
||||
|
||||
/* 十六进制字符串转字节码 */
|
||||
static std::vector<unsigned char> hex_to_bytes(const std::string &hex) {
|
||||
// 检查长度是否为偶数
|
||||
if (hex.size() % 2 != 0) {
|
||||
throw std::invalid_argument("十六进制字符串长度必须为偶数");
|
||||
}
|
||||
|
||||
std::vector<unsigned char> bytes;
|
||||
bytes.reserve(hex.size() / 2);
|
||||
|
||||
auto char_to_nibble = [](char c) -> unsigned char {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'a' && c <= 'f') return 10 + (c - 'a');
|
||||
if (c >= 'A' && c <= 'F') return 10 + (c - 'A');
|
||||
throw std::invalid_argument("无效的十六进制字符:" + std::string(1, c));
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < hex.size(); i += 2) {
|
||||
unsigned char high = char_to_nibble(hex[i]);
|
||||
unsigned char low = char_to_nibble(hex[i + 1]);
|
||||
bytes.push_back((high << 4) | low);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
#pragma once
|
||||
#include "Crypto_Basic.hpp"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/core_names.h>
|
||||
// 错误处理宏
|
||||
#define HANDLE_SSL_ERROR() { \
|
||||
char err_buf[512]; \
|
||||
ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)); \
|
||||
std::cerr << "SSL error: " << err_buf << std::endl; \
|
||||
}
|
||||
// 自定义删除器用于管理OpenSSL资源
|
||||
struct EVP_PKEY_deleter {
|
||||
void operator()(EVP_PKEY* pkey) const { EVP_PKEY_free(pkey); }
|
||||
};
|
||||
using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY, EVP_PKEY_deleter>;
|
||||
|
||||
struct EVP_PKEY_CTX_deleter {
|
||||
void operator()(EVP_PKEY_CTX* ctx) const { EVP_PKEY_CTX_free(ctx); }
|
||||
};
|
||||
using EVP_PKEY_CTX_ptr = std::unique_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_deleter>;
|
||||
|
||||
struct BIO_deleter {
|
||||
void operator()(BIO* bio) const { BIO_free_all(bio); }
|
||||
};
|
||||
using BIO_ptr = std::unique_ptr<BIO, BIO_deleter>;
|
||||
|
||||
namespace qing {
|
||||
class MyRSA: public qing::Crypto_Basic {
|
||||
public:
|
||||
class Generator {
|
||||
public:
|
||||
Generator() {
|
||||
key = generate_rsa_key();
|
||||
}
|
||||
void save_pri_key(const std::string filename) {
|
||||
if (!save_private_key(key.get(), filename)) {
|
||||
throw Crypto_Basic::CryptoExp("保存私钥失败。");
|
||||
}
|
||||
}
|
||||
void save_pub_key(const std::string filename) {
|
||||
if(!save_public_key(key.get(), filename)) {
|
||||
throw Crypto_Basic::CryptoExp("保存公钥失败。");
|
||||
}
|
||||
}
|
||||
private:
|
||||
EVP_PKEY_ptr key;
|
||||
// 生成 RSA 密钥对
|
||||
|
||||
static EVP_PKEY_ptr generate_rsa_key(size_t key_bits = 2048) {
|
||||
EVP_PKEY_CTX_ptr ctx(EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr));
|
||||
if (!ctx) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return nullptr;
|
||||
}
|
||||
if (EVP_PKEY_keygen_init(ctx.get()) <= 0) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 设置 RSA 密钥长度
|
||||
OSSL_PARAM params[] = {
|
||||
OSSL_PARAM_construct_size_t(OSSL_PKEY_PARAM_RSA_BITS, &key_bits),
|
||||
OSSL_PARAM_END
|
||||
};
|
||||
|
||||
if (EVP_PKEY_CTX_set_params(ctx.get(), params) <= 0) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EVP_PKEY* pkey = nullptr;
|
||||
if (EVP_PKEY_generate(ctx.get(), &pkey) <= 0) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return EVP_PKEY_ptr(pkey);
|
||||
}
|
||||
|
||||
// 保存私钥到文件
|
||||
static bool save_private_key(EVP_PKEY* pkey, const std::string& filename,
|
||||
const char* password = nullptr) {
|
||||
BIO_ptr bio(BIO_new_file(filename.c_str(), "wb"));
|
||||
if (!bio) {
|
||||
std::cerr << "Failed to open file: " << filename << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 选择加密算法
|
||||
const EVP_CIPHER* cipher = password ? EVP_aes_256_cbc() : nullptr;
|
||||
|
||||
int ret = PEM_write_bio_PrivateKey(bio.get(), pkey, cipher,
|
||||
(unsigned char*) password,
|
||||
password ? strlen(password) : 0,
|
||||
nullptr, nullptr);
|
||||
|
||||
if (ret != 1) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 保存公钥到文件
|
||||
static bool save_public_key(EVP_PKEY* pkey, const std::string& filename) {
|
||||
BIO_ptr bio(BIO_new_file(filename.c_str(), "wb"));
|
||||
if (!bio) {
|
||||
std::cerr << "Failed ot open file: " << filename << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = PEM_write_bio_PUBKEY(bio.get(), pkey);
|
||||
if (ret != 1) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class Private_Key: Crypto_Basic {
|
||||
public:
|
||||
Private_Key(const std::string fullpath) {
|
||||
if (!(pri_key = load_private_key(fullpath)))
|
||||
throw Crypto_Basic::CryptoExp("读取私钥失败");
|
||||
}
|
||||
std::string Decrypt(const std::string cipher_hex) {
|
||||
std::vector<unsigned char> ciphertext = hex_to_bytes(cipher_hex);
|
||||
std::vector<unsigned char> decrypted = rsa_decrypt(pri_key.get(), ciphertext);
|
||||
if (decrypted.empty()) throw Crypto_Basic::CryptoExp("解密失败");
|
||||
return std::string(decrypted.begin(), decrypted.end());
|
||||
}
|
||||
private:
|
||||
EVP_PKEY_ptr pri_key;
|
||||
// 从文件加载私钥
|
||||
EVP_PKEY_ptr load_private_key(const std::string& filename,
|
||||
const char* password = nullptr) {
|
||||
BIO_ptr bio(BIO_new_file(filename.c_str(), "rb"));
|
||||
if (!bio) {
|
||||
std::cerr << "Failed to open file: " << filename << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 密码回调函数,显示转换为函数指针类型
|
||||
pem_password_cb* callback = +[] (char *buf, int size, int rwflag, void * u) {
|
||||
if (!u) return 0;
|
||||
const char* pass = (const char*)u;
|
||||
int len = strlen(pass);
|
||||
if (len > size) len = size;
|
||||
memcpy(buf, pass, len);
|
||||
return len;
|
||||
};
|
||||
pem_password_cb* cb = password ? nullptr : callback;
|
||||
|
||||
EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio.get(), nullptr, cb, (void*)password);
|
||||
if (!pkey) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return EVP_PKEY_ptr(pkey);
|
||||
}
|
||||
|
||||
// 使用私钥解密数据
|
||||
static std::vector<unsigned char> rsa_decrypt(EVP_PKEY* private_key,
|
||||
const std::vector<unsigned char>& ciphertext) {
|
||||
EVP_PKEY_CTX_ptr ctx(EVP_PKEY_CTX_new(private_key, nullptr));
|
||||
if (!ctx) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (EVP_PKEY_decrypt_init(ctx.get()) <= 0) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return {};
|
||||
}
|
||||
|
||||
// 使用 OAEP 填充
|
||||
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return {};
|
||||
}
|
||||
|
||||
// 获取输出缓冲区大小
|
||||
size_t outlen = 0;
|
||||
if (EVP_PKEY_decrypt(ctx.get(), nullptr, &outlen,
|
||||
ciphertext.data(), ciphertext.size()) <= 0) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return {};
|
||||
}
|
||||
|
||||
// 执行解密
|
||||
std::vector<unsigned char> plaintext(outlen);
|
||||
if (EVP_PKEY_decrypt(ctx.get(), plaintext.data(), &outlen,
|
||||
ciphertext.data(), ciphertext.size()) <= 0) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return {};
|
||||
}
|
||||
|
||||
plaintext.resize(outlen);
|
||||
return plaintext;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class Public_Key: public Crypto_Basic {
|
||||
public:
|
||||
Public_Key(const std::string filename) {
|
||||
if (!(pub_key = load_public_key(filename)))
|
||||
throw Crypto_Basic::CryptoExp("读取公钥失败");
|
||||
}
|
||||
std::string Encrypt(std::string original_text) {
|
||||
std::vector<unsigned char> plaintext(original_text.begin(), original_text.end());
|
||||
std::vector<unsigned char> ciphertext = rsa_encrypt(pub_key.get(), plaintext);
|
||||
if (ciphertext.empty()) throw Crypto_Basic::CryptoExp("加密失败");
|
||||
return bytes_to_hex(ciphertext);
|
||||
}
|
||||
private:
|
||||
EVP_PKEY_ptr pub_key;
|
||||
|
||||
// 从文件加载公钥
|
||||
static EVP_PKEY_ptr load_public_key(const std::string& filename) {
|
||||
BIO_ptr bio(BIO_new_file(filename.c_str(), "rb"));
|
||||
if (!bio) {
|
||||
std::cerr << "Failed to open file: " << filename << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EVP_PKEY* pkey = PEM_read_bio_PUBKEY(bio.get(), nullptr, nullptr, nullptr);
|
||||
if (!pkey) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return EVP_PKEY_ptr(pkey);
|
||||
}
|
||||
|
||||
// 使用公钥加密数据
|
||||
static std::vector<unsigned char> rsa_encrypt(EVP_PKEY* public_key,
|
||||
const std::vector<unsigned char>& plaintext) {
|
||||
EVP_PKEY_CTX_ptr ctx(EVP_PKEY_CTX_new(public_key, nullptr));
|
||||
if (!ctx) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (EVP_PKEY_encrypt_init(ctx.get()) <= 0) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return {};
|
||||
}
|
||||
|
||||
// 使用 OAEP 填充
|
||||
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return {};
|
||||
}
|
||||
|
||||
// 获取输出缓冲区大小
|
||||
size_t outlen =0 ;
|
||||
if (EVP_PKEY_encrypt(ctx.get(), nullptr, &outlen,
|
||||
plaintext.data(), plaintext.size()) <= 0) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return {};
|
||||
}
|
||||
|
||||
// 执行加密
|
||||
std::vector<unsigned char> ciphertext(outlen);
|
||||
if (EVP_PKEY_encrypt(ctx.get(), ciphertext.data(), &outlen,
|
||||
plaintext.data(), plaintext.size()) <= 0) {
|
||||
HANDLE_SSL_ERROR();
|
||||
return {};
|
||||
}
|
||||
|
||||
ciphertext.resize(outlen);
|
||||
return ciphertext;
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include "nn/Matrix.hh"
|
||||
#include "nn/ILayer.hpp"
|
||||
#include "nn/ILearnable.hpp"
|
||||
#include "nn/ILayerFullyConnectted.hpp"
|
||||
#include "nn/ILayerConvolutional.hpp"
|
||||
#include "nn/NeuralNetwork.hpp"
|
||||
#include "nn/ConvLayer.hpp"
|
||||
#include "nn/PoolingLayer.hpp"
|
||||
#include "nn/FlattenLayer.hpp"
|
||||
#include "nn/NNBuilder.hpp"
|
||||
#include "nn/CNNBuilder.hpp"
|
||||
#include "nn/ImageLoader.hpp"
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
/*
|
||||
* # 安装工具
|
||||
* apt install i2c-tools libi2c-dev
|
||||
*
|
||||
* # 扫描设备
|
||||
* i2cdetect -y 1
|
||||
*/
|
||||
#include "cn/中文化.hpp"
|
||||
|
||||
/* 基本I2C */
|
||||
逻辑型 I2C_检查();
|
||||
空类型 I2C_初始化(常量 字符* 文件路径 = "/dev/i2c-1");
|
||||
空类型 I2C_清除();
|
||||
空类型 I2C_获取锁(); /* FIXME: 使用锁类 */
|
||||
空类型 I2C_释放锁();
|
||||
空类型 I2C_置地址(常量 无号8位整 地址);
|
||||
空类型 I2C_写寄存器(无号8位整 寄存器, 无号8位整 值);
|
||||
无号8位整 I2C_读寄存器(无号8位整 寄存器);
|
||||
|
||||
/* PCA9685 */
|
||||
逻辑型 PCA9685_检查();
|
||||
空类型 PCA9685_初始化();
|
||||
空类型 PCA9685_设置PWM(无号8位整 通道, 无号16位整 导通值, 无号16位整 关断值);
|
||||
空类型 PCA9685_调整角度(无号8位整 通道, 单精度小数 角度);
|
||||
|
||||
/*
|
||||
* MPU6050
|
||||
* 传感器数据
|
||||
*/
|
||||
结构体 MPU6050_传感器数据 {
|
||||
有号16位整 加速度计_横, 加速度计_纵, 加速度计_竖;
|
||||
有号16位整 陀螺仪_横, 陀螺仪_纵, 陀螺仪_竖;
|
||||
};
|
||||
|
||||
逻辑型 MPU6050_检查();
|
||||
空类型 MPU6050_初始化();
|
||||
MPU6050_传感器数据 MPU6050_读传感器(); /* FIXME: 输入结构体引用(或指针)会更快 */
|
||||
空类型 MPU6050_计算角度(常量 MPU6050_传感器数据& 数据, 单精度小数& 俯仰角, 单精度小数& 翻滚角);
|
||||
|
||||
|
||||
|
||||
/* Robot */
|
||||
#include "hd/Robot.hpp"
|
||||
@@ -0,0 +1,177 @@
|
||||
|
||||
//作者:简单
|
||||
//修改:qing
|
||||
/*
|
||||
* C++17支持了中文变量名,配合define预编译指令可以将代码中文化。
|
||||
*
|
||||
* 在C语言中,变量名只能由字母、数字或是下划线组成。
|
||||
* 由于C++语言兼容C语言,最开始的C++是不能够使用多字节变量名的。
|
||||
*
|
||||
* */
|
||||
#ifndef __中文模式
|
||||
#define __中文模式
|
||||
#include <stdint.h>
|
||||
/*保留字*/
|
||||
#define 使用 using
|
||||
#define 命名空间 namespace
|
||||
#define 取别名 typedef
|
||||
#define 覆盖 override
|
||||
#define 取内存大小 sizeof
|
||||
#define 返回 return
|
||||
#define 如果 if
|
||||
#define 否则 else
|
||||
#define 又或者 else if
|
||||
#define 真 true
|
||||
#define 假 false
|
||||
//#define 且 and
|
||||
//#define 或 or
|
||||
#define 空类型 void
|
||||
#define 常量 const
|
||||
#define 有符号 signed
|
||||
#define 无符号 unsigned
|
||||
#define 文件 FILE
|
||||
#define 文件结束 EOF
|
||||
#define 类 class
|
||||
#define 结构体 struct
|
||||
#define 枚举类型 enum
|
||||
#define 对象 this
|
||||
#define 抛出异常 throw
|
||||
#define 新建 new
|
||||
//#define 新的 new
|
||||
#define 删除 delete
|
||||
#define 跳出 break
|
||||
#define 继续 continue
|
||||
#define 判断循环 while
|
||||
#define 循环判断 do
|
||||
#define 循环判断尾 while
|
||||
#define 分支选择 switch
|
||||
#define 支系 case
|
||||
#define 缺省 default
|
||||
#define 计次循环 for
|
||||
#define 尝试 try
|
||||
#define 获取异常 catch
|
||||
#define 捕获异常 catch
|
||||
#define 退出 exit
|
||||
#define 公开的 public
|
||||
#define 受保护的 protected
|
||||
#define 私有的 private
|
||||
#define 外部的 extern
|
||||
#define 静态的 static
|
||||
#define 虚拟的 virtual
|
||||
|
||||
#define 有号8位整 int8_t
|
||||
#define 无号8位整 uint8_t
|
||||
#define 有号16位整 int16_t
|
||||
#define 无号16位整 uint16_t
|
||||
#define 有号32位整 int32_t
|
||||
#define 无号32位整 uint32_t
|
||||
#define 有号64位整 int64_t
|
||||
#define 无号64位整 uint64_t
|
||||
|
||||
#define 空的 NULL
|
||||
#define 空指针 nullptr
|
||||
#define 整数 int
|
||||
#define 字符 char
|
||||
#define 短整数 short
|
||||
#define 长型 long
|
||||
#define 超长型 long long
|
||||
#define 单精度小数 float
|
||||
#define 双精度小数 double
|
||||
#define 长度单位 size_t
|
||||
#define 布尔 bool
|
||||
#define 逻辑型 bool
|
||||
#define 可变类型 auto
|
||||
#define 自动的 auto
|
||||
#define 适应 auto
|
||||
#define _文件 FILE
|
||||
#define _文件结束 EOF
|
||||
#define _读模式 "r"
|
||||
#define _写模式 "w"
|
||||
#define 模板 template
|
||||
#define 类型名 typename
|
||||
#define 默认的 default
|
||||
#define 被删除 delete
|
||||
#define 入口 main
|
||||
#define 主函数 main
|
||||
#define 或 ||
|
||||
#define 或者 ||
|
||||
#define 而且 &&
|
||||
#define 且 &&
|
||||
#define 等于 ==
|
||||
#define 大于等于 >=
|
||||
#define 小于等于 <=
|
||||
#define 大于 >
|
||||
#define 小于 <
|
||||
#define 不报错的 noexcept
|
||||
#define 不转换的 explicit
|
||||
#define 纯函数的 consteval
|
||||
#define 预求值的 constexpr
|
||||
|
||||
#define _加法重载 operator+
|
||||
#define _取下标重载 operator[]
|
||||
#define _开始 begin
|
||||
#define _结束 end
|
||||
//-----------------------------------------------------------
|
||||
//-----------------------------------------------------------
|
||||
//基本单元
|
||||
#ifdef __cplusplus
|
||||
#include <iostream>
|
||||
|
||||
命名空间 中文标准{
|
||||
|
||||
类 字符串 {
|
||||
公开的:
|
||||
/* 初始化为空字符串 */
|
||||
字符串() {
|
||||
对象->s = "";
|
||||
}
|
||||
/* 以字面量初始化 */
|
||||
字符串(常量 字符* 源) {
|
||||
对象->s = 源;
|
||||
}
|
||||
/* 访问器 */
|
||||
常量 字符* 取值() {
|
||||
返回 s.c_str();
|
||||
}
|
||||
/* */
|
||||
字符串& _加法重载(常量 字符* 乙方) {
|
||||
对象->s += 乙方;
|
||||
返回 *对象;
|
||||
}
|
||||
/* 字符串之间的连接 */
|
||||
字符串& _加法重载(字符串& 乙) {
|
||||
对象->s += 乙.取值();
|
||||
返回 *对象;
|
||||
}
|
||||
私有的:
|
||||
std::string s;
|
||||
};
|
||||
|
||||
类 标准输出{
|
||||
公开的:
|
||||
空类型 调试输出(常量 字符* 消息) {
|
||||
std::cout << 消息;
|
||||
}
|
||||
空类型 调试输出(字符串& 信) {
|
||||
std::cout<< 信.取值();
|
||||
}
|
||||
};
|
||||
|
||||
类 标准输入{
|
||||
公开的:
|
||||
字符串 取字符串(){
|
||||
std::string s;
|
||||
std::cin >> s;
|
||||
返回 字符串(s.c_str());
|
||||
}
|
||||
整数 取整数() {
|
||||
整数 数;
|
||||
std::cin >> 数;
|
||||
返回 数;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
#endif //__cplusplus
|
||||
#endif //__中文模式
|
||||
@@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
#include "中文化.hpp"
|
||||
#include "异常.hpp"
|
||||
命名空间 中文标准 {
|
||||
模板 <类型名 类型1>
|
||||
类 向量{
|
||||
公开的:
|
||||
|
||||
/* 注意:当长度单位为0时,内部指针是空的 */
|
||||
向量() {
|
||||
对象->指针 = 空指针;
|
||||
对象->容量 = 0;
|
||||
}
|
||||
/* 专门用于处理花括号初始化列表 */
|
||||
向量(常量 std::initializer_list<类型1>& init = {}) {
|
||||
对象->指针 = 新建 类型1[init.size()] {空的};
|
||||
对象->容量 = init.size();
|
||||
长度单位 计数 = 0;
|
||||
计次循环(适应 & 东西: init) {
|
||||
对象->指针[计数++] = 东西;
|
||||
}
|
||||
}
|
||||
|
||||
~向量() {
|
||||
删除 对象->指针;
|
||||
对象->指针 = 空指针;
|
||||
对象->容量 = 0;
|
||||
}
|
||||
/*----------访问器----------*/
|
||||
长度单位 取容量() {
|
||||
返回 对象->容量;
|
||||
}
|
||||
|
||||
布尔 是否为空() {
|
||||
返回 对象->容量 等于 0;
|
||||
}
|
||||
|
||||
/*--------运算符重载--------*/
|
||||
向量& _取下标重载( 常量 长度单位 下标 ) {
|
||||
如果(下标 < 0 或 下标 >= 对象->容量)
|
||||
抛出异常 下标越界{"向量", 对象->容量, 下标};
|
||||
返回 对象->指针[下标];
|
||||
}
|
||||
|
||||
/*--------可范围遍历--------*/
|
||||
类型1* _开始() {
|
||||
返回 对象->指针;
|
||||
}
|
||||
|
||||
类型1* _结束() {
|
||||
返回 对象->指针 + 对象->容量;
|
||||
}
|
||||
|
||||
/*----------变异器----------*/
|
||||
空类型 插入元素(常量 长度单位 位置, 常量 向量& 媒介) {
|
||||
|
||||
/* 越界检查 */
|
||||
如果(位置 < 0 或 位置 > 对象->容量)
|
||||
抛出异常 下标越界{"向量元素插入", 对象->容量+1, 位置};
|
||||
|
||||
适应 长度 = 取容量();
|
||||
适应 新的 = 新建 类型1[长度 + 1] { 空的 };
|
||||
|
||||
/* 插入位置之前 */
|
||||
计次循环(适应 计数 = 0; 计数 < 位置; ++计数)
|
||||
新的[计数] = 对象->指针[计数];
|
||||
|
||||
/* 执行插入 */
|
||||
新的[位置] = 媒介;
|
||||
|
||||
/* 插入位置之后 */
|
||||
计次循环(适应 计数 = 位置 + 1; 计数 <= 长度; ++计数)
|
||||
新的[计数] = 对象->指针[计数 - 1];
|
||||
|
||||
如果(对象->指针) 删除[] 对象->指针; /* 可能为空 */
|
||||
对象->指针 = 新的;
|
||||
}
|
||||
|
||||
空类型 追加元素(常量 向量& 媒介) {
|
||||
适应 长度 = 取容量();
|
||||
适应 新的 = 新建 类型1[长度 +1] {空的};
|
||||
计次循环(适应 计数 = 0; 计数 < 长度; ++计数)
|
||||
新的[计数] = 对象->指针[计数];
|
||||
新的[长度] = 媒介;
|
||||
如果(对象->指针) 删除[] 指针; /* 可能为空 */
|
||||
对象->指针 = 新的;
|
||||
}
|
||||
|
||||
空类型 删除元素(常量 长度单位 位置) {
|
||||
|
||||
/* 越界检查 */
|
||||
如果(位置 < 0 或 位置 >= 对象->容量)
|
||||
抛出异常 下标越界{"向量元素插入", 对象->容量 + 1, 位置};
|
||||
|
||||
适应 长度 = 取容量();
|
||||
适应 新的 = (长度 等于 1)
|
||||
? 空指针 : 新建 类型1[长度 - 1] { 空的 };
|
||||
|
||||
/* 目标位置之前 */
|
||||
计次循环(适应 计数 = 0; 计数 < 位置; ++计数)
|
||||
新的[计数] = 对象->指针[计数];
|
||||
|
||||
/* 目标位置之后 */
|
||||
计次循环(适应 计数 = 位置; 计数 < 长度; ++计数)
|
||||
新的[计数] = 对象->指针[计数 + 1];
|
||||
|
||||
删除[] 对象->指针;
|
||||
对象->指针 = 新的;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*----------------*/
|
||||
//类 迭代器{
|
||||
//公开的:
|
||||
// 迭代器(常量 std::vector<类型1>::iterator& it) {
|
||||
// this->it = it;
|
||||
// }
|
||||
//私有的:
|
||||
// std::vector<类型1>::iterator it;
|
||||
//};
|
||||
/*----------------*/
|
||||
|
||||
|
||||
私有的:
|
||||
类型1 *指针;
|
||||
长度单位 容量;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include "中文化.hpp"
|
||||
命名空间 中文标准 {
|
||||
类 标准异常 : 公开的 std::exception {
|
||||
公开的:
|
||||
不转换的 标准异常(常量 字符* 信息): 信息(信息) {}
|
||||
常量 字符* what() 常量 不报错的 覆盖 {
|
||||
返回 信息;
|
||||
}
|
||||
私有的:
|
||||
常量 字符* 信息;
|
||||
};
|
||||
|
||||
类 下标越界 : 公开的 标准异常{
|
||||
公开的:
|
||||
下标越界(常量 字符 * 提示, 常量 长度单位 标准值, 常量 长度单位 目前值)
|
||||
: 标准异常(提示), 标准值(标准值), 目前值(目前值) {
|
||||
};
|
||||
长度单位 取标准值() { 返回 标准值; }
|
||||
长度单位 取目前值() { 返回 目前值; }
|
||||
私有的:
|
||||
长度单位 标准值, 目前值;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
#include "中文化.hpp"
|
||||
#include "异常.hpp"
|
||||
#include "顺序表.hpp"
|
||||
命名空间 中文标准 {
|
||||
模板 <类型名 类型1, 长度单位 节点大小 = 64>
|
||||
类 链串 {
|
||||
公开的:
|
||||
/* 初始化为空链串 */
|
||||
链串() {
|
||||
对象->长度 = 0;
|
||||
对象->下一个 = 空指针;
|
||||
对象->上一个 = 空指针;
|
||||
对象->头 = 对象; /* 每一个子节点都记录同一个头结点 */
|
||||
对象->尾 = 对象; /* 为了方便,只有头结点记录尾节点 */
|
||||
}
|
||||
|
||||
链串 (常量 std::initializer_list<类型1>& init = {})
|
||||
{
|
||||
计次循环(适应 & 东西: init) {
|
||||
插入(东西);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 链式析构
|
||||
*
|
||||
* 析构时,会递归析构所有节点。
|
||||
*/
|
||||
~链串() {
|
||||
删除[] 对象->下一个;
|
||||
}
|
||||
|
||||
空类型 插入(常量 类型1& 元素) {
|
||||
链串 *暂时 = 对象->尾;
|
||||
如果(暂时->长度 等于 节点大小)
|
||||
_推();
|
||||
暂时->存储[暂时->长度++] = 元素;
|
||||
}
|
||||
|
||||
长度单位 统计() {
|
||||
长度单位 总数 = 0;
|
||||
链串 *暂时 = 对象->头;
|
||||
判断循环(暂时 != 空指针) {
|
||||
总数 += 暂时->长度;
|
||||
暂时 = 暂时->下一个;
|
||||
}
|
||||
返回 总数;
|
||||
}
|
||||
|
||||
/*
|
||||
* 链串重组
|
||||
*
|
||||
* 将链串重组为顺序表W
|
||||
*/
|
||||
顺序表<类型1> 重组() {
|
||||
顺序表<类型1> 结果(统计());
|
||||
链串 *暂时 = 对象->头;
|
||||
长度单位 计数 = 0;
|
||||
判断循环(暂时 != 空指针) {
|
||||
计次循环(适应 索引 = 0; 索引 < 暂时->长度; ++索引) {
|
||||
结果[计数++] = 暂时->存储[索引];
|
||||
}
|
||||
暂时 = 暂时->下一个;
|
||||
}
|
||||
返回 结果;
|
||||
}
|
||||
|
||||
类 链串异常: 公开的 标准异常{
|
||||
公开的:
|
||||
链串异常(常量 字符* 消息) : 标准异常(消息) {}
|
||||
};
|
||||
私有的:
|
||||
类型1 存储[节点大小];
|
||||
长度单位 长度; /* 存储中的元素数量 */
|
||||
链串 *下一个;
|
||||
链串 *上一个;
|
||||
链串 *头;
|
||||
链串 *尾;
|
||||
|
||||
空类型 _推() {
|
||||
链串*新的;
|
||||
尝试{ 新的 = 新建 链串(); }
|
||||
捕获异常 (std::exception& exp){
|
||||
抛出异常 链串异常{"链串推失败"};
|
||||
}
|
||||
_拼接节点(新的);
|
||||
}
|
||||
|
||||
空类型 _拼接节点(链串* 新的) {
|
||||
对象->尾->下一个 = 新的;
|
||||
新的->上一个 = 对象->尾;
|
||||
对象->尾 = 新的;
|
||||
新的->头 = 对象;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include "中文化.hpp"
|
||||
#include "异常.hpp"
|
||||
命名空间 中文标准 {
|
||||
模板<类型名 类型1>
|
||||
类 顺序表 {
|
||||
公开的:
|
||||
顺序表(长度单位 大小) {
|
||||
存储 = 新建 类型1[大小];
|
||||
长度 = 大小;
|
||||
}
|
||||
~顺序表() {
|
||||
删除[] 存储;
|
||||
}
|
||||
类型1 _取下标重载(长度单位 下标) {
|
||||
如果(下标 >= 长度)
|
||||
抛出异常 顺序表异常{"下标超出范围"};
|
||||
返回 存储[下标];
|
||||
}
|
||||
类 顺序表异常: 公开的 标准异常{
|
||||
公开的:
|
||||
顺序表异常(常量 字符* 消息) : 标准异常(消息) {}
|
||||
};
|
||||
私有的:
|
||||
类型1 *存储;
|
||||
长度单位 长度;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,560 @@
|
||||
#ifndef PMC_GUI_GTK_HPP
|
||||
#define PMC_GUI_GTK_HPP
|
||||
|
||||
/**
|
||||
* @file GTK.hpp
|
||||
* @brief GTK库入口封装
|
||||
*
|
||||
* 提供GTK库的统一入口和简化接口,便于在pmc项目中使用GTK进行GUI开发。
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace pmc {
|
||||
namespace gui {
|
||||
|
||||
/**
|
||||
* @class GTKApplication
|
||||
* @brief GTK应用程序封装类
|
||||
*
|
||||
* 封装GTK应用程序的初始化和运行逻辑,提供简化的API。
|
||||
*/
|
||||
class GTKApplication {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param app_id 应用程序ID
|
||||
* @param app_name 应用程序名称
|
||||
*/
|
||||
GTKApplication(const std::string& app_id, const std::string& app_name);
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~GTKApplication();
|
||||
|
||||
/**
|
||||
* @brief 初始化GTK应用程序
|
||||
* @param argc 命令行参数数量
|
||||
* @param argv 命令行参数数组
|
||||
* @return 初始化是否成功
|
||||
*/
|
||||
bool initialize(int argc, char** argv);
|
||||
|
||||
/**
|
||||
* @brief 运行应用程序
|
||||
* @return 应用程序退出码
|
||||
*/
|
||||
int run();
|
||||
|
||||
/**
|
||||
* @brief 获取GTK应用程序指针
|
||||
* @return GtkApplication指针
|
||||
*/
|
||||
GtkApplication* get_app() const { return app_; }
|
||||
|
||||
/**
|
||||
* @brief 设置应用程序图标
|
||||
* @param icon_path 图标文件路径
|
||||
* @return 设置是否成功
|
||||
*/
|
||||
bool set_icon(const std::string& icon_path);
|
||||
|
||||
/**
|
||||
* @brief 设置窗口默认大小
|
||||
* @param width 窗口宽度
|
||||
* @param height 窗口高度
|
||||
*/
|
||||
void set_default_window_size(int width, int height);
|
||||
|
||||
private:
|
||||
std::string app_id_;
|
||||
std::string app_name_;
|
||||
GtkApplication* app_;
|
||||
int default_width_;
|
||||
int default_height_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class GTKWindow
|
||||
* @brief GTK窗口封装类
|
||||
*
|
||||
* 封装GTK窗口的创建和管理,提供简化的窗口操作接口。
|
||||
*/
|
||||
class GTKWindow {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param title 窗口标题
|
||||
* @param width 窗口宽度
|
||||
* @param height 窗口高度
|
||||
*/
|
||||
GTKWindow(const std::string& title, int width = 800, int height = 600);
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~GTKWindow();
|
||||
|
||||
/**
|
||||
* @brief 显示窗口
|
||||
*/
|
||||
void show();
|
||||
|
||||
/**
|
||||
* @brief 隐藏窗口
|
||||
*/
|
||||
void hide();
|
||||
|
||||
/**
|
||||
* @brief 设置窗口标题
|
||||
* @param title 新标题
|
||||
*/
|
||||
void set_title(const std::string& title);
|
||||
|
||||
/**
|
||||
* @brief 获取窗口标题
|
||||
* @return 窗口标题
|
||||
*/
|
||||
std::string get_title() const;
|
||||
|
||||
/**
|
||||
* @brief 设置窗口大小
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
*/
|
||||
void set_size(int width, int height);
|
||||
|
||||
/**
|
||||
* @brief 获取窗口宽度
|
||||
* @return 窗口宽度
|
||||
*/
|
||||
int get_width() const;
|
||||
|
||||
/**
|
||||
* @brief 获取窗口高度
|
||||
* @return 窗口高度
|
||||
*/
|
||||
int get_height() const;
|
||||
|
||||
/**
|
||||
* @brief 获取GTK窗口指针
|
||||
* @return GtkWindow指针
|
||||
*/
|
||||
GtkWindow* get_window() const { return window_; }
|
||||
|
||||
/**
|
||||
* @brief 设置关闭回调函数
|
||||
* @param callback 关闭回调函数
|
||||
*/
|
||||
void set_close_callback(std::function<void()> callback);
|
||||
|
||||
/**
|
||||
* @brief 添加子控件
|
||||
* @param widget 要添加的控件
|
||||
*/
|
||||
void add_widget(GtkWidget* widget);
|
||||
|
||||
/**
|
||||
* @brief 设置窗口位置居中
|
||||
*/
|
||||
void center_on_screen();
|
||||
|
||||
private:
|
||||
GtkWindow* window_;
|
||||
GtkWidget* container_;
|
||||
std::string title_;
|
||||
int width_;
|
||||
int height_;
|
||||
std::function<void()> close_callback_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class GTKButton
|
||||
* @brief GTK按钮封装类
|
||||
*
|
||||
* 封装GTK按钮的创建和事件处理。
|
||||
*/
|
||||
class GTKButton {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param label 按钮标签
|
||||
*/
|
||||
GTKButton(const std::string& label);
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~GTKButton();
|
||||
|
||||
/**
|
||||
* @brief 设置点击回调函数
|
||||
* @param callback 点击回调函数
|
||||
*/
|
||||
void set_click_callback(std::function<void()> callback);
|
||||
|
||||
/**
|
||||
* @brief 获取GTK按钮指针
|
||||
* @return GtkButton指针
|
||||
*/
|
||||
GtkButton* get_button() const { return button_; }
|
||||
|
||||
/**
|
||||
* @brief 设置按钮标签
|
||||
* @param label 新标签
|
||||
*/
|
||||
void set_label(const std::string& label);
|
||||
|
||||
/**
|
||||
* @brief 获取按钮标签
|
||||
* @return 按钮标签
|
||||
*/
|
||||
std::string get_label() const;
|
||||
|
||||
/**
|
||||
* @brief 启用按钮
|
||||
*/
|
||||
void enable();
|
||||
|
||||
/**
|
||||
* @brief 禁用按钮
|
||||
*/
|
||||
void disable();
|
||||
|
||||
/**
|
||||
* @brief 设置按钮工具提示
|
||||
* @param tooltip 工具提示文本
|
||||
*/
|
||||
void set_tooltip(const std::string& tooltip);
|
||||
|
||||
private:
|
||||
GtkButton* button_;
|
||||
std::string label_;
|
||||
std::function<void()> click_callback_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class GTKLabel
|
||||
* @brief GTK标签封装类
|
||||
*
|
||||
* 封装GTK标签的创建和文本管理。
|
||||
*/
|
||||
class GTKLabel {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param text 标签文本
|
||||
*/
|
||||
GTKLabel(const std::string& text = "");
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~GTKLabel();
|
||||
|
||||
/**
|
||||
* @brief 设置标签文本
|
||||
* @param text 新文本
|
||||
*/
|
||||
void set_text(const std::string& text);
|
||||
|
||||
/**
|
||||
* @brief 获取标签文本
|
||||
* @return 标签文本
|
||||
*/
|
||||
std::string get_text() const;
|
||||
|
||||
/**
|
||||
* @brief 获取GTK标签指针
|
||||
* @return GtkLabel指针
|
||||
*/
|
||||
GtkLabel* get_label() const { return label_; }
|
||||
|
||||
/**
|
||||
* @brief 设置文本对齐方式
|
||||
* @param xalign 水平对齐 (0.0=左对齐, 0.5=居中, 1.0=右对齐)
|
||||
* @param yalign 垂直对齐 (0.0=顶部, 0.5=居中, 1.0=底部)
|
||||
*/
|
||||
void set_alignment(float xalign = 0.0f, float yalign = 0.5f);
|
||||
|
||||
/**
|
||||
* @brief 设置文本换行
|
||||
* @param wrap 是否换行
|
||||
*/
|
||||
void set_wrap(bool wrap);
|
||||
|
||||
/**
|
||||
* @brief 设置选择模式
|
||||
* @param selectable 是否可选择
|
||||
*/
|
||||
void set_selectable(bool selectable);
|
||||
|
||||
private:
|
||||
GtkLabel* label_;
|
||||
std::string text_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class GTKEntry
|
||||
* @brief GTK输入框封装类
|
||||
*
|
||||
* 封装GTK输入框的创建和文本管理。
|
||||
*/
|
||||
class GTKEntry {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param placeholder 占位符文本
|
||||
*/
|
||||
GTKEntry(const std::string& placeholder = "");
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~GTKEntry();
|
||||
|
||||
/**
|
||||
* @brief 获取输入文本
|
||||
* @return 输入文本
|
||||
*/
|
||||
std::string get_text() const;
|
||||
|
||||
/**
|
||||
* @brief 设置输入文本
|
||||
* @param text 新文本
|
||||
*/
|
||||
void set_text(const std::string& text);
|
||||
|
||||
/**
|
||||
* @brief 清空输入框
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 获取GTK输入框指针
|
||||
* @return GtkEntry指针
|
||||
*/
|
||||
GtkEntry* get_entry() const { return entry_; }
|
||||
|
||||
/**
|
||||
* @brief 设置占位符文本
|
||||
* @param placeholder 占位符文本
|
||||
*/
|
||||
void set_placeholder(const std::string& placeholder);
|
||||
|
||||
/**
|
||||
* @brief 设置输入变化回调函数
|
||||
* @param callback 输入变化回调函数
|
||||
*/
|
||||
void set_changed_callback(std::function<void()> callback);
|
||||
|
||||
/**
|
||||
* @brief 设置激活回调函数(回车键)
|
||||
* @param callback 激活回调函数
|
||||
*/
|
||||
void set_activate_callback(std::function<void()> callback);
|
||||
|
||||
/**
|
||||
* @brief 启用输入框
|
||||
*/
|
||||
void enable();
|
||||
|
||||
/**
|
||||
* @brief 禁用输入框
|
||||
*/
|
||||
void disable();
|
||||
|
||||
private:
|
||||
GtkEntry* entry_;
|
||||
std::string placeholder_;
|
||||
std::function<void()> changed_callback_;
|
||||
std::function<void()> activate_callback_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class GTKTextView
|
||||
* @brief GTK文本视图封装类
|
||||
*
|
||||
* 封装GTK文本视图的创建和文本管理,支持多行文本编辑。
|
||||
*/
|
||||
class GTKTextView {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
GTKTextView();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~GTKTextView();
|
||||
|
||||
/**
|
||||
* @brief 获取文本内容
|
||||
* @return 文本内容
|
||||
*/
|
||||
std::string get_text() const;
|
||||
|
||||
/**
|
||||
* @brief 设置文本内容
|
||||
* @param text 新文本
|
||||
*/
|
||||
void set_text(const std::string& text);
|
||||
|
||||
/**
|
||||
* @brief 追加文本
|
||||
* @param text 要追加的文本
|
||||
*/
|
||||
void append_text(const std::string& text);
|
||||
|
||||
/**
|
||||
* @brief 清空文本
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 获取GTK文本视图指针
|
||||
* @return GtkTextView指针
|
||||
*/
|
||||
GtkTextView* get_text_view() const { return text_view_; }
|
||||
|
||||
/**
|
||||
* @brief 获取文本缓冲区
|
||||
* @return GtkTextBuffer指针
|
||||
*/
|
||||
GtkTextBuffer* get_buffer() const { return buffer_; }
|
||||
|
||||
/**
|
||||
* @brief 设置只读模式
|
||||
* @param read_only 是否只读
|
||||
*/
|
||||
void set_read_only(bool read_only);
|
||||
|
||||
/**
|
||||
* @brief 设置文本变化回调函数
|
||||
* @param callback 文本变化回调函数
|
||||
*/
|
||||
void set_changed_callback(std::function<void()> callback);
|
||||
|
||||
/**
|
||||
* @brief 滚动到底部
|
||||
*/
|
||||
void scroll_to_bottom();
|
||||
|
||||
private:
|
||||
GtkTextView* text_view_;
|
||||
GtkTextBuffer* buffer_;
|
||||
std::function<void()> changed_callback_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class GTKBox
|
||||
* @brief GTK盒子布局容器封装类
|
||||
*
|
||||
* 封装GTK盒子布局容器的创建和子控件管理。
|
||||
*/
|
||||
class GTKBox {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param orientation 布局方向 (GTK_ORIENTATION_HORIZONTAL 或 GTK_ORIENTATION_VERTICAL)
|
||||
* @param spacing 子控件间距
|
||||
*/
|
||||
GTKBox(GtkOrientation orientation = GTK_ORIENTATION_VERTICAL, int spacing = 5);
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~GTKBox();
|
||||
|
||||
/**
|
||||
* @brief 添加子控件
|
||||
* @param widget 要添加的控件
|
||||
* @param expand 是否扩展
|
||||
* @param fill 是否填充
|
||||
* @param padding 内边距
|
||||
*/
|
||||
void add_widget(GtkWidget* widget, bool expand = false, bool fill = false, int padding = 0);
|
||||
|
||||
/**
|
||||
* @brief 获取GTK盒子指针
|
||||
* @return GtkBox指针
|
||||
*/
|
||||
GtkBox* get_box() const { return box_; }
|
||||
|
||||
/**
|
||||
* @brief 设置间距
|
||||
* @param spacing 新间距
|
||||
*/
|
||||
void set_spacing(int spacing);
|
||||
|
||||
/**
|
||||
* @brief 设置对齐方式
|
||||
* @param align 对齐方式
|
||||
*/
|
||||
void set_alignment(GtkAlign align);
|
||||
|
||||
/**
|
||||
* @brief 设置均匀分配
|
||||
* @param homogeneous 是否均匀分配空间
|
||||
*/
|
||||
void set_homogeneous(bool homogeneous);
|
||||
|
||||
private:
|
||||
GtkBox* box_;
|
||||
GtkOrientation orientation_;
|
||||
int spacing_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 初始化GTK库
|
||||
* @param argc 命令行参数数量指针
|
||||
* @param argv 命令行参数数组指针
|
||||
* @return 初始化是否成功
|
||||
*/
|
||||
bool gtk_init(int* argc, char*** argv);
|
||||
|
||||
/**
|
||||
* @brief 运行GTK主循环
|
||||
*/
|
||||
void gtk_main();
|
||||
|
||||
/**
|
||||
* @brief 退出GTK主循环
|
||||
*/
|
||||
void gtk_main_quit();
|
||||
|
||||
/**
|
||||
* @brief 显示错误对话框
|
||||
* @param parent 父窗口
|
||||
* @param title 对话框标题
|
||||
* @param message 错误消息
|
||||
*/
|
||||
void show_error_dialog(GtkWindow* parent, const std::string& title, const std::string& message);
|
||||
|
||||
/**
|
||||
* @brief 显示信息对话框
|
||||
* @param parent 父窗口
|
||||
* @param title 对话框标题
|
||||
* @param message 信息消息
|
||||
*/
|
||||
void show_info_dialog(GtkWindow* parent, const std::string& title, const std::string& message);
|
||||
|
||||
/**
|
||||
* @brief 显示确认对话框
|
||||
* @param parent 父窗口
|
||||
* @param title 对话框标题
|
||||
* @param message 确认消息
|
||||
* @return 用户是否确认
|
||||
*/
|
||||
bool show_confirm_dialog(GtkWindow* parent, const std::string& title, const std::string& message);
|
||||
|
||||
} // namespace gui
|
||||
} // namespace pmc
|
||||
|
||||
#endif // PMC_GUI_GTK_HPP
|
||||
@@ -0,0 +1,456 @@
|
||||
#ifndef PMC_GUI_GTK_IMPL_HPP
|
||||
#define PMC_GUI_GTK_IMPL_HPP
|
||||
|
||||
/**
|
||||
* @file GTK_impl.hpp
|
||||
* @brief GTK库封装实现模板
|
||||
*
|
||||
* 提供GTK封装类的内联实现,便于头文件包含。
|
||||
*/
|
||||
|
||||
#include "gui/GTK.hpp"
|
||||
#include <iostream>
|
||||
|
||||
namespace pmc {
|
||||
namespace gui {
|
||||
|
||||
// GTKApplication 实现
|
||||
inline GTKApplication::GTKApplication(const std::string& app_id, const std::string& app_name)
|
||||
: app_id_(app_id), app_name_(app_name), app_(nullptr), default_width_(800), default_height_(600) {
|
||||
}
|
||||
|
||||
inline GTKApplication::~GTKApplication() {
|
||||
if (app_) {
|
||||
g_object_unref(app_);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool GTKApplication::initialize(int argc, char** argv) {
|
||||
app_ = gtk_application_new(app_id_.c_str(), G_APPLICATION_FLAGS_NONE);
|
||||
if (!app_) {
|
||||
std::cerr << "Failed to create GTK application: " << app_name_ << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline int GTKApplication::run() {
|
||||
if (!app_) {
|
||||
std::cerr << "GTK application not initialized" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
return g_application_run(G_APPLICATION(app_), 0, nullptr);
|
||||
}
|
||||
|
||||
inline bool GTKApplication::set_icon(const std::string& icon_path) {
|
||||
// 实现图标设置逻辑
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void GTKApplication::set_default_window_size(int width, int height) {
|
||||
default_width_ = width;
|
||||
default_height_ = height;
|
||||
}
|
||||
|
||||
// GTKWindow 实现
|
||||
inline GTKWindow::GTKWindow(const std::string& title, int width, int height)
|
||||
: title_(title), width_(width), height_(height), window_(nullptr), container_(nullptr) {
|
||||
window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
|
||||
if (window_) {
|
||||
gtk_window_set_title(window_, title_.c_str());
|
||||
gtk_window_set_default_size(window_, width_, height_);
|
||||
container_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
|
||||
gtk_container_add(GTK_CONTAINER(window_), container_);
|
||||
}
|
||||
}
|
||||
|
||||
inline GTKWindow::~GTKWindow() {
|
||||
// GTK会自动管理窗口内存
|
||||
}
|
||||
|
||||
inline void GTKWindow::show() {
|
||||
if (window_) {
|
||||
gtk_widget_show_all(GTK_WIDGET(window_));
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKWindow::hide() {
|
||||
if (window_) {
|
||||
gtk_widget_hide(GTK_WIDGET(window_));
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKWindow::set_title(const std::string& title) {
|
||||
title_ = title;
|
||||
if (window_) {
|
||||
gtk_window_set_title(window_, title_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string GTKWindow::get_title() const {
|
||||
return title_;
|
||||
}
|
||||
|
||||
inline void GTKWindow::set_size(int width, int height) {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
if (window_) {
|
||||
gtk_window_resize(window_, width_, height_);
|
||||
}
|
||||
}
|
||||
|
||||
inline int GTKWindow::get_width() const {
|
||||
return width_;
|
||||
}
|
||||
|
||||
inline int GTKWindow::get_height() const {
|
||||
return height_;
|
||||
}
|
||||
|
||||
inline void GTKWindow::set_close_callback(std::function<void()> callback) {
|
||||
close_callback_ = callback;
|
||||
if (window_) {
|
||||
g_signal_connect(window_, "destroy", G_CALLBACK(+[](GtkWidget* widget, gpointer data) {
|
||||
auto self = static_cast<GTKWindow*>(data);
|
||||
if (self && self->close_callback_) {
|
||||
self->close_callback_();
|
||||
}
|
||||
}), this);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKWindow::add_widget(GtkWidget* widget) {
|
||||
if (container_ && widget) {
|
||||
gtk_container_add(GTK_CONTAINER(container_), widget);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKWindow::center_on_screen() {
|
||||
if (window_) {
|
||||
gtk_window_set_position(window_, GTK_WIN_POS_CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
// GTKButton 实现
|
||||
inline GTKButton::GTKButton(const std::string& label)
|
||||
: label_(label) {
|
||||
button_ = GTK_BUTTON(gtk_button_new_with_label(label_.c_str()));
|
||||
}
|
||||
|
||||
inline GTKButton::~GTKButton() {
|
||||
// GTK会自动管理按钮内存
|
||||
}
|
||||
|
||||
inline void GTKButton::set_click_callback(std::function<void()> callback) {
|
||||
click_callback_ = callback;
|
||||
if (button_) {
|
||||
g_signal_connect(button_, "clicked", G_CALLBACK(+[](GtkButton* button, gpointer data) {
|
||||
auto self = static_cast<GTKButton*>(data);
|
||||
if (self && self->click_callback_) {
|
||||
self->click_callback_();
|
||||
}
|
||||
}), this);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKButton::set_label(const std::string& label) {
|
||||
label_ = label;
|
||||
if (button_) {
|
||||
gtk_button_set_label(button_, label_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string GTKButton::get_label() const {
|
||||
return label_;
|
||||
}
|
||||
|
||||
inline void GTKButton::enable() {
|
||||
if (button_) {
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(button_), TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKButton::disable() {
|
||||
if (button_) {
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(button_), FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKButton::set_tooltip(const std::string& tooltip) {
|
||||
if (button_) {
|
||||
gtk_widget_set_tooltip_text(GTK_WIDGET(button_), tooltip.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// GTKLabel 实现
|
||||
inline GTKLabel::GTKLabel(const std::string& text)
|
||||
: text_(text) {
|
||||
label_ = GTK_LABEL(gtk_label_new(text_.c_str()));
|
||||
}
|
||||
|
||||
inline GTKLabel::~GTKLabel() {
|
||||
// GTK会自动管理标签内存
|
||||
}
|
||||
|
||||
inline void GTKLabel::set_text(const std::string& text) {
|
||||
text_ = text;
|
||||
if (label_) {
|
||||
gtk_label_set_text(label_, text_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string GTKLabel::get_text() const {
|
||||
return text_;
|
||||
}
|
||||
|
||||
inline void GTKLabel::set_alignment(float xalign, float yalign) {
|
||||
if (label_) {
|
||||
gtk_label_set_xalign(label_, xalign);
|
||||
gtk_label_set_yalign(label_, yalign);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKLabel::set_wrap(bool wrap) {
|
||||
if (label_) {
|
||||
gtk_label_set_line_wrap(label_, wrap);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKLabel::set_selectable(bool selectable) {
|
||||
if (label_) {
|
||||
gtk_label_set_selectable(label_, selectable);
|
||||
}
|
||||
}
|
||||
|
||||
// GTKEntry 实现
|
||||
inline GTKEntry::GTKEntry(const std::string& placeholder)
|
||||
: placeholder_(placeholder) {
|
||||
entry_ = GTK_ENTRY(gtk_entry_new());
|
||||
if (!placeholder_.empty()) {
|
||||
gtk_entry_set_placeholder_text(entry_, placeholder_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
inline GTKEntry::~GTKEntry() {
|
||||
// GTK会自动管理输入框内存
|
||||
}
|
||||
|
||||
inline std::string GTKEntry::get_text() const {
|
||||
if (entry_) {
|
||||
const gchar* text = gtk_entry_get_text(entry_);
|
||||
return text ? std::string(text) : "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
inline void GTKEntry::set_text(const std::string& text) {
|
||||
if (entry_) {
|
||||
gtk_entry_set_text(entry_, text.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKEntry::clear() {
|
||||
if (entry_) {
|
||||
gtk_entry_set_text(entry_, "");
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKEntry::set_placeholder(const std::string& placeholder) {
|
||||
placeholder_ = placeholder;
|
||||
if (entry_) {
|
||||
gtk_entry_set_placeholder_text(entry_, placeholder_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKEntry::set_changed_callback(std::function<void()> callback) {
|
||||
changed_callback_ = callback;
|
||||
if (entry_) {
|
||||
g_signal_connect(entry_, "changed", G_CALLBACK(+[](GtkEntry* entry, gpointer data) {
|
||||
auto self = static_cast<GTKEntry*>(data);
|
||||
if (self && self->changed_callback_) {
|
||||
self->changed_callback_();
|
||||
}
|
||||
}), this);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKEntry::set_activate_callback(std::function<void()> callback) {
|
||||
activate_callback_ = callback;
|
||||
if (entry_) {
|
||||
g_signal_connect(entry_, "activate", G_CALLBACK(+[](GtkEntry* entry, gpointer data) {
|
||||
auto self = static_cast<GTKEntry*>(data);
|
||||
if (self && self->activate_callback_) {
|
||||
self->activate_callback_();
|
||||
}
|
||||
}), this);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKEntry::enable() {
|
||||
if (entry_) {
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(entry_), TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKEntry::disable() {
|
||||
if (entry_) {
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(entry_), FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
// GTKTextView 实现
|
||||
inline GTKTextView::GTKTextView() {
|
||||
text_view_ = GTK_TEXT_VIEW(gtk_text_view_new());
|
||||
buffer_ = gtk_text_view_get_buffer(text_view_);
|
||||
}
|
||||
|
||||
inline GTKTextView::~GTKTextView() {
|
||||
// GTK会自动管理文本视图内存
|
||||
}
|
||||
|
||||
inline std::string GTKTextView::get_text() const {
|
||||
if (buffer_) {
|
||||
GtkTextIter start, end;
|
||||
gtk_text_buffer_get_start_iter(buffer_, &start);
|
||||
gtk_text_buffer_get_end_iter(buffer_, &end);
|
||||
gchar* text = gtk_text_buffer_get_text(buffer_, &start, &end, FALSE);
|
||||
std::string result(text ? text : "");
|
||||
g_free(text);
|
||||
return result;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
inline void GTKTextView::set_text(const std::string& text) {
|
||||
if (buffer_) {
|
||||
gtk_text_buffer_set_text(buffer_, text.c_str(), -1);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKTextView::append_text(const std::string& text) {
|
||||
if (buffer_) {
|
||||
GtkTextIter end;
|
||||
gtk_text_buffer_get_end_iter(buffer_, &end);
|
||||
gtk_text_buffer_insert(buffer_, &end, text.c_str(), -1);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKTextView::clear() {
|
||||
if (buffer_) {
|
||||
gtk_text_buffer_set_text(buffer_, "", -1);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKTextView::set_read_only(bool read_only) {
|
||||
if (text_view_) {
|
||||
gtk_text_view_set_editable(text_view_, !read_only);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKTextView::set_changed_callback(std::function<void()> callback) {
|
||||
changed_callback_ = callback;
|
||||
if (buffer_) {
|
||||
g_signal_connect(buffer_, "changed", G_CALLBACK(+[](GtkTextBuffer* buffer, gpointer data) {
|
||||
auto self = static_cast<GTKTextView*>(data);
|
||||
if (self && self->changed_callback_) {
|
||||
self->changed_callback_();
|
||||
}
|
||||
}), this);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKTextView::scroll_to_bottom() {
|
||||
if (text_view_ && buffer_) {
|
||||
GtkTextIter end;
|
||||
gtk_text_buffer_get_end_iter(buffer_, &end);
|
||||
gtk_text_view_scroll_to_iter(text_view_, &end, 0.0, FALSE, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// GTKBox 实现
|
||||
inline GTKBox::GTKBox(GtkOrientation orientation, int spacing)
|
||||
: orientation_(orientation), spacing_(spacing) {
|
||||
box_ = GTK_BOX(gtk_box_new(orientation_, spacing_));
|
||||
}
|
||||
|
||||
inline GTKBox::~GTKBox() {
|
||||
// GTK会自动管理盒子内存
|
||||
}
|
||||
|
||||
inline void GTKBox::add_widget(GtkWidget* widget, bool expand, bool fill, int padding) {
|
||||
if (box_ && widget) {
|
||||
gtk_box_pack_start(box_, widget, expand, fill, padding);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKBox::set_spacing(int spacing) {
|
||||
spacing_ = spacing;
|
||||
if (box_) {
|
||||
gtk_box_set_spacing(box_, spacing_);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKBox::set_alignment(GtkAlign align) {
|
||||
if (box_) {
|
||||
gtk_widget_set_halign(GTK_WIDGET(box_), align);
|
||||
gtk_widget_set_valign(GTK_WIDGET(box_), align);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GTKBox::set_homogeneous(bool homogeneous) {
|
||||
if (box_) {
|
||||
gtk_box_set_homogeneous(box_, homogeneous);
|
||||
}
|
||||
}
|
||||
|
||||
// 全局函数实现
|
||||
inline bool gtk_init(int* argc, char*** argv) {
|
||||
return gtk_init_check(argc, argv);
|
||||
}
|
||||
|
||||
inline void gtk_main() {
|
||||
::gtk_main();
|
||||
}
|
||||
|
||||
inline void gtk_main_quit() {
|
||||
::gtk_main_quit();
|
||||
}
|
||||
|
||||
inline void show_error_dialog(GtkWindow* parent, const std::string& title, const std::string& message) {
|
||||
GtkWidget* dialog = gtk_message_dialog_new(parent,
|
||||
GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_ERROR,
|
||||
GTK_BUTTONS_OK,
|
||||
"%s", message.c_str());
|
||||
gtk_window_set_title(GTK_WINDOW(dialog), title.c_str());
|
||||
gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
inline void show_info_dialog(GtkWindow* parent, const std::string& title, const std::string& message) {
|
||||
GtkWidget* dialog = gtk_message_dialog_new(parent,
|
||||
GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_INFO,
|
||||
GTK_BUTTONS_OK,
|
||||
"%s", message.c_str());
|
||||
gtk_window_set_title(GTK_WINDOW(dialog), title.c_str());
|
||||
gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
inline bool show_confirm_dialog(GtkWindow* parent, const std::string& title, const std::string& message) {
|
||||
GtkWidget* dialog = gtk_message_dialog_new(parent,
|
||||
GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_QUESTION,
|
||||
GTK_BUTTONS_YES_NO,
|
||||
"%s", message.c_str());
|
||||
gtk_window_set_title(GTK_WINDOW(dialog), title.c_str());
|
||||
gint response = gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
gtk_widget_destroy(dialog);
|
||||
return response == GTK_RESPONSE_YES;
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
} // namespace pmc
|
||||
|
||||
#endif // PMC_GUI_GTK_IMPL_HPP
|
||||
@@ -0,0 +1,174 @@
|
||||
# PMC GUI 模块 - GTK 封装
|
||||
|
||||
## 概述
|
||||
|
||||
本模块提供了对GTK库的C++封装,简化在pmc项目中使用GTK进行GUI开发的过程。封装提供了面向对象的接口,隐藏了GTK的C语言细节。
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
include/gui/
|
||||
├── GTK.hpp # 主封装头文件(类声明)
|
||||
├── GTK_impl.hpp # 内联实现文件
|
||||
├── gui.hpp # GUI模块主入口
|
||||
├── example.hpp # 使用示例
|
||||
└── README.md # 本文档
|
||||
```
|
||||
|
||||
## 主要类
|
||||
|
||||
### 1. GTKApplication
|
||||
封装GTK应用程序的初始化和运行。
|
||||
|
||||
```cpp
|
||||
pmc::gui::GTKApplication app("com.example.pmc", "PMC Application");
|
||||
app.initialize(argc, argv);
|
||||
app.run();
|
||||
```
|
||||
|
||||
### 2. GTKWindow
|
||||
封装GTK窗口的创建和管理。
|
||||
|
||||
```cpp
|
||||
pmc::gui::GTKWindow window("My Window", 800, 600);
|
||||
window.set_close_callback([]() {
|
||||
std::cout << "Window closing" << std::endl;
|
||||
gtk_main_quit();
|
||||
});
|
||||
window.show();
|
||||
```
|
||||
|
||||
### 3. GTKButton
|
||||
封装GTK按钮的创建和事件处理。
|
||||
|
||||
```cpp
|
||||
pmc::gui::GTKButton button("Click Me");
|
||||
button.set_click_callback([]() {
|
||||
std::cout << "Button clicked!" << std::endl;
|
||||
});
|
||||
```
|
||||
|
||||
### 4. GTKEntry
|
||||
封装GTK输入框的创建和文本管理。
|
||||
|
||||
```cpp
|
||||
pmc::gui::GTKEntry entry("Enter text here...");
|
||||
entry.set_activate_callback([]() {
|
||||
std::string text = entry.get_text();
|
||||
std::cout << "Entered: " << text << std::endl;
|
||||
});
|
||||
```
|
||||
|
||||
### 5. GTKTextView
|
||||
封装GTK文本视图的创建和文本管理。
|
||||
|
||||
```cpp
|
||||
pmc::gui::GTKTextView text_view;
|
||||
text_view.set_read_only(true);
|
||||
text_view.append_text("Hello, World!\n");
|
||||
```
|
||||
|
||||
### 6. GTKBox
|
||||
封装GTK盒子布局容器的创建和子控件管理。
|
||||
|
||||
```cpp
|
||||
pmc::gui::GTKBox vbox(GTK_ORIENTATION_VERTICAL, 5);
|
||||
vbox.add_widget(GTK_WIDGET(button.get_button()));
|
||||
vbox.add_widget(GTK_WIDGET(entry.get_entry()));
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 简单聊天窗口
|
||||
|
||||
```cpp
|
||||
#include "gui/example.hpp"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
pmc::gui::example::run_simple_chat_example(argc, argv);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 设置窗口
|
||||
|
||||
```cpp
|
||||
#include "gui/example.hpp"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
pmc::gui::example::run_settings_example(argc, argv);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 编译要求
|
||||
|
||||
### 依赖项
|
||||
- GTK+ 3.0 或更高版本
|
||||
- C++11 或更高版本
|
||||
|
||||
### Ubuntu/Debian 安装依赖
|
||||
```bash
|
||||
sudo apt-get install libgtk-3-dev
|
||||
```
|
||||
|
||||
### Fedora/RHEL 安装依赖
|
||||
```bash
|
||||
sudo dnf install gtk3-devel
|
||||
```
|
||||
|
||||
### 编译命令示例
|
||||
```bash
|
||||
g++ -std=c++11 -o myapp main.cpp `pkg-config --cflags --libs gtk+-3.0`
|
||||
```
|
||||
|
||||
## 集成到pmc项目
|
||||
|
||||
### 1. 在CMakeLists.txt中添加
|
||||
```cmake
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
|
||||
|
||||
include_directories(${GTK3_INCLUDE_DIRS})
|
||||
target_link_libraries(your_target ${GTK3_LIBRARIES})
|
||||
```
|
||||
|
||||
### 2. 使用封装类
|
||||
```cpp
|
||||
#include "gui/GTK.hpp"
|
||||
|
||||
// 使用pmc::gui命名空间中的类
|
||||
```
|
||||
|
||||
## 设计原则
|
||||
|
||||
1. **简化接口**:隐藏GTK的C语言细节,提供C++面向对象接口
|
||||
2. **资源管理**:使用RAII原则管理GTK对象生命周期
|
||||
3. **类型安全**:使用C++类型系统减少错误
|
||||
4. **可扩展性**:易于添加新的封装类
|
||||
5. **向后兼容**:保持与原生GTK API的互操作性
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **线程安全**:GTK不是线程安全的,所有GUI操作必须在主线程进行
|
||||
2. **内存管理**:GTK使用引用计数,封装类会自动处理引用
|
||||
3. **信号连接**:使用lambda表达式简化信号处理
|
||||
4. **错误处理**:提供基本的错误检查和对话框支持
|
||||
|
||||
## 扩展指南
|
||||
|
||||
要添加新的GTK控件封装:
|
||||
|
||||
1. 在`GTK.hpp`中声明类
|
||||
2. 在`GTK_impl.hpp`中实现内联方法
|
||||
3. 提供构造函数、析构函数和主要方法
|
||||
4. 添加信号连接支持
|
||||
5. 更新示例和文档
|
||||
|
||||
## 许可证
|
||||
|
||||
本模块遵循pmc项目的整体许可证。
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提交问题和拉取请求改进本模块。
|
||||
@@ -0,0 +1,100 @@
|
||||
#ifndef PMC_GUI_HPP
|
||||
#define PMC_GUI_HPP
|
||||
|
||||
/**
|
||||
* @file gui.hpp
|
||||
* @brief pmc项目GUI模块主入口
|
||||
*
|
||||
* 提供GTK GUI模块的统一入口,简化GUI开发。
|
||||
*/
|
||||
|
||||
#include "gui/GTK.hpp"
|
||||
|
||||
namespace pmc {
|
||||
namespace gui {
|
||||
|
||||
/**
|
||||
* @brief 初始化GUI模块
|
||||
* @param argc 命令行参数数量
|
||||
* @param argv 命令行参数数组
|
||||
* @return 初始化是否成功
|
||||
*/
|
||||
bool init(int argc, char** argv);
|
||||
|
||||
/**
|
||||
* @brief 运行GUI主循环
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* @brief 退出GUI主循环
|
||||
*/
|
||||
void quit();
|
||||
|
||||
/**
|
||||
* @brief 创建主窗口
|
||||
* @param title 窗口标题
|
||||
* @param width 窗口宽度
|
||||
* @param height 窗口高度
|
||||
* @return 窗口指针
|
||||
*/
|
||||
GTKWindow* create_main_window(const std::string& title = "PMC Application",
|
||||
int width = 800, int height = 600);
|
||||
|
||||
/**
|
||||
* @brief 创建聊天窗口
|
||||
* @param parent 父窗口
|
||||
* @return 聊天窗口指针
|
||||
*/
|
||||
GTKWindow* create_chat_window(GTKWindow* parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief 创建设置窗口
|
||||
* @param parent 父窗口
|
||||
* @return 设置窗口指针
|
||||
*/
|
||||
GTKWindow* create_settings_window(GTKWindow* parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief 显示关于对话框
|
||||
* @param parent 父窗口
|
||||
*/
|
||||
void show_about_dialog(GTKWindow* parent);
|
||||
|
||||
/**
|
||||
* @brief 显示状态栏
|
||||
* @param parent 父窗口
|
||||
* @param message 状态消息
|
||||
*/
|
||||
void show_status_bar(GTKWindow* parent, const std::string& message);
|
||||
|
||||
/**
|
||||
* @brief 更新状态栏消息
|
||||
* @param message 新状态消息
|
||||
*/
|
||||
void update_status_bar(const std::string& message);
|
||||
|
||||
/**
|
||||
* @brief 显示进度对话框
|
||||
* @param parent 父窗口
|
||||
* @param title 对话框标题
|
||||
* @param message 进度消息
|
||||
*/
|
||||
void show_progress_dialog(GTKWindow* parent, const std::string& title, const std::string& message);
|
||||
|
||||
/**
|
||||
* @brief 更新进度对话框
|
||||
* @param progress 进度值 (0.0-1.0)
|
||||
* @param message 进度消息
|
||||
*/
|
||||
void update_progress_dialog(double progress, const std::string& message);
|
||||
|
||||
/**
|
||||
* @brief 关闭进度对话框
|
||||
*/
|
||||
void close_progress_dialog();
|
||||
|
||||
} // namespace gui
|
||||
} // namespace pmc
|
||||
|
||||
#endif // PMC_GUI_HPP
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
/* Camera */
|
||||
#define cimg_display 0 /* 不显示图像,只保存 */
|
||||
#include "CImg.h"
|
||||
|
||||
void Camera_init(int w, int h);
|
||||
void Camera_close();
|
||||
bool Camera_is_open();
|
||||
|
||||
/* 在线程安全的情况下取得一帧图像 */
|
||||
cimg_library::CImg<unsigned char> get_frame_from_camera(bool& flag);
|
||||
@@ -0,0 +1,241 @@
|
||||
#pragma once
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <unistd.h>
|
||||
#include "cn/中文化.hpp"
|
||||
#include "Dashun_linux.hpp"
|
||||
命名空间 qing{
|
||||
类 伺服电机{ /* 180度舵机 */
|
||||
公开的:
|
||||
|
||||
/* FIXME: min只能比max小 */
|
||||
伺服电机(整数 通道, 单精度小数 上限=180, 单精度小数 下限=0, 逻辑型 逆向=false)
|
||||
: 通道(通道), 上限(上限), 下限(下限), 逆向(逆向) {}
|
||||
|
||||
/* 控制舵机运行至一个角度 */
|
||||
空类型 转动至(单精度小数 角度) {
|
||||
角度 = std::min(上限, 角度);
|
||||
角度 = std::max(下限, 角度);
|
||||
PCA9685_调整角度(
|
||||
通道, (逆向) ? 180 - 角度 : 角度
|
||||
);
|
||||
}
|
||||
|
||||
/* FIXME: 返回最大值 */
|
||||
单精度小数 取上限() {
|
||||
返回 上限;
|
||||
}
|
||||
|
||||
|
||||
私有的:
|
||||
整数 通道; /* 通道编号 */
|
||||
单精度小数 上限; /* 角度上限 */
|
||||
单精度小数 下限; /* 角度下限 */
|
||||
逻辑型 逆向; /* 是否反向 */
|
||||
};
|
||||
}
|
||||
|
||||
命名空间 qing {
|
||||
类 ActParser{ /* 动作脚本解析器 */
|
||||
公开的:
|
||||
|
||||
/* 解析标准输入中的动作脚本 */
|
||||
空类型 fromStdin() {
|
||||
计数 = 0;
|
||||
清除所有();
|
||||
解析(std::cin);
|
||||
}
|
||||
|
||||
/* 解析文件输入流中的动作脚本 */
|
||||
空类型 fromFile(const std::string& 路径) {
|
||||
计数 = 0;
|
||||
std::ifstream 文件(路径.c_str(), std::ios::in);
|
||||
如果 (!文件.is_open())
|
||||
抛出异常 std::runtime_error("打开文件失败");
|
||||
清除所有();
|
||||
解析(文件);
|
||||
文件.close();
|
||||
}
|
||||
|
||||
/* 解析字符串流中的动作脚本 */
|
||||
空类型 fromStr(const std::string& 脚本) {
|
||||
std::stringstream 流(脚本);
|
||||
计数 = 0;
|
||||
清除所有();
|
||||
解析(流);
|
||||
}
|
||||
|
||||
/* 执行刚刚解析出来的机器人和动作 */
|
||||
空类型 play() {
|
||||
如果 (机组.empty() || 矩阵.empty())
|
||||
抛出异常 std::runtime_error("没动作或机组");
|
||||
机器人扮演(机组, 矩阵);
|
||||
}
|
||||
|
||||
/* 获得机器人的最大值向量, FIXME: 把逻辑写在外面 */
|
||||
std::vector<float> get() {
|
||||
适应 结果 = std::vector<float>();
|
||||
计次循环 (适应& 项目: this->机组) {
|
||||
结果.push_back(项目.取上限());
|
||||
}
|
||||
/* 这个数组中包含机器人每个舵机的角度上限 */
|
||||
返回 结果;
|
||||
}
|
||||
|
||||
/* 获取当前这一组动作 */
|
||||
std::vector<float> now() {
|
||||
如果 (计数 >= 矩阵.size())
|
||||
抛出异常 std::runtime_error("now finished");
|
||||
返回 矩阵[计数];
|
||||
}
|
||||
|
||||
/* 执行下一组动作 */
|
||||
空类型 next() {
|
||||
如果 (计数 >= 矩阵.size())
|
||||
抛出异常 std::runtime_error("没下一个动作了");
|
||||
适应 临时向量 = 矩阵[计数++];
|
||||
适应 临时矩阵 = std::vector<std::vector<float>> ({临时向量});
|
||||
机器人扮演(机组, 临时矩阵);
|
||||
}
|
||||
|
||||
/* 输入下一个动作执行 */
|
||||
空类型 next(std::vector<float>& act) {
|
||||
计数++;
|
||||
auto vv = std::vector<std::vector<float>> ({act});
|
||||
机器人扮演(机组, vv);
|
||||
}
|
||||
private:
|
||||
字符 句[16] = {0}; /* FIXME: 这个栈有越界风险 */
|
||||
整数 顶 = 0;
|
||||
整数 计数 = 0;
|
||||
std::vector<float> 数组; /* 临时使用 */
|
||||
std::vector<std::vector<float>> 矩阵; /* 解析出的整套动作 */
|
||||
std::vector<伺服电机> 机组; /* 机器人抽象 */
|
||||
空类型 解析(std::istream& 源) { /* NOTE: 修改为非静态成员函数能够减少行数 FIXME: 改为使用C++风格的文件操作 */
|
||||
清除临时();
|
||||
字符 c = 0;
|
||||
布尔 end_flag = true;
|
||||
布尔 comment_flag = false;
|
||||
布尔 obj_flag = false;
|
||||
判断循环 (end_flag) { /* 255 for arm */
|
||||
end_flag = (bool)源.get(c);
|
||||
//std::cout<<c;
|
||||
如果 ((!comment_flag && c >= '0' && c <= '9') || c == '+' || c == '-') { /* 读取数字 */
|
||||
句[顶++] = c;
|
||||
std::cout<<c;
|
||||
}
|
||||
否则 如果 (!comment_flag && c == ' ') { /* 每个角度以空格结尾 */
|
||||
句[顶] = '\0';
|
||||
递交舵机();
|
||||
}
|
||||
否则 如果 (!comment_flag && (c == '\r' || c == '\n')) { /* 忽略回车 */
|
||||
;
|
||||
}
|
||||
否则 如果 (!comment_flag && c == '#') { /* 注释以#开头 */
|
||||
comment_flag = 真;
|
||||
}
|
||||
否则 如果 (comment_flag && c == '\n') { /* 注释以换行结尾 */
|
||||
comment_flag = 假;
|
||||
}
|
||||
否则 如果 (comment_flag && c != '\n') { /* 忽略注释 */
|
||||
;
|
||||
}
|
||||
否则 如果 (!comment_flag && !obj_flag && 顶 == 0 && c == '!') { /* 构建模式 */
|
||||
obj_flag = 真;
|
||||
}
|
||||
否则 如果 (!comment_flag && obj_flag && (c==';' || end_flag)) { /* 构建机器人 */
|
||||
句[顶] = '\0';
|
||||
递交舵机();
|
||||
递交机组();
|
||||
obj_flag = 假;
|
||||
}
|
||||
否则 如果 (!comment_flag && !obj_flag && (c == ';' || end_flag)) { /* 每个动作以分号结尾 */
|
||||
句[顶] = '\0';
|
||||
递交舵机();
|
||||
递交动作();
|
||||
}
|
||||
否则 {
|
||||
抛出异常 std::runtime_error(
|
||||
"Invalid action: " + std::to_string(int(c)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
空类型 递交舵机() {
|
||||
如果 (顶 > 0) {
|
||||
单精度小数 角度 = (单精度小数)atoi(句);
|
||||
数组.push_back(角度);
|
||||
std::cout<<角度<<std::endl;
|
||||
}
|
||||
句[0] = '\0';
|
||||
顶 = 0;
|
||||
}
|
||||
|
||||
空类型 递交动作() {
|
||||
如果 (!数组.empty()) {
|
||||
std::cout<<"接收到动作: " << 数组.size() << std::endl;
|
||||
矩阵.push_back(数组);
|
||||
数组 = std::vector<float>{};
|
||||
}
|
||||
}
|
||||
|
||||
空类型 递交机组() {
|
||||
如果 (!数组.empty()) {
|
||||
std::cout << "接收到机组: " << 数组.size() << std::endl;
|
||||
机组 = 机组工厂(数组);
|
||||
数组 = std::vector<float>{};
|
||||
}
|
||||
}
|
||||
|
||||
空类型 清除临时() {
|
||||
句[0] = '\0';
|
||||
顶 = 0;
|
||||
数组 = std::vector<float> {};
|
||||
}
|
||||
|
||||
空类型 清除所有() {
|
||||
句[0] = '\0';
|
||||
顶 = 0;
|
||||
数组 = std::vector<float> {};
|
||||
矩阵 = std::vector<std::vector<float>> {};
|
||||
机组 = std::vector<伺服电机> {};
|
||||
}
|
||||
|
||||
/* 机器人工厂,FIXME: 这里可以做成舵机工厂就可以了, */
|
||||
静态的 std::vector<伺服电机> 机组工厂(const std::vector<float>& 上限集) {
|
||||
std::vector<伺服电机> 机组;
|
||||
计次循环 (整数 甲=0; 甲 < 上限集.size(); ++甲)
|
||||
机组.push_back(
|
||||
伺服电机(甲, std::abs(上限集[甲]), 0, (上限集[甲] >= 0) ? 假 : 真));
|
||||
返回 机组;
|
||||
}
|
||||
|
||||
/* 机器人移动,输入一组舵机对象和一组目标角度 */
|
||||
静态的 空类型 机器人行动(std::vector<伺服电机>& 机组, std::vector<float>& 动作) {
|
||||
如果 (机组.size() + 1 != 动作.size())
|
||||
抛出异常 std::runtime_error(
|
||||
"动作与机组不符合, 我们有"
|
||||
+ std::to_string(机组.size()) + "和" + std::to_string(动作.size())
|
||||
);
|
||||
|
||||
/* FIXME: 随机下标以消除先后时间差 */
|
||||
计次循环 (int 甲=0; 甲<机组.size(); ++甲) {
|
||||
机组[甲].转动至(动作[甲]);
|
||||
}
|
||||
/* 最后一组数据是毫秒延时 */
|
||||
预求值的 整数 缩放 = 1000;
|
||||
usleep(动作[动作.size()-1] * 缩放);
|
||||
}
|
||||
|
||||
/* 机器人整套动作 */
|
||||
静态的 空类型 机器人扮演(
|
||||
std::vector<伺服电机>& 机组, std::vector<std::vector<float>>& 动作集)
|
||||
{
|
||||
计次循环 (适应 动作: 动作集)
|
||||
机器人行动(机组, 动作);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 这是cpp使用linux C手段读写串口的实现。
|
||||
*
|
||||
* 作者在其中加入了“沟通”手段,这种手段主要是针对COPS设备。
|
||||
*
|
||||
* COPS设备的通信手段主要是上位机问,下位机答,一问一答的形式。
|
||||
*
|
||||
* (!)还未进行对于热插拔的支持。
|
||||
*
|
||||
* 20250621 暂时不用了
|
||||
*/
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <regex>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <cstdio>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
#include <sys/time.h>
|
||||
namespace qing {
|
||||
/*
|
||||
* 串口类
|
||||
*
|
||||
* 长连接的速度会更快
|
||||
*/
|
||||
class Serial {
|
||||
private:
|
||||
/* 串口文件描述符 */
|
||||
int fd = 0;
|
||||
public:
|
||||
/* 输入设备文件地址,进行构造。打开后通常需要等待一下 */
|
||||
Serial(const std::string& path, double wait = 2) {
|
||||
fd = open(path.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
|
||||
if (fd == -1) {
|
||||
throw DeviceOpenErroring();
|
||||
}
|
||||
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds((long)(wait*1000))); /* 等待串口 */
|
||||
}
|
||||
//释放
|
||||
~Serial() {
|
||||
if (fd != -1) close(fd);
|
||||
}
|
||||
|
||||
/* 设置串口 */
|
||||
void Set() {
|
||||
struct termios options;
|
||||
tcgetattr(fd, &options);
|
||||
cfsetispeed(&options, B115200); /* 设置波特率 */
|
||||
cfsetospeed(&options, B115200);
|
||||
options.c_cflag |= (CLOCAL | CREAD);// 启用接收和本地模式
|
||||
options.c_cflag &= ~PARENB;// 8位数据位,无校验,1位停止位
|
||||
options.c_cflag &= ~CSTOPB;
|
||||
options.c_cflag &= ~CSIZE;
|
||||
options.c_cflag |= CS8;
|
||||
options.c_cflag &= ~CRTSCTS;// 禁用硬件流控
|
||||
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);// 原始输入模式
|
||||
options.c_oflag &= ~OPOST;// 原始输出模式
|
||||
options.c_cc[VMIN] = 0;// 设置超时 - 1秒
|
||||
options.c_cc[VTIME] = 10;
|
||||
tcsetattr(fd, TCSANOW, &options);
|
||||
tcflush(fd, TCIOFLUSH); /* 清空缓冲区 */
|
||||
}
|
||||
|
||||
/* 写数据 每次写之前清空缓冲区数据*/
|
||||
void Write(const std::string& msg) {
|
||||
tcflush(fd, TCIOFLUSH); /* 清空缓冲区 */
|
||||
write(fd, msg.c_str(), msg.size());
|
||||
}
|
||||
|
||||
/* 读数据 */
|
||||
std::string CircleRead() {
|
||||
char buffer[1024] = {0};
|
||||
std::string ret = "";
|
||||
ssize_t n;
|
||||
do {
|
||||
n = read(fd, buffer, sizeof(buffer));
|
||||
if (n == -1 && ret == "") {
|
||||
throw NoResponse();
|
||||
} else if (n == -1 && ret != "") {
|
||||
return ret;
|
||||
} else if (n == 0) {
|
||||
return ret;
|
||||
} else if (n > 0) {
|
||||
buffer[n] = '\0';
|
||||
ret += buffer;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
/* 读数据 */
|
||||
std::string Read() {
|
||||
char buffer[1024] = {0};
|
||||
std::string ret = "";
|
||||
ssize_t n;
|
||||
n = read(fd, buffer, sizeof(buffer));
|
||||
if (n == -1 && ret == "") {
|
||||
throw NoResponse();
|
||||
} else if (n > 0) {
|
||||
buffer[n] = '\0';
|
||||
ret += buffer;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* 发送然后获取 */
|
||||
std::string Send_Wait_And_Get(const std::string& snd, double wait) {
|
||||
Write(snd);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds((long)(wait*1000)));
|
||||
return Read();
|
||||
}
|
||||
|
||||
/* 一问一答,尝试一定次数 */
|
||||
std::string Try_Comm(const std::string& snd, double wait, int times = 10, int drop_tail = 0) {
|
||||
int num = 0;
|
||||
while (true) {
|
||||
try {
|
||||
num ++;
|
||||
if (num > times) break;
|
||||
std::string msg = Send_Wait_And_Get(snd, wait);
|
||||
DropTail(msg, drop_tail);
|
||||
return msg;
|
||||
} catch (qing::Serial::Serial_Error e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
throw NoResponse();
|
||||
}
|
||||
|
||||
/* 去掉字符串末尾一定数量的字符 */
|
||||
static void DropTail(std::string& msg, int n) {
|
||||
for (int i=0; i<n; i++) {
|
||||
msg.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
/* 串口异常类 */
|
||||
class Serial_Error: public std::exception {
|
||||
private:
|
||||
std::string message;
|
||||
public:
|
||||
explicit Serial_Error(const std::string& msg): message(msg) {}
|
||||
const char* what() const noexcept override {
|
||||
return message.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
/* 打开设备异常 */
|
||||
class DeviceOpenErroring: public Serial_Error {
|
||||
public:
|
||||
DeviceOpenErroring(): Serial_Error("Device Open Erroring") {}
|
||||
};
|
||||
|
||||
/* select异常 */
|
||||
class SelectErroring: public Serial_Error {
|
||||
public:
|
||||
SelectErroring(): Serial_Error("Select Erroring") {}
|
||||
};
|
||||
|
||||
/* 无响应 */
|
||||
class NoResponse: public Serial_Error {
|
||||
public:
|
||||
NoResponse(): Serial_Error(" No Response") {}
|
||||
};
|
||||
|
||||
/* 无响应异常 */
|
||||
class SerialReadError: public Serial_Error {
|
||||
public:
|
||||
SerialReadError(): Serial_Error("Serial Read Error") {}
|
||||
};
|
||||
|
||||
/* 获取所有的串口 */
|
||||
static std::vector<std::string> GetPorts() {
|
||||
std::vector<std::string> ports;
|
||||
//Linux实现
|
||||
DIR *dir;
|
||||
struct dirent *ent;
|
||||
const std::regex serial_regex("(ttyS|ttyUSB|ttyACM)[0-9]+");
|
||||
if ((dir = opendir("/dev")) != NULL) {
|
||||
while((ent = readdir(dir)) != NULL) {
|
||||
std::string name = ent->d_name;
|
||||
if (std::regex_match(name, serial_regex)) {
|
||||
ports.push_back("/dev/"+name);
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
return ports;
|
||||
}
|
||||
|
||||
/* 以线程不安全的方式打印所有的串口 */
|
||||
static void UnsafePrintPorts() {
|
||||
auto v = GetPorts();
|
||||
for (const auto& i: v) {
|
||||
std::cout << i << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <unistd.h>
|
||||
|
||||
struct CpuData {
|
||||
std::string cpu_name;
|
||||
long user;
|
||||
long nice;
|
||||
long system;
|
||||
long idle;
|
||||
long iowait;
|
||||
long irq;
|
||||
long softirq;
|
||||
long steal;
|
||||
long guest;
|
||||
long guest_nice;
|
||||
|
||||
long total() const {
|
||||
return user + nice + system + idle + iowait + irq + softirq + steal;
|
||||
}
|
||||
|
||||
long idle_total() const {
|
||||
return idle + iowait;
|
||||
}
|
||||
};
|
||||
|
||||
class CpuMonitor {
|
||||
private:
|
||||
std::vector<CpuData> read_cpu_stats() {
|
||||
std::vector<CpuData> stats;
|
||||
std::ifstream file("/proc/stat");
|
||||
std::string line;
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
if (line.substr(0, 3) == "cpu") {
|
||||
std::istringstream iss(line);
|
||||
CpuData data;
|
||||
iss >> data.cpu_name >> data.user >> data.nice >> data.system
|
||||
>> data.idle >> data.iowait >> data.irq >> data.softirq
|
||||
>> data.steal >> data.guest >> data.guest_nice;
|
||||
stats.push_back(data);
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public:
|
||||
double get_cpu_usage() {
|
||||
auto stats1 = read_cpu_stats();
|
||||
sleep(1);
|
||||
auto stats2 = read_cpu_stats();
|
||||
|
||||
if (stats1.empty() || stats2.empty()) {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
long total_diff = stats2[0].total() - stats1[0].total();
|
||||
long idle_diff = stats2[0].idle_total() - stats1[0].idle_total();
|
||||
|
||||
if (total_diff == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return 100.0 * (total_diff - idle_diff) / total_diff;
|
||||
}
|
||||
|
||||
std::vector<double> get_per_cpu_usage() {
|
||||
auto stats1 = read_cpu_stats();
|
||||
sleep(1);
|
||||
auto stats2 = read_cpu_stats();
|
||||
|
||||
std::vector<double> usages;
|
||||
|
||||
for (size_t i = 1; i < stats1.size() && i < stats2.size(); i++) {
|
||||
long total_diff = stats2[i].total() - stats1[1].total();
|
||||
long idle_diff = stats2[i].idle_total() - stats1[i].idle_total();
|
||||
|
||||
if (total_diff > 0) {
|
||||
usages.push_back(100.0 * (total_diff - idle_diff) / total_diff);
|
||||
} else {
|
||||
usages.push_back(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
return usages;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef HD_MEM_H
|
||||
#define HD_MEM_H
|
||||
#include <iostream>
|
||||
#include <sys/sysinfo.h>
|
||||
|
||||
struct MemoryInfo {
|
||||
long totalMemory; /* 总内存(bytes) */
|
||||
long freeMemory; /* 空闲内存 */
|
||||
long usedMemory; /* 已用内存 */
|
||||
double usageRate; /* 使用率(%) */
|
||||
};
|
||||
|
||||
MemoryInfo getMemoryUsageWithSysinfo();
|
||||
#endif
|
||||
@@ -0,0 +1,106 @@
|
||||
#ifndef PMC_LLM_CLIENT_HPP
|
||||
#define PMC_LLM_CLIENT_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include "net/HttpClient.hpp"
|
||||
|
||||
namespace pmc::llm {
|
||||
|
||||
class LLMClient {
|
||||
public:
|
||||
LLMClient(const std::string& api_key) : api_key_(api_key) {}
|
||||
|
||||
// 流式请求(使用同步版本避免线程死锁)
|
||||
void stream_request(const std::vector<std::pair<std::string, std::string>>& messages,
|
||||
std::function<void(const std::string&)> callback) {
|
||||
// 使用智能指针确保HttpClient在回调期间保持存活
|
||||
auto http_client = std::make_shared<pmc::net::HttpClient>();
|
||||
|
||||
// 设置请求头
|
||||
http_client->set_header("Authorization: Bearer " + api_key_);
|
||||
http_client->set_header("Content-Type: application/json");
|
||||
http_client->set_header("Accept: text/event-stream");
|
||||
|
||||
// 构建请求JSON
|
||||
boost::json::object request;
|
||||
request["model"] = "deepseek-chat";
|
||||
request["stream"] = true;
|
||||
|
||||
// 构建消息数组,添加系统消息要求返回Markdown格式
|
||||
boost::json::array messages_array;
|
||||
|
||||
// 添加系统消息,要求使用Markdown格式回复
|
||||
boost::json::object system_message;
|
||||
system_message["role"] = "system";
|
||||
system_message["content"] = "请使用Markdown格式回复。使用**粗体**表示重要概念,*斜体*表示强调,`代码`表示代码片段,使用列表、标题等Markdown元素来组织内容。";
|
||||
messages_array.push_back(system_message);
|
||||
|
||||
// 添加用户消息
|
||||
for (const auto& msg : messages) {
|
||||
boost::json::object message_obj;
|
||||
message_obj["role"] = msg.first;
|
||||
message_obj["content"] = msg.second;
|
||||
messages_array.push_back(message_obj);
|
||||
}
|
||||
request["messages"] = messages_array;
|
||||
|
||||
// 转换为字符串
|
||||
std::string request_body = boost::json::serialize(request);
|
||||
|
||||
// 创建包装回调,解析JSON并提取文本内容
|
||||
auto wrapped_callback = [callback](const std::string& json_str) {
|
||||
try {
|
||||
// 解析JSON
|
||||
boost::json::value json_value = boost::json::parse(json_str);
|
||||
|
||||
// 检查是否是有效的响应
|
||||
if (json_value.is_object()) {
|
||||
const auto& obj = json_value.as_object();
|
||||
|
||||
// 检查是否有choices数组
|
||||
if (obj.contains("choices") && obj.at("choices").is_array()) {
|
||||
const auto& choices = obj.at("choices").as_array();
|
||||
if (!choices.empty() && choices[0].is_object()) {
|
||||
const auto& choice = choices[0].as_object();
|
||||
|
||||
// 检查是否有delta对象
|
||||
if (choice.contains("delta") && choice.at("delta").is_object()) {
|
||||
const auto& delta = choice.at("delta").as_object();
|
||||
|
||||
// 检查是否有content字段
|
||||
if (delta.contains("content") && delta.at("content").is_string()) {
|
||||
const std::string& content = delta.at("content").as_string().c_str();
|
||||
callback(content);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果JSON解析失败或没有有效内容,发送原始字符串(用于调试)
|
||||
callback("[" + json_str + "]");
|
||||
} catch (const std::exception& e) {
|
||||
// JSON解析失败,发送原始字符串
|
||||
callback("[" + json_str + "]");
|
||||
}
|
||||
};
|
||||
|
||||
// 发送POST请求 - 使用post_stream_sync方法
|
||||
http_client->post_stream_sync("https://api.deepseek.com/v1/chat/completions", request_body, wrapped_callback);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string api_key_;
|
||||
};
|
||||
|
||||
} // namespace pmc::llm
|
||||
|
||||
#endif // PMC_LLM_CLIENT_HPP
|
||||
@@ -0,0 +1,133 @@
|
||||
#ifndef PMC_LLM_WEBSOCKET_SERVER_HPP
|
||||
#define PMC_LLM_WEBSOCKET_SERVER_HPP
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <atomic>
|
||||
|
||||
#include "LLMClient.hpp"
|
||||
#include "net/WebSocketServer.hpp"
|
||||
|
||||
namespace pmc::llm {
|
||||
|
||||
class LLMWebSocketServer {
|
||||
public:
|
||||
LLMWebSocketServer(const std::string& api_key, unsigned short port = 8080)
|
||||
: api_key_(api_key)
|
||||
, port_(port)
|
||||
, running_(false) {
|
||||
|
||||
// 创建WebSocket服务器
|
||||
ws_server_ = std::make_unique<pmc::net::WebSocketServer>(port);
|
||||
}
|
||||
|
||||
~LLMWebSocketServer() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "启动LLM WebSocket服务器,端口: " << port_ << std::endl;
|
||||
std::cout << "API密钥: " << (api_key_.empty() ? "未设置" : "已设置") << std::endl;
|
||||
|
||||
// 设置WebSocket回调
|
||||
ws_server_->onConnect([this](const std::string& client_id, const std::string& remote_address) {
|
||||
handle_connect(client_id, remote_address);
|
||||
});
|
||||
|
||||
ws_server_->onMessage("/", [this](const std::string& client_id,
|
||||
const std::string& message,
|
||||
std::function<void(const std::string&)> send_callback) {
|
||||
// 这里我们使用异步发送,所以忽略send_callback参数
|
||||
handle_message(client_id, message);
|
||||
});
|
||||
|
||||
ws_server_->onDisconnect([this](const std::string& client_id) {
|
||||
handle_disconnect(client_id);
|
||||
});
|
||||
|
||||
// 启动WebSocket服务器
|
||||
ws_server_->start();
|
||||
|
||||
running_ = true;
|
||||
|
||||
std::cout << "服务器已启动,等待客户端连接..." << std::endl;
|
||||
std::cout << "按Ctrl+C停止服务器" << std::endl;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (!running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "正在停止服务器..." << std::endl;
|
||||
ws_server_->stop();
|
||||
running_ = false;
|
||||
}
|
||||
|
||||
bool is_running() const {
|
||||
return running_;
|
||||
}
|
||||
|
||||
private:
|
||||
// 处理连接
|
||||
void handle_connect(const std::string& client_id, const std::string& remote_address) {
|
||||
std::cout << "客户端连接: " << client_id << " (" << remote_address << ")" << std::endl;
|
||||
|
||||
// 使用异步线程发送欢迎消息,确保客户端已准备好
|
||||
std::thread([this, client_id]() {
|
||||
// 等待一小段时间,确保客户端连接完全建立
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// 发送欢迎消息
|
||||
ws_server_->sendToClient(client_id, "欢迎连接到LLM WebSocket服务器!");
|
||||
ws_server_->sendToClient(client_id, "请发送您的消息,我将为您提供AI助手服务。");
|
||||
}).detach();
|
||||
}
|
||||
|
||||
// 处理断开连接
|
||||
void handle_disconnect(const std::string& client_id) {
|
||||
std::cout << "客户端断开: " << client_id << std::endl;
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
void handle_message(const std::string& client_id, const std::string& message) {
|
||||
std::cout << "收到来自客户端 " << client_id << " 的消息: " << message << std::endl;
|
||||
|
||||
// 创建LLM客户端
|
||||
LLMClient llm_client(api_key_);
|
||||
|
||||
// 构建消息历史
|
||||
std::vector<std::pair<std::string, std::string>> messages = {
|
||||
{"user", message}
|
||||
};
|
||||
|
||||
// 发送流式请求
|
||||
llm_client.stream_request(messages, [this, client_id](const std::string& chunk) {
|
||||
// 将每个数据块发送给客户端
|
||||
ws_server_->sendToClient(client_id, chunk);
|
||||
});
|
||||
|
||||
// 发送处理完成标记
|
||||
ws_server_->sendToClient(client_id, "[处理完成]");
|
||||
}
|
||||
|
||||
private:
|
||||
std::string api_key_;
|
||||
unsigned short port_;
|
||||
bool running_;
|
||||
std::unique_ptr<pmc::net::WebSocketServer> ws_server_;
|
||||
};
|
||||
|
||||
} // namespace pmc::llm
|
||||
|
||||
#endif // PMC_LLM_WEBSOCKET_SERVER_HPP
|
||||
@@ -0,0 +1,107 @@
|
||||
/* 日志 */
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <ctime>
|
||||
|
||||
enum class LogLevel {
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR,
|
||||
CRITICAL
|
||||
};
|
||||
|
||||
namespace qing {
|
||||
/*
|
||||
* 这是由Deepseek创作的,用于记录日志的类模块
|
||||
* 简化版本,只输出到控制台
|
||||
*
|
||||
* 优化意见:改为存储日志文件
|
||||
*/
|
||||
class Logger {
|
||||
public:
|
||||
//获取日志实例(单例模式)
|
||||
static Logger& getInstance() {
|
||||
static Logger instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
//初始化日志系统
|
||||
void init(LogLevel minLogLevel = LogLevel::INFO,
|
||||
bool consoleOutput = true) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
minLogLevel_ = minLogLevel;
|
||||
consoleOutput_ = consoleOutput;
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
// 设置最低日志级别
|
||||
void setMinLogLevel(LogLevel level) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
minLogLevel_ = level;
|
||||
}
|
||||
|
||||
// 日志输出方法
|
||||
void log(LogLevel level, const std::string& message) {
|
||||
if (level < minLogLevel_ || !consoleOutput_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto in_time_t = std::chrono::system_clock::to_time_t(now);
|
||||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()) % 1000;
|
||||
|
||||
std::tm tm;
|
||||
localtime_r(&in_time_t, &tm);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S")
|
||||
<< "." << std::setfill('0') << std::setw(3) << ms.count()
|
||||
<< " [" << logLevelToString(level) << "] "
|
||||
<< message;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::cout << ss.str() << std::endl;
|
||||
}
|
||||
|
||||
Logger(const Logger&) = delete;
|
||||
Logger& operator=(const Logger&) = delete;
|
||||
|
||||
private:
|
||||
Logger() = default;
|
||||
~Logger() = default;
|
||||
|
||||
std::string logLevelToString(LogLevel level) {
|
||||
switch (level) {
|
||||
case LogLevel::DEBUG: return "DEBUG";
|
||||
case LogLevel::INFO: return "INFO";
|
||||
case LogLevel::WARNING: return "WARNING";
|
||||
case LogLevel::ERROR: return "ERROR";
|
||||
case LogLevel::CRITICAL: return "CRITICAL";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
std::mutex mutex_;
|
||||
LogLevel minLogLevel_ = LogLevel::INFO;
|
||||
bool consoleOutput_ = true;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
}
|
||||
|
||||
#define LOG_DEBUG(message) qing::Logger::getInstance().log(LogLevel::DEBUG, message)
|
||||
#define LOG_INFO(message) qing::Logger::getInstance().log(LogLevel::INFO, message)
|
||||
#define LOG_WARNING(message) qing::Logger::getInstance().log(LogLevel::WARNING, message)
|
||||
#define LOG_ERROR(message) qing::Logger::getInstance().log(LogLevel::ERROR, message)
|
||||
#define LOG_CRITICAL(message) qing::Logger::getInstance().log(LogLevel::CRITICAL, message)
|
||||
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
#include <httplib.h> // libcpp-httplib-dev
|
||||
#include <nlohmann/json.hpp> //
|
||||
#include <iostream>
|
||||
#include <filesystem> //
|
||||
#include <stdexcept>
|
||||
namespace fs = std::filesystem;
|
||||
/* 这里的类型是HTTP服务器要设置的回调函数的类型 */
|
||||
using Handler = std::function<void(const httplib::Request&, httplib::Response&)>;
|
||||
namespace qing {
|
||||
/* NOTE: 不要删除该类,这个抽象成能够方便重构 */
|
||||
class Http { /* 超文本传输协议(HTTP)服务器类 */
|
||||
public:
|
||||
/* 初始化各个变量 */
|
||||
Http(std::string addr, int port): addr(addr), port(port) {}
|
||||
|
||||
/* 运行服务器,失败抛出异常 */
|
||||
void run() {
|
||||
//printAddr(); /* 在日志里打印更好 */
|
||||
if (!svr.listen(addr.c_str(), port))
|
||||
throw StartServerFailed("");
|
||||
}
|
||||
|
||||
/* 终止服务器 */
|
||||
void stop() { svr.stop(); }
|
||||
|
||||
|
||||
/*
|
||||
* 设置路由和对应的处理函数
|
||||
*
|
||||
* FIXME: 使用一个方法实现两个功能
|
||||
*/
|
||||
void get (std::string path, Handler handler) { svr.Get(path, handler); }
|
||||
void post(std::string path, Handler handler) { svr.Post(path, handler); }
|
||||
|
||||
/*
|
||||
* 打印地址
|
||||
*
|
||||
* FIXME: 把这个函数的具体功能放到类的外部
|
||||
*/
|
||||
std::string printAddr() { return std::string("pmcd now listenning on this address: http://") + addr + ":" + std::to_string(port); }
|
||||
|
||||
/* 获取当前执行文件目录 */
|
||||
//static std::string getCurrentDirectory() {
|
||||
// fs::path currentPath = fs::absolute(__FILE__);
|
||||
// return currentPath.parent_path().string();
|
||||
//}
|
||||
|
||||
// 读取文本文件的所有内容
|
||||
//static std::string readFile(const std::string& filePath) {
|
||||
// std::ifstream file(filePath); // 创建一个输入文件流对象,用于读取文件
|
||||
// if (!file) {
|
||||
// std::cerr << "无法打开文件:" << filePath << std::endl;
|
||||
// return "";
|
||||
// }
|
||||
// std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); /* 使用迭代器读取文件内容到字符串 */
|
||||
// file.close();
|
||||
// return content;
|
||||
//}
|
||||
|
||||
class Http_Error: public std::exception { /* HTTP 异常类 */
|
||||
private:
|
||||
std::string message;
|
||||
public:
|
||||
explicit Http_Error(const std::string& msg): message(msg) {}
|
||||
const char* what() const noexcept override {
|
||||
return message.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
class StartServerFailed: public Http_Error { /* 开始服务失败 */
|
||||
public:
|
||||
StartServerFailed(const std::string& msg): Http_Error(msg) {}
|
||||
};
|
||||
|
||||
|
||||
private:
|
||||
httplib::Server svr; /* 创建HTTP服务器对象(如果需要HTTPS,则使用SSLServer */
|
||||
std::string addr; /* 服务地址 */
|
||||
int port; /* 端口 */
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
#ifndef PMC_HTTP_CLIENT_HPP
|
||||
#define PMC_HTTP_CLIENT_HPP
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <curl/curl.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace pmc::net {
|
||||
|
||||
class HttpClient {
|
||||
public:
|
||||
HttpClient() {
|
||||
std::cout << "HttpClient构造函数: 开始初始化" << std::endl;
|
||||
curl_ = curl_easy_init();
|
||||
if (!curl_) {
|
||||
throw std::runtime_error("Failed to initialize CURL");
|
||||
}
|
||||
std::cout << "HttpClient构造函数: 初始化完成" << std::endl;
|
||||
}
|
||||
|
||||
~HttpClient() {
|
||||
std::cout << "HttpClient析构函数: 开始清理" << std::endl;
|
||||
if (curl_) {
|
||||
curl_easy_cleanup(curl_);
|
||||
}
|
||||
if (headers_) {
|
||||
curl_slist_free_all(headers_);
|
||||
}
|
||||
std::cout << "HttpClient析构函数: 清理完成" << std::endl;
|
||||
}
|
||||
|
||||
void set_header(const std::string& header) {
|
||||
std::cout << "set_header: 添加header: " << header << std::endl;
|
||||
headers_ = curl_slist_append(headers_, header.c_str());
|
||||
}
|
||||
|
||||
// 同步流式请求
|
||||
void post_stream_sync(const std::string& url, const std::string& data, std::function<void(const std::string&)> callback) {
|
||||
std::cout << "post_stream_sync: 开始发送流式请求到 " << url << std::endl;
|
||||
|
||||
if (!curl_) {
|
||||
throw std::runtime_error("CURL not initialized");
|
||||
}
|
||||
|
||||
// 重置CURL句柄
|
||||
curl_easy_reset(curl_);
|
||||
|
||||
// 设置URL
|
||||
curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
|
||||
|
||||
// 设置POST请求
|
||||
curl_easy_setopt(curl_, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, data.c_str());
|
||||
curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE, data.size());
|
||||
|
||||
// 设置请求头
|
||||
curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, headers_);
|
||||
|
||||
// 设置流式回调函数
|
||||
std::string buffer;
|
||||
auto data_pair = std::make_pair(&buffer, &callback);
|
||||
std::cout << "post_stream_sync: 数据对创建完成" << std::endl;
|
||||
|
||||
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, stream_write_callback);
|
||||
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &data_pair);
|
||||
|
||||
// 设置超时和连接选项
|
||||
curl_easy_setopt(curl_, CURLOPT_TIMEOUT, 30L);
|
||||
curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT, 10L);
|
||||
|
||||
// 执行请求
|
||||
std::cout << "post_stream_sync: 执行请求" << std::endl;
|
||||
CURLcode res = curl_easy_perform(curl_);
|
||||
std::cout << "post_stream_sync: 数据对清理完成" << std::endl;
|
||||
|
||||
if (res != CURLE_OK) {
|
||||
std::cout << "post_stream_sync: CURL错误: " << curl_easy_strerror(res) << std::endl;
|
||||
throw std::runtime_error(std::string("CURL error: ") + curl_easy_strerror(res));
|
||||
}
|
||||
|
||||
std::cout << "post_stream_sync: 请求成功" << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
CURL* curl_ = nullptr;
|
||||
struct curl_slist* headers_ = nullptr;
|
||||
|
||||
static size_t stream_write_callback(char* ptr, size_t size, size_t nmemb, void* userdata) {
|
||||
// userdata指向一个std::pair<std::string*, std::function<void(const std::string&)>*>
|
||||
auto* data_pair = static_cast<std::pair<std::string*, std::function<void(const std::string&)>*>*>(userdata);
|
||||
if (!data_pair || !data_pair->first || !data_pair->second) {
|
||||
std::cout << "stream_write_callback: 无效的用户数据" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string& buffer = *(data_pair->first);
|
||||
std::function<void(const std::string&)>& callback = *(data_pair->second);
|
||||
size_t total_size = size * nmemb;
|
||||
|
||||
std::cout << "stream_write_callback: 收到 " << total_size << " 字节数据" << std::endl;
|
||||
|
||||
// 将数据追加到缓冲区
|
||||
buffer.append(ptr, total_size);
|
||||
|
||||
// 打印原始数据(用于调试)
|
||||
std::string raw_data(ptr, total_size);
|
||||
std::cout << "stream_write_callback: 原始数据: " << raw_data << std::endl;
|
||||
|
||||
// 处理流式数据(按行处理Server-Sent Events格式)
|
||||
size_t pos = 0;
|
||||
size_t newline_pos;
|
||||
|
||||
while ((newline_pos = buffer.find('\n', pos)) != std::string::npos) {
|
||||
std::string line = buffer.substr(pos, newline_pos - pos);
|
||||
pos = newline_pos + 1;
|
||||
|
||||
std::cout << "stream_write_callback: 处理行: " << line << std::endl;
|
||||
|
||||
// 处理SSE格式的数据(以"data: "开头)
|
||||
if (line.rfind("data: ", 0) == 0) {
|
||||
std::string json_str = line.substr(6);
|
||||
|
||||
if (json_str == "[DONE]") {
|
||||
std::cout << "stream_write_callback: 收到 [DONE]" << std::endl;
|
||||
callback("[[流式传输完成]]");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 调用回调函数处理JSON数据
|
||||
std::cout << "stream_write_callback: 调用回调,数据长度: " << json_str.length() << std::endl;
|
||||
callback(json_str);
|
||||
} else if (!line.empty()) {
|
||||
// 如果不是SSE格式,直接传递给回调(可能是错误消息)
|
||||
std::cout << "stream_write_callback: 非SSE数据,直接传递: " << line << std::endl;
|
||||
callback(line);
|
||||
}
|
||||
}
|
||||
|
||||
// 移除已处理的数据
|
||||
if (pos > 0) {
|
||||
buffer.erase(0, pos);
|
||||
}
|
||||
|
||||
return total_size;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace pmc::net
|
||||
|
||||
#endif // PMC_HTTP_CLIENT_HPP
|
||||
@@ -0,0 +1,193 @@
|
||||
#ifndef HTTPSERVER_HPP
|
||||
#define HTTPSERVER_HPP
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/beast.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
namespace beast = boost::beast;
|
||||
namespace http = beast::http;
|
||||
namespace asio = boost::asio;
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
|
||||
namespace pmc {
|
||||
namespace net {
|
||||
|
||||
/**
|
||||
* @brief HTTP请求处理器类型定义
|
||||
*
|
||||
* 处理HTTP请求并返回响应
|
||||
*/
|
||||
using HttpRequestHandler = std::function<http::response<http::string_body>(
|
||||
const http::request<http::string_body>&,
|
||||
const std::unordered_map<std::string, std::string>&)>;
|
||||
|
||||
/**
|
||||
* @brief 中间件处理器类型定义
|
||||
*
|
||||
* 用于处理请求链的中间件
|
||||
*/
|
||||
using MiddlewareHandler = std::function<bool(
|
||||
const http::request<http::string_body>&,
|
||||
http::response<http::string_body>&,
|
||||
const std::unordered_map<std::string, std::string>&)>;
|
||||
|
||||
/**
|
||||
* @brief HTTP服务器类(合并增强版)
|
||||
*
|
||||
* 基于boost::asio和boost::beast的异步HTTP服务器
|
||||
* 结合了原始版本的易用性和修复版本的功能完整性
|
||||
*/
|
||||
class HttpServer {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数(监听所有接口)
|
||||
* @param port 监听端口
|
||||
* @param threads 工作线程数
|
||||
*/
|
||||
explicit HttpServer(unsigned short port, unsigned int threads = 1);
|
||||
|
||||
/**
|
||||
* @brief 构造函数(指定监听地址)
|
||||
* @param address 监听IP地址(如 "0.0.0.0" 或 "127.0.0.1")
|
||||
* @param port 监听端口
|
||||
* @param threads 工作线程数
|
||||
*/
|
||||
explicit HttpServer(const std::string& address, unsigned short port, unsigned int threads = 1);
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~HttpServer();
|
||||
|
||||
/**
|
||||
* @brief 启动服务器
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* @brief 停止服务器
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* @brief 注册GET请求处理器
|
||||
* @param path 请求路径
|
||||
* @param handler 请求处理器
|
||||
*/
|
||||
void get(const std::string& path, HttpRequestHandler handler);
|
||||
|
||||
/**
|
||||
* @brief 注册POST请求处理器
|
||||
* @param path 请求路径
|
||||
* @param handler 请求处理器
|
||||
*/
|
||||
void post(const std::string& path, HttpRequestHandler handler);
|
||||
|
||||
/**
|
||||
* @brief 注册PUT请求处理器
|
||||
* @param path 请求路径
|
||||
* @param handler 请求处理器
|
||||
*/
|
||||
void put(const std::string& path, HttpRequestHandler handler);
|
||||
|
||||
/**
|
||||
* @brief 注册DELETE请求处理器
|
||||
* @param path 请求路径
|
||||
* @param handler 请求处理器
|
||||
*/
|
||||
void del(const std::string& path, HttpRequestHandler handler);
|
||||
|
||||
/**
|
||||
* @brief 注册中间件
|
||||
* @param middleware 中间件处理器
|
||||
*/
|
||||
void use(MiddlewareHandler middleware);
|
||||
|
||||
/**
|
||||
* @brief 设置静态文件目录
|
||||
* @param path 静态文件目录路径
|
||||
*/
|
||||
void setStaticDirectory(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 获取服务器状态
|
||||
* @return 服务器是否正在运行
|
||||
*/
|
||||
bool isRunning() const;
|
||||
|
||||
/**
|
||||
* @brief 获取服务器端口
|
||||
* @return 服务器监听端口
|
||||
*/
|
||||
unsigned short getPort() const;
|
||||
|
||||
/**
|
||||
* @brief 获取服务器监听地址
|
||||
* @return 服务器监听地址
|
||||
*/
|
||||
std::string getAddress() const;
|
||||
|
||||
/**
|
||||
* @brief 运行服务器(阻塞调用)- 从原始版本继承
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* @brief 显示服务器状态信息 - 从原始版本继承
|
||||
*/
|
||||
void showStatus() const;
|
||||
|
||||
private:
|
||||
// 内部类声明
|
||||
class Session;
|
||||
class Listener;
|
||||
|
||||
// 服务器配置
|
||||
std::string address_;
|
||||
unsigned short port_;
|
||||
unsigned int threads_;
|
||||
std::atomic<bool> running_{false};
|
||||
|
||||
// Boost.Asio上下文
|
||||
std::unique_ptr<asio::io_context> ioc_;
|
||||
|
||||
// 网络组件
|
||||
std::shared_ptr<Listener> listener_;
|
||||
std::vector<std::thread> worker_threads_;
|
||||
|
||||
// 路由表
|
||||
std::unordered_map<std::string, HttpRequestHandler> get_handlers_;
|
||||
std::unordered_map<std::string, HttpRequestHandler> post_handlers_;
|
||||
std::unordered_map<std::string, HttpRequestHandler> put_handlers_;
|
||||
std::unordered_map<std::string, HttpRequestHandler> delete_handlers_;
|
||||
|
||||
// 中间件链
|
||||
std::vector<MiddlewareHandler> middlewares_;
|
||||
|
||||
// 静态文件目录
|
||||
std::string static_directory_;
|
||||
|
||||
// 内部方法
|
||||
std::unordered_map<std::string, std::string> parseQueryParams(const std::string& query);
|
||||
|
||||
// 请求处理
|
||||
http::response<http::string_body> handleRequest(const http::request<http::string_body>& req);
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
} // namespace pmc
|
||||
|
||||
#endif // HTTPSERVER_HPP
|
||||
@@ -0,0 +1,63 @@
|
||||
#ifndef PMC_NET_WEBSOCKET_CLIENT_HPP
|
||||
#define PMC_NET_WEBSOCKET_CLIENT_HPP
|
||||
|
||||
#include "WebSocketClientBase.hpp"
|
||||
|
||||
namespace pmc {
|
||||
namespace net {
|
||||
|
||||
/**
|
||||
* @brief 基于 Boost.Beast 的异步 WebSocket 客户端(非 SSL 版本)
|
||||
*/
|
||||
class WebSocketClient : public WebSocketClientBase {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param host 服务器主机名或 IP 地址
|
||||
* @param port 服务器端口号
|
||||
* @param path WebSocket 路径(默认 "/")
|
||||
*/
|
||||
WebSocketClient(const std::string& host, const std::string& port, const std::string& path = "/");
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~WebSocketClient();
|
||||
|
||||
// 实现 WebSocketClientBase 接口
|
||||
void setMessageCallback(MessageCallback callback) override;
|
||||
void setErrorCallback(ErrorCallback callback) override;
|
||||
void setConnectCallback(ConnectCallback callback) override;
|
||||
void setCloseCallback(CloseCallback callback) override;
|
||||
|
||||
bool connect(int timeout_ms = 5000) override;
|
||||
void disconnect() override;
|
||||
bool isConnected() const override;
|
||||
|
||||
bool send(const std::string& message) override;
|
||||
bool sendBinary(const void* data, size_t size) override;
|
||||
|
||||
void setAutoReconnect(bool enable, int interval_ms = 3000) override;
|
||||
void setHeartbeatInterval(int interval_ms = 30000) override;
|
||||
|
||||
std::string getHost() const override;
|
||||
std::string getPort() const override;
|
||||
std::string getPath() const override;
|
||||
|
||||
// SSL/TLS 相关(非 SSL 版本返回默认值)
|
||||
bool isSecure() const override { return false; }
|
||||
void setVerifyCertificate(bool verify) override { /* 忽略 */ }
|
||||
void setCertificateFile(const std::string& cert_file) override { /* 忽略 */ }
|
||||
void setPrivateKeyFile(const std::string& key_file) override { /* 忽略 */ }
|
||||
void setCertificateAuthorityFile(const std::string& ca_file) override { /* 忽略 */ }
|
||||
|
||||
private:
|
||||
// 前向声明实现类
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
} // namespace pmc
|
||||
|
||||
#endif // PMC_NET_WEBSOCKET_CLIENT_HPP
|
||||
@@ -0,0 +1,61 @@
|
||||
#ifndef PMC_NET_WEBSOCKET_CLIENT_BASE_HPP
|
||||
#define PMC_NET_WEBSOCKET_CLIENT_BASE_HPP
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace pmc {
|
||||
namespace net {
|
||||
|
||||
/**
|
||||
* @brief WebSocket 客户端基类
|
||||
*
|
||||
* 定义所有 WebSocket 客户端的公共接口
|
||||
*/
|
||||
class WebSocketClientBase {
|
||||
public:
|
||||
// 回调函数类型
|
||||
using MessageCallback = std::function<void(const std::string&)>;
|
||||
using ErrorCallback = std::function<void(const std::string&)>;
|
||||
using ConnectCallback = std::function<void(bool)>;
|
||||
using CloseCallback = std::function<void()>;
|
||||
|
||||
virtual ~WebSocketClientBase() = default;
|
||||
|
||||
// 回调函数设置
|
||||
virtual void setMessageCallback(MessageCallback callback) = 0;
|
||||
virtual void setErrorCallback(ErrorCallback callback) = 0;
|
||||
virtual void setConnectCallback(ConnectCallback callback) = 0;
|
||||
virtual void setCloseCallback(CloseCallback callback) = 0;
|
||||
|
||||
// 连接管理
|
||||
virtual bool connect(int timeout_ms = 5000) = 0;
|
||||
virtual void disconnect() = 0;
|
||||
virtual bool isConnected() const = 0;
|
||||
|
||||
// 消息发送
|
||||
virtual bool send(const std::string& message) = 0;
|
||||
virtual bool sendBinary(const void* data, size_t size) = 0;
|
||||
|
||||
// 配置选项
|
||||
virtual void setAutoReconnect(bool enable, int interval_ms = 3000) = 0;
|
||||
virtual void setHeartbeatInterval(int interval_ms = 30000) = 0;
|
||||
|
||||
// 信息获取
|
||||
virtual std::string getHost() const = 0;
|
||||
virtual std::string getPort() const = 0;
|
||||
virtual std::string getPath() const = 0;
|
||||
|
||||
// SSL/TLS 相关
|
||||
virtual bool isSecure() const = 0;
|
||||
virtual void setVerifyCertificate(bool verify) = 0;
|
||||
virtual void setCertificateFile(const std::string& cert_file) = 0;
|
||||
virtual void setPrivateKeyFile(const std::string& key_file) = 0;
|
||||
virtual void setCertificateAuthorityFile(const std::string& ca_file) = 0;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
} // namespace pmc
|
||||
|
||||
#endif // PMC_NET_WEBSOCKET_CLIENT_BASE_HPP
|
||||
@@ -0,0 +1,85 @@
|
||||
#ifndef PMC_NET_WEBSOCKET_CLIENT_SSL_HPP
|
||||
#define PMC_NET_WEBSOCKET_CLIENT_SSL_HPP
|
||||
|
||||
#include "WebSocketClientBase.hpp"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace pmc {
|
||||
namespace net {
|
||||
|
||||
/**
|
||||
* @brief 基于 Boost.Beast 的异步 WebSocket 客户端(SSL/TLS 版本)
|
||||
*
|
||||
* 支持安全的 WebSocket 连接(wss://)
|
||||
*/
|
||||
class WebSocketClientSSL : public WebSocketClientBase {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param host 服务器主机名或 IP 地址
|
||||
* @param port 服务器端口号(通常为 443)
|
||||
* @param path WebSocket 路径(默认 "/")
|
||||
*/
|
||||
WebSocketClientSSL(const std::string& host, const std::string& port, const std::string& path = "/");
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~WebSocketClientSSL();
|
||||
|
||||
// 实现 WebSocketClientBase 接口
|
||||
void setMessageCallback(MessageCallback callback) override;
|
||||
void setErrorCallback(ErrorCallback callback) override;
|
||||
void setConnectCallback(ConnectCallback callback) override;
|
||||
void setCloseCallback(CloseCallback callback) override;
|
||||
|
||||
bool connect(int timeout_ms = 5000) override;
|
||||
void disconnect() override;
|
||||
bool isConnected() const override;
|
||||
|
||||
bool send(const std::string& message) override;
|
||||
bool sendBinary(const void* data, size_t size) override;
|
||||
|
||||
void setAutoReconnect(bool enable, int interval_ms = 3000) override;
|
||||
void setHeartbeatInterval(int interval_ms = 30000) override;
|
||||
|
||||
std::string getHost() const override;
|
||||
std::string getPort() const override;
|
||||
std::string getPath() const override;
|
||||
|
||||
// SSL/TLS 相关方法
|
||||
bool isSecure() const override { return true; }
|
||||
void setVerifyCertificate(bool verify) override;
|
||||
void setCertificateFile(const std::string& cert_file) override;
|
||||
void setPrivateKeyFile(const std::string& key_file) override;
|
||||
void setCertificateAuthorityFile(const std::string& ca_file) override;
|
||||
|
||||
/**
|
||||
* @brief 设置 SSL 上下文选项
|
||||
* @param options SSL 上下文选项
|
||||
*/
|
||||
void setSSLContextOptions(long options);
|
||||
|
||||
/**
|
||||
* @brief 设置密码列表
|
||||
* @param ciphers 密码列表字符串
|
||||
*/
|
||||
void setCiphers(const std::string& ciphers);
|
||||
|
||||
/**
|
||||
* @brief 设置 SNI 主机名
|
||||
* @param sni_hostname SNI 主机名
|
||||
*/
|
||||
void setSNIHostname(const std::string& sni_hostname);
|
||||
|
||||
private:
|
||||
// 前向声明实现类
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
} // namespace pmc
|
||||
|
||||
#endif // PMC_NET_WEBSOCKET_CLIENT_SSL_HPP
|
||||
@@ -0,0 +1,176 @@
|
||||
// WebSocketServer.hpp - WebSocket服务器头文件
|
||||
// 基于Boost.Beast和Boost.Asio
|
||||
|
||||
#ifndef PMC_NET_WEBSOCKETSERVER_HPP
|
||||
#define PMC_NET_WEBSOCKETSERVER_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
namespace pmc::net {
|
||||
|
||||
/**
|
||||
* @brief WebSocket连接处理器类型定义
|
||||
*
|
||||
* 处理WebSocket连接建立后的消息收发
|
||||
*/
|
||||
using WebSocketMessageHandler = std::function<void(
|
||||
const std::string& clientId,
|
||||
const std::string& message,
|
||||
std::function<void(const std::string&)> sendCallback)>;
|
||||
|
||||
/**
|
||||
* @brief WebSocket连接回调类型定义
|
||||
*/
|
||||
using WebSocketConnectHandler = std::function<void(
|
||||
const std::string& clientId,
|
||||
const std::string& remoteAddress)>;
|
||||
|
||||
/**
|
||||
* @brief WebSocket断开连接回调类型定义
|
||||
*/
|
||||
using WebSocketDisconnectHandler = std::function<void(
|
||||
const std::string& clientId)>;
|
||||
|
||||
/**
|
||||
* @brief WebSocket服务器类
|
||||
*
|
||||
* 基于Boost.Beast和Boost.Asio实现的WebSocket服务器
|
||||
*/
|
||||
class WebSocketServer {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param port 监听端口
|
||||
* @param threads 线程数(默认2)
|
||||
*/
|
||||
WebSocketServer(unsigned short port, unsigned int threads = 2);
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~WebSocketServer();
|
||||
|
||||
/**
|
||||
* @brief 启动服务器
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* @brief 停止服务器
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* @brief 注册消息处理器
|
||||
* @param path 路径(用于路由)
|
||||
* @param handler 消息处理器
|
||||
*/
|
||||
void onMessage(const std::string& path, WebSocketMessageHandler handler);
|
||||
|
||||
/**
|
||||
* @brief 注册连接处理器
|
||||
* @param handler 连接处理器
|
||||
*/
|
||||
void onConnect(WebSocketConnectHandler handler);
|
||||
|
||||
/**
|
||||
* @brief 注册断开连接处理器
|
||||
* @param handler 断开连接处理器
|
||||
*/
|
||||
void onDisconnect(WebSocketDisconnectHandler handler);
|
||||
|
||||
/**
|
||||
* @brief 向指定客户端发送消息
|
||||
* @param clientId 客户端ID
|
||||
* @param message 消息内容
|
||||
* @return 是否发送成功
|
||||
*/
|
||||
bool sendToClient(const std::string& clientId, const std::string& message);
|
||||
|
||||
/**
|
||||
* @brief 向所有客户端广播消息
|
||||
* @param message 消息内容
|
||||
* @return 成功发送的客户端数量
|
||||
*/
|
||||
size_t broadcast(const std::string& message);
|
||||
|
||||
/**
|
||||
* @brief 断开指定客户端连接
|
||||
* @param clientId 客户端ID
|
||||
*/
|
||||
void disconnectClient(const std::string& clientId);
|
||||
|
||||
/**
|
||||
* @brief 获取当前连接的客户端数量
|
||||
* @return 客户端数量
|
||||
*/
|
||||
size_t getClientCount() const;
|
||||
|
||||
/**
|
||||
* @brief 获取服务器是否正在运行
|
||||
* @return 运行状态
|
||||
*/
|
||||
bool isRunning() const { return running_; }
|
||||
|
||||
/**
|
||||
* @brief 获取服务器监听端口
|
||||
* @return 端口号
|
||||
*/
|
||||
unsigned short getPort() const { return port_; }
|
||||
|
||||
private:
|
||||
// 内部Session类
|
||||
class Session;
|
||||
|
||||
// 内部Listener类
|
||||
class Listener;
|
||||
|
||||
// 内部实现结构体
|
||||
struct Impl {
|
||||
boost::asio::io_context ioc_;
|
||||
std::shared_ptr<Listener> listener_;
|
||||
std::vector<std::thread> threads_;
|
||||
};
|
||||
|
||||
// 服务器配置
|
||||
unsigned short port_;
|
||||
unsigned int threads_;
|
||||
std::atomic<bool> running_{false};
|
||||
|
||||
// 内部实现
|
||||
std::unique_ptr<Impl> impl_;
|
||||
|
||||
// 生成客户端ID
|
||||
std::string generateClientId() const;
|
||||
|
||||
// 添加客户端
|
||||
void addClient(const std::string& clientId, std::shared_ptr<Session> session);
|
||||
|
||||
// 移除客户端
|
||||
void removeClient(const std::string& clientId);
|
||||
|
||||
// 获取客户端Session
|
||||
std::shared_ptr<Session> getClient(const std::string& clientId);
|
||||
|
||||
// 回调函数
|
||||
std::unordered_map<std::string, WebSocketMessageHandler> messageHandlers_;
|
||||
WebSocketConnectHandler connectHandler_;
|
||||
WebSocketDisconnectHandler disconnectHandler_;
|
||||
|
||||
// 客户端管理
|
||||
mutable std::mutex clientsMutex_;
|
||||
std::unordered_map<std::string, std::shared_ptr<Session>> clients_;
|
||||
};
|
||||
|
||||
} // namespace pmc::net
|
||||
|
||||
#endif // PMC_NET_WEBSOCKETSERVER_HPP
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 利用条件变量进行等待
|
||||
*
|
||||
* FIXME: deepseek 认为有一个设计缺陷:它只能用于单次等待-唤醒的场景。
|
||||
* FIXME: 可能导致永久堵塞,应使用带超时的等待
|
||||
* FIXME: 使用标准库的 future/promise
|
||||
*/
|
||||
|
||||
#include <thread>
|
||||
#include <atomic> /* 也许将字段flag创建为原子变量 */
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
namespace qing {
|
||||
class Cv_wait { /* 用于等待的条件变量类,对同一个对象进行操作能够阻塞当前线程,或者唤醒被阻塞的线程 */
|
||||
private:
|
||||
std::mutex mtx{}; /* 用于控制条件变量的锁 */
|
||||
std::condition_variable cv{};
|
||||
bool flag = false; /* 用于解锁条件变量的标志 */
|
||||
public:
|
||||
|
||||
inline void Wait() /* 线程进入等待 */
|
||||
{
|
||||
auto lock = std::unique_lock<std::mutex>(mtx);
|
||||
flag = false;
|
||||
cv.wait(lock, [this] { return flag; });
|
||||
}
|
||||
|
||||
inline void WakeCv() /* 唤醒线程 */
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lcok(mtx);
|
||||
flag = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 有限状态机(Finite State Machine,FSM)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <mutex> /*上锁 使得有限状态机线程安全*/
|
||||
#include <condition_variable> /*条件变量,使得子类能够等待状态的设置*/
|
||||
namespace qing {
|
||||
/*
|
||||
* FiniteStateMachine有限状态机
|
||||
*
|
||||
* 每个数字代表一个状态 使用static_cast<>()之类的函数去赋值
|
||||
* FIXME: 之前用过一种使用回调函数处理的方式
|
||||
*/
|
||||
class Fsm {
|
||||
public:
|
||||
|
||||
/* 状态枚举类 */
|
||||
enum class Stat: int {
|
||||
SHUT = -1, /* 关闭 */
|
||||
STOP = 0, /* 静止 */
|
||||
START = 1, /* 启动 */
|
||||
RUNNING = 2 /* 运行 */
|
||||
/* ... */
|
||||
};/*Stat*/
|
||||
|
||||
|
||||
/* 以STOP为初始状态 */
|
||||
Fsm() { set(Stat::STOP); }
|
||||
|
||||
enum Stat check() { /* 检查当前的状态 */
|
||||
std::lock_guard<std::mutex> lk(mtx);
|
||||
return stat;
|
||||
} /*check*/
|
||||
|
||||
|
||||
/*
|
||||
* 在不上锁的情况下设置一个状态
|
||||
* 这里不能使用锁,因为我们还要在临界区里唤醒条件变量
|
||||
*/
|
||||
void set_without_lock(Stat stat) { this->stat = stat; }
|
||||
|
||||
/* 带锁的状态设置 但是该方法不唤醒条件变量 */
|
||||
void set_without_notify(Stat stat) {
|
||||
std::lock_guard<std::mutex> lk(mtx);
|
||||
this->stat = stat;
|
||||
}
|
||||
|
||||
/* 带锁和条件唤醒的状态设置 */
|
||||
void set(Stat stat) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mtx);
|
||||
set_without_lock(stat);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
//protected:
|
||||
|
||||
void suspend() { /* 阻塞线程,等待事件唤醒 */
|
||||
std::unique_lock<std::mutex> lk(mtx);
|
||||
ready = false;
|
||||
cv.wait(lk, [this] { return ready; });
|
||||
}/*suspend */
|
||||
|
||||
private:
|
||||
std::mutex mtx; /* 线程安全 */
|
||||
bool ready = false; /* 用于条件变量的阻塞唤醒 */
|
||||
std::condition_variable cv; /* 条件变量 阻塞/唤醒线程 */
|
||||
enum Stat stat; /* 状态机的状态变量 */
|
||||
|
||||
};//Fsm
|
||||
|
||||
}//qing
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
namespace qing{
|
||||
class ITask { /* 单个任务通用接口 */
|
||||
public:
|
||||
virtual void start() = 0; /* 任务开始 */
|
||||
virtual void stop() = 0; /* 任务结束 */
|
||||
virtual bool isRunning() = 0; /* 是否在跑 */
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/* 线程
|
||||
*
|
||||
*
|
||||
* 包含一个简易的多线程控制机制,需要共享的变量和函数声明为成员。
|
||||
*
|
||||
* 线程主函数包括了流程控制,要使用需要实现其中的各个事件方法
|
||||
*
|
||||
* 20250621: 从派生改为持有
|
||||
*
|
||||
* NOTE: 在对象构造完之后应该激活
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <thread>
|
||||
#include <memory> /* 内存管理 */
|
||||
#include <chrono> /* 用于线程休息 */
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <functional>
|
||||
#include "th/Fsm.hpp" /* 有限状态机 */
|
||||
namespace qing {
|
||||
class Thread;
|
||||
//using f_t = void(*)(Thread&);
|
||||
using f_t = std::function<void(Thread&)>;
|
||||
class Thread {
|
||||
public:
|
||||
|
||||
/* 作为可变的函数,持有回调可能比继承更好 */
|
||||
Thread(f_t stop_callback, f_t wake_callback, f_t loop_callback, f_t clear_callback);
|
||||
|
||||
/*
|
||||
* 删除复制构造函数。
|
||||
*
|
||||
* 因为持有线程类,不要轻举妄动。
|
||||
*/
|
||||
Thread(const Thread&) = delete;
|
||||
|
||||
|
||||
/*
|
||||
* 虚析构函数
|
||||
*
|
||||
* FIXME: 具体有什么用,忘了
|
||||
*/
|
||||
virtual ~Thread() {}
|
||||
|
||||
/*-----------------------------------*/
|
||||
/*-----------------------------------*/
|
||||
/*
|
||||
* 检查状态的方法
|
||||
*
|
||||
* NOTE: 做成虚函数,万一子类有特别要求呢
|
||||
*
|
||||
* FIXME: 将检查器和变异器做成一个简单的回调函数
|
||||
*/
|
||||
virtual Fsm::Stat check() {
|
||||
return fsm.check();
|
||||
}
|
||||
|
||||
/*-----------------------------------*/
|
||||
/*-----------------------------------*/
|
||||
/* 让线程能够阻塞直到新的状态被赋予的方法 */
|
||||
inline void suspend() {
|
||||
fsm.suspend();
|
||||
}
|
||||
|
||||
/*-----------------------------------*/
|
||||
/*-----------------------------------*/
|
||||
/* 状态改变的方法 - 变异器 */
|
||||
|
||||
/*
|
||||
* 进入唤醒状态
|
||||
*
|
||||
* 唤醒函数不可以进行该类中的锁操作,否则会造成死锁
|
||||
*/
|
||||
virtual void wake() {
|
||||
//{
|
||||
//std::lock_guard<std::mutex> lk(mtx_th);
|
||||
fsm.set(Fsm::Stat::START);
|
||||
//ready = true;
|
||||
//}
|
||||
//cv.notify_all();
|
||||
} /* wake */
|
||||
|
||||
/* 进入静止状态
|
||||
*
|
||||
* 上锁、唤醒阻塞的线程
|
||||
*/
|
||||
virtual void stop() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mtx_th);
|
||||
fsm.set(Fsm::Stat::STOP);
|
||||
ready = true;
|
||||
}
|
||||
/* 可能会出现多个线程在等待的情况 */
|
||||
cv.notify_all();
|
||||
}/* stop */
|
||||
|
||||
/* 进入关闭状态 */
|
||||
virtual void shut() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mtx_th);
|
||||
fsm.set(Fsm::Stat::SHUT);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
} /*shut*/
|
||||
|
||||
/* 进入运行状态 */
|
||||
virtual void run() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mtx_th);
|
||||
fsm.set(Fsm::Stat::RUNNING);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
}/*run*/
|
||||
|
||||
/*-----------------------------------*/
|
||||
/*-----------------------------------*/
|
||||
|
||||
/*
|
||||
* 激活线程,申请线程对象
|
||||
*
|
||||
* 该函数不能放在构造函数内,因为需要确保对象已经被构造了
|
||||
*/
|
||||
void Activate() {
|
||||
|
||||
/* 防止重复初始化
|
||||
* FIXME: 抛出异常?感觉也不太合适 */
|
||||
if (th) return;
|
||||
|
||||
/* 申请线程资源 */
|
||||
th = std::make_unique<std::thread>(&Thread::main, this);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* 尝试唤醒线程并等待它开始 据说该函数有问题可能导致异常
|
||||
*/
|
||||
void WaitStart() {
|
||||
std::unique_lock<std::mutex> lk(mtx_th); /* 不进入临界区可能会造成死等,在唤醒过程非常短暂的情况下 */
|
||||
wake(); /* 这就是为什么wake()不可以获取临界区,否则会造成死锁 */
|
||||
ready = false; /* 这个临界区结束后,WakeEvent()中的run()会唤醒线程的堵塞 */
|
||||
cv.wait(lk, [this] { return ready; });
|
||||
}
|
||||
|
||||
/* 等待线程关闭 */
|
||||
void WaitClose() {
|
||||
shut();
|
||||
if (th->joinable())
|
||||
th->join();
|
||||
}
|
||||
|
||||
/*-----------------------------------*/
|
||||
/* 线程主函数 */
|
||||
void main();
|
||||
|
||||
private:
|
||||
|
||||
qing::Fsm fsm{}; /* 持有状态机类型 */
|
||||
|
||||
std::unique_ptr<std::thread> th; /* 持有线程类型 */
|
||||
|
||||
std::mutex mtx_th; /* 持有锁类型 */
|
||||
|
||||
/*
|
||||
* 这里持有一个条件变量,用于阻塞以等待线程的关闭
|
||||
*
|
||||
* 注意状态机中的cv是另一个变量
|
||||
*/
|
||||
std::condition_variable cv;
|
||||
|
||||
/* 辅助条件变量的逻辑型 */
|
||||
bool ready;
|
||||
|
||||
/* 事件回调 */
|
||||
std::unique_ptr<f_t> StopEvent;
|
||||
std::unique_ptr<f_t> WakeEvent;
|
||||
std::unique_ptr<f_t> LoopEvent;
|
||||
std::unique_ptr<f_t> ClearEvent;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 目前是Message Queue 的发送端(不涉及创建消息队列)
|
||||
*
|
||||
* FIXME: 集成发送、接收和创建
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <boost/interprocess/ipc/message_queue.hpp>
|
||||
|
||||
namespace qing {
|
||||
namespace bip = boost::interprocess;
|
||||
using sf_t = std::function<void(const char*)>; /* success */
|
||||
using ef_t = std::function<void(void)>; /* error */
|
||||
class Mq {
|
||||
public:
|
||||
enum {CREATOR=0, USER=1};
|
||||
Mq(const std::string& name, int msglen, size_t msgcnt, int mode = USER) {
|
||||
this->name = std::string("/") + name;
|
||||
this->msglen = msglen;
|
||||
this->msgcnt = msgcnt;
|
||||
this->mode = mode;
|
||||
switch (mode) {
|
||||
case USER:
|
||||
mqp = std::make_unique<bip::message_queue>(bip::open_only, this->name.c_str());
|
||||
break;
|
||||
case CREATOR: /* 由创建者创建消息队列 */
|
||||
bip::message_queue::remove(this->name.c_str());
|
||||
mqp = std::make_unique<bip::message_queue>(
|
||||
bip::create_only, this->name.c_str(),
|
||||
msgcnt, msglen * sizeof(char)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* 创建者负责清理 */
|
||||
~Mq() {
|
||||
if (mode == CREATOR)
|
||||
bip::message_queue::remove(name.c_str());
|
||||
}
|
||||
|
||||
/* */
|
||||
Mq(const Mq&) = delete;
|
||||
Mq& operator=(const Mq&) = delete;
|
||||
|
||||
|
||||
/* 发送消息 */
|
||||
void send(std::string &msg)
|
||||
{
|
||||
if (msg.size() + 1 > msglen) /* over the len */
|
||||
throw std::length_error("Message queue Send Length over");
|
||||
|
||||
mqp->send(msg.c_str(), msg.size() + 1, 0); /* include \0 */
|
||||
}
|
||||
|
||||
/* 尝试接收消息 */
|
||||
void recv(sf_t success, ef_t notyet) {
|
||||
|
||||
auto data = std::make_unique<char[]>(msglen); /* 接收缓冲区 */
|
||||
unsigned int priority; /* 优先级 */
|
||||
bip::message_queue::size_type recvd_size;
|
||||
|
||||
try { /* try_receive()返回bool类型 */
|
||||
auto r = mqp->try_receive(
|
||||
data.get(), msglen*sizeof(char),
|
||||
recvd_size, priority
|
||||
);
|
||||
|
||||
if (r) /* 成功接收到数据 */
|
||||
success(data.get());
|
||||
|
||||
else /* 没有收到数据 */
|
||||
notyet();
|
||||
}
|
||||
|
||||
catch (std::exception &exp) {
|
||||
std::cerr << "exception type: " << typeid(exp).name() << std::endl;
|
||||
std::cerr << "exception message: " << exp.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/* */
|
||||
std::unique_ptr<bip::message_queue> mqp;
|
||||
std::string name;
|
||||
size_t msglen=0, msgcnt=0;
|
||||
int mode;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h> /* open() */
|
||||
#include <sys/stat.h> /* mkfifo() */
|
||||
#include <sys/select.h>
|
||||
|
||||
namespace qing {
|
||||
class Pipe{ /* 20260309(qing): 该类不会抛出异常 */
|
||||
public:
|
||||
static const int CREATOR = 1;
|
||||
static const int USER =2;
|
||||
static const ssize_t PIPE_MAX_LEN = 2048;
|
||||
Pipe(const std::string& path, int mode)
|
||||
: fd(-1), isCreator(mode == CREATOR) {
|
||||
std::string real_path = "/tmp/pst-";
|
||||
real_path += path;
|
||||
fifo_path = real_path;
|
||||
if (isCreator && mkfifo(fifo_path.c_str(), 0666) == -1 && errno != EEXIST) /* wr_wr_wr */
|
||||
perror("mkfifo"); /* 文件已存在是正常情况,其他错误才需要处理 */
|
||||
}
|
||||
|
||||
~Pipe() {
|
||||
/* 如果打开了文件,则关闭文件 */
|
||||
if (fd != -1) close(fd);
|
||||
/**********************/
|
||||
/* 如果管道由该对象创建,就要负责清理 */
|
||||
if (isCreator) unlink(fifo_path.c_str()); /* 创建者负责清理 */
|
||||
}
|
||||
Pipe(const Pipe&) = delete;
|
||||
Pipe& operator=(const Pipe&) = delete;
|
||||
|
||||
/* 这里才是打开文件的指令 - 以读模式打开,可以非阻塞 */
|
||||
bool openForRead(bool nonblock = false) {
|
||||
|
||||
/* 设置是否以非阻塞方式打开文件 */
|
||||
int flags = O_RDONLY;
|
||||
if (nonblock) {
|
||||
flags |= O_NONBLOCK;
|
||||
}
|
||||
|
||||
fd = open(fifo_path.c_str(), flags);
|
||||
|
||||
if (fd == -1) {
|
||||
perror("open for read");
|
||||
return false;
|
||||
}
|
||||
|
||||
else {
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(fd, &read_fds);
|
||||
if (fd > max_fd) max_fd = fd;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* 以写模式打开文件 */
|
||||
bool openForWrite(/*bool nonblock = falsei*/) {
|
||||
|
||||
/* 写入管道是不需要阻塞/非阻塞的? */
|
||||
int flags = O_WRONLY;
|
||||
//if (nonblock) {
|
||||
// flags |= O_NONBLOCK;
|
||||
//}
|
||||
fd = open(fifo_path.c_str(), flags);
|
||||
if (fd == -1) {
|
||||
perror("open for write");
|
||||
return false;
|
||||
}
|
||||
|
||||
else {
|
||||
FD_ZERO(&write_fds);
|
||||
FD_SET(fd, &write_fds);
|
||||
if (fd > max_fd) max_fd = fd;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* 写入管道,如果失败返回-1。成功返回写入的字符数。 */
|
||||
ssize_t writePipe(const std::string &data) {
|
||||
if (fd == -1) {
|
||||
std::cerr << "Pipe not opened for writing" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return write(fd, data.c_str(), data.size());
|
||||
}
|
||||
}
|
||||
|
||||
/* 将探测与读取解耦,使用者可以自行决定要不要阻塞 */
|
||||
bool waitForRead(int timeout_sec = 0, int timeout_usec = 0) {
|
||||
|
||||
/* 如果文件没打开,等待是失败的 */
|
||||
if (fd == -1) return false;
|
||||
|
||||
fd_set temp_fds = read_fds;
|
||||
struct timeval timeout = {timeout_sec, timeout_usec};
|
||||
|
||||
int ret = select(max_fd + 1, &temp_fds, nullptr, nullptr, &timeout);
|
||||
|
||||
if (ret == -1) {
|
||||
perror("select");
|
||||
return false;
|
||||
}
|
||||
|
||||
else {
|
||||
return ret > 0 && FD_ISSET(fd, &temp_fds);
|
||||
}
|
||||
}
|
||||
|
||||
/* 读取数据到字符串,返回实际读取的字节数 */
|
||||
ssize_t readPipe(std::string *output) {
|
||||
|
||||
if (fd == -1) {
|
||||
std::cerr << "Pipe not opened for reading" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
else
|
||||
|
||||
{
|
||||
|
||||
char buffer[PIPE_MAX_LEN];
|
||||
ssize_t n = read(fd, buffer, sizeof(buffer) - 1);
|
||||
if (n >0) {
|
||||
buffer[n] = '\0';
|
||||
*output = buffer;
|
||||
}
|
||||
|
||||
else if (n == 0) {
|
||||
/* 管道写入端关闭 */
|
||||
*output = "";
|
||||
}
|
||||
|
||||
else {
|
||||
perror("read");
|
||||
}
|
||||
|
||||
return n;
|
||||
|
||||
}
|
||||
}
|
||||
private:
|
||||
std::string fifo_path;
|
||||
int fd;
|
||||
bool isCreator;
|
||||
fd_set read_fds;
|
||||
fd_set write_fds;
|
||||
fd_set except_fds;
|
||||
int max_fd;
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user