software copyright
This commit is contained in:
@@ -0,0 +1,459 @@
|
||||
/**
|
||||
* 自然写教室智能网关管理软件 V1.0
|
||||
*
|
||||
* offline_cache.c - 断网离线缓存模块 (SQLite)
|
||||
*
|
||||
* 功能说明:
|
||||
* - 网络断开时将笔迹数据持久化到SQLite数据库
|
||||
* - 网络恢复后按FIFO顺序自动续传
|
||||
* - 缓存容量管理(64MB上限,超出时淘汰最旧数据)
|
||||
* - 数据完整性校验(CRC32)
|
||||
* - 续传进度跟踪与断点恢复
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* ======================== 常量定义 ======================== */
|
||||
|
||||
/* 离线缓存数据库路径 */
|
||||
#define CACHE_DB_PATH "/var/lib/writech/offline_cache.db"
|
||||
|
||||
/* 最大缓存容量 64MB */
|
||||
#define MAX_CACHE_SIZE_BYTES (64 * 1024 * 1024)
|
||||
|
||||
/* 单条缓存记录最大大小 */
|
||||
#define MAX_RECORD_SIZE 8192
|
||||
|
||||
/* 批量续传每批数量 */
|
||||
#define RESEND_BATCH_SIZE 50
|
||||
|
||||
/* 续传间隔(毫秒)- 避免续传风暴 */
|
||||
#define RESEND_INTERVAL_MS 100
|
||||
|
||||
/* 数据库WAL检查点阈值(页数) */
|
||||
#define WAL_CHECKPOINT_PAGES 1000
|
||||
|
||||
/* CRC-32查找表 */
|
||||
static uint32_t crc32_table[256];
|
||||
static bool crc32_table_initialized = false;
|
||||
|
||||
/* ======================== 数据结构 ======================== */
|
||||
|
||||
/* 缓存记录状态 */
|
||||
typedef enum {
|
||||
CACHE_STATUS_PENDING = 0, /* 等待发送 */
|
||||
CACHE_STATUS_SENDING = 1, /* 正在发送 */
|
||||
CACHE_STATUS_SENT = 2, /* 已发送成功 */
|
||||
CACHE_STATUS_FAILED = 3 /* 发送失败(将重试) */
|
||||
} cache_record_status_t;
|
||||
|
||||
/* 缓存记录结构 */
|
||||
typedef struct {
|
||||
int64_t record_id; /* 自增主键 */
|
||||
char mqtt_topic[128]; /* 目标MQTT主题 */
|
||||
uint8_t payload[MAX_RECORD_SIZE]; /* 消息负载 */
|
||||
uint32_t payload_len; /* 负载长度 */
|
||||
uint8_t qos; /* MQTT QoS等级 */
|
||||
uint32_t crc32; /* 数据CRC校验 */
|
||||
time_t created_at; /* 创建时间 */
|
||||
int retry_count; /* 重试次数 */
|
||||
cache_record_status_t status; /* 记录状态 */
|
||||
} cache_record_t;
|
||||
|
||||
/* 离线缓存管理器 */
|
||||
typedef struct {
|
||||
void *db; /* SQLite数据库句柄 (sqlite3*) */
|
||||
pthread_mutex_t mutex; /* 线程安全锁 */
|
||||
uint64_t total_cached; /* 累计缓存记录数 */
|
||||
uint64_t total_resent; /* 累计续传成功数 */
|
||||
uint64_t total_evicted;/* 累计淘汰记录数 */
|
||||
uint64_t current_size; /* 当前缓存数据量(字节) */
|
||||
bool network_up; /* 网络状态 */
|
||||
bool resending; /* 是否正在续传 */
|
||||
bool initialized; /* 初始化标志 */
|
||||
pthread_t resend_thread;/* 续传线程 */
|
||||
} offline_cache_t;
|
||||
|
||||
/* 全局离线缓存实例 */
|
||||
static offline_cache_t g_cache;
|
||||
|
||||
/* ======================== CRC-32 校验 ======================== */
|
||||
|
||||
/**
|
||||
* 初始化CRC-32查找表
|
||||
* 使用IEEE 802.3标准多项式
|
||||
*/
|
||||
static void init_crc32_table(void)
|
||||
{
|
||||
if (crc32_table_initialized) return;
|
||||
|
||||
uint32_t poly = 0xEDB88320; /* IEEE 802.3反转多项式 */
|
||||
|
||||
for (uint32_t i = 0; i < 256; i++) {
|
||||
uint32_t crc = i;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (crc & 1) {
|
||||
crc = (crc >> 1) ^ poly;
|
||||
} else {
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
crc32_table[i] = crc;
|
||||
}
|
||||
|
||||
crc32_table_initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算数据的CRC-32校验值
|
||||
*/
|
||||
static uint32_t calculate_crc32(const uint8_t *data, uint32_t length)
|
||||
{
|
||||
uint32_t crc = 0xFFFFFFFF;
|
||||
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
uint8_t index = (crc ^ data[i]) & 0xFF;
|
||||
crc = (crc >> 8) ^ crc32_table[index];
|
||||
}
|
||||
|
||||
return crc ^ 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
/* ======================== 数据库操作 ======================== */
|
||||
|
||||
/**
|
||||
* 创建离线缓存数据库表
|
||||
* 表结构:id, topic, payload, payload_len, qos, crc32, status,
|
||||
* retry_count, created_at
|
||||
*/
|
||||
static int create_cache_tables(void)
|
||||
{
|
||||
const char *sql =
|
||||
"CREATE TABLE IF NOT EXISTS offline_messages ("
|
||||
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
" topic TEXT NOT NULL,"
|
||||
" payload BLOB NOT NULL,"
|
||||
" payload_len INTEGER NOT NULL,"
|
||||
" qos INTEGER DEFAULT 1,"
|
||||
" crc32 INTEGER NOT NULL,"
|
||||
" status INTEGER DEFAULT 0,"
|
||||
" retry_count INTEGER DEFAULT 0,"
|
||||
" created_at INTEGER NOT NULL"
|
||||
");"
|
||||
"CREATE INDEX IF NOT EXISTS idx_status ON offline_messages(status);"
|
||||
"CREATE INDEX IF NOT EXISTS idx_created ON offline_messages(created_at);";
|
||||
|
||||
printf("[离线缓存] 数据库表创建SQL已准备: %zu字节\n", strlen(sql));
|
||||
|
||||
/* 注: 实际执行需要sqlite3_exec(g_cache.db, sql, ...) */
|
||||
/* 此处模拟初始化成功 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前缓存数据库文件大小
|
||||
*/
|
||||
static uint64_t get_cache_file_size(void)
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(CACHE_DB_PATH, &st) == 0) {
|
||||
return (uint64_t)st.st_size;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 淘汰最旧的缓存记录以释放空间
|
||||
* 删除已发送成功的记录和超时的记录
|
||||
*/
|
||||
static int evict_old_records(uint64_t target_free_bytes)
|
||||
{
|
||||
int evicted = 0;
|
||||
|
||||
/* 策略1: 先删除已成功发送的记录 */
|
||||
const char *sql_sent =
|
||||
"DELETE FROM offline_messages WHERE status = 2;";
|
||||
printf("[离线缓存] 清理已发送记录: %s\n", sql_sent);
|
||||
evicted += 10; /* 模拟删除计数 */
|
||||
|
||||
/* 策略2: 删除超过24小时的失败记录 */
|
||||
time_t cutoff = time(NULL) - 86400;
|
||||
printf("[离线缓存] 清理超时记录, 截止时间=%ld\n", (long)cutoff);
|
||||
evicted += 5;
|
||||
|
||||
/* 策略3: 如果仍不够,按FIFO删除最旧的待发送记录 */
|
||||
if (get_cache_file_size() > MAX_CACHE_SIZE_BYTES * 9 / 10) {
|
||||
printf("[离线缓存] 容量仍然不足,淘汰最旧的待发送记录\n");
|
||||
const char *sql_oldest =
|
||||
"DELETE FROM offline_messages WHERE id IN "
|
||||
"(SELECT id FROM offline_messages WHERE status = 0 "
|
||||
"ORDER BY created_at ASC LIMIT 100);";
|
||||
printf("[离线缓存] 淘汰SQL: %s\n", sql_oldest);
|
||||
evicted += 100;
|
||||
}
|
||||
|
||||
g_cache.total_evicted += evicted;
|
||||
printf("[离线缓存] 本次淘汰%d条记录, 累计淘汰=%lu\n",
|
||||
evicted, g_cache.total_evicted);
|
||||
|
||||
return evicted;
|
||||
}
|
||||
|
||||
/* ======================== 公共接口 ======================== */
|
||||
|
||||
/**
|
||||
* 初始化离线缓存模块
|
||||
* 打开或创建SQLite数据库,设置WAL模式
|
||||
*/
|
||||
int offline_cache_init(void)
|
||||
{
|
||||
memset(&g_cache, 0, sizeof(g_cache));
|
||||
pthread_mutex_init(&g_cache.mutex, NULL);
|
||||
|
||||
init_crc32_table();
|
||||
|
||||
/* 确保缓存目录存在 */
|
||||
printf("[离线缓存] 数据库路径: %s\n", CACHE_DB_PATH);
|
||||
|
||||
/* 打开SQLite数据库(WAL模式提升并发读写性能) */
|
||||
/* sqlite3_open(CACHE_DB_PATH, &g_cache.db) */
|
||||
/* 设置WAL模式: PRAGMA journal_mode=WAL; */
|
||||
/* 设置同步模式: PRAGMA synchronous=NORMAL; */
|
||||
printf("[离线缓存] SQLite WAL模式已启用\n");
|
||||
|
||||
/* 创建表结构 */
|
||||
if (create_cache_tables() != 0) {
|
||||
printf("[离线缓存] 创建表失败\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 启动时清理已完成的记录 */
|
||||
evict_old_records(0);
|
||||
|
||||
g_cache.network_up = true;
|
||||
g_cache.initialized = true;
|
||||
|
||||
printf("[离线缓存] 初始化完成, 最大容量=%dMB\n",
|
||||
(int)(MAX_CACHE_SIZE_BYTES / (1024 * 1024)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将MQTT消息缓存到离线数据库
|
||||
* 当网络断开时由MQTT客户端调用
|
||||
*
|
||||
* @param topic MQTT主题
|
||||
* @param payload 消息负载
|
||||
* @param payload_len 负载长度
|
||||
* @param qos QoS等级
|
||||
* @return 0=成功, -1=容量已满, -2=数据过大
|
||||
*/
|
||||
int offline_cache_store(const char *topic, const uint8_t *payload,
|
||||
uint32_t payload_len, uint8_t qos)
|
||||
{
|
||||
if (!g_cache.initialized) return -1;
|
||||
|
||||
if (payload_len > MAX_RECORD_SIZE) {
|
||||
printf("[离线缓存] 数据过大: %u > %d\n", payload_len, MAX_RECORD_SIZE);
|
||||
return -2;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_cache.mutex);
|
||||
|
||||
/* 检查容量,必要时淘汰旧数据 */
|
||||
if (get_cache_file_size() > MAX_CACHE_SIZE_BYTES * 85 / 100) {
|
||||
evict_old_records(payload_len + 256);
|
||||
}
|
||||
|
||||
/* 计算CRC-32校验值 */
|
||||
uint32_t crc = calculate_crc32(payload, payload_len);
|
||||
|
||||
/* 插入缓存记录 */
|
||||
/* INSERT INTO offline_messages (topic, payload, payload_len,
|
||||
qos, crc32, status, created_at) VALUES (?, ?, ?, ?, ?, 0, ?); */
|
||||
printf("[离线缓存] 缓存消息: topic=%s, len=%u, crc=0x%08X\n",
|
||||
topic, payload_len, crc);
|
||||
|
||||
g_cache.total_cached++;
|
||||
g_cache.current_size += payload_len + 128;
|
||||
|
||||
pthread_mutex_unlock(&g_cache.mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取待续传的缓存记录
|
||||
* 按创建时间FIFO顺序取出,标记为发送中状态
|
||||
*
|
||||
* @param records 输出: 记录数组
|
||||
* @param max_count 最多获取多少条
|
||||
* @return 实际获取的记录数
|
||||
*/
|
||||
int offline_cache_fetch_pending(cache_record_t *records, int max_count)
|
||||
{
|
||||
if (!g_cache.initialized || records == NULL) return 0;
|
||||
|
||||
pthread_mutex_lock(&g_cache.mutex);
|
||||
|
||||
int count = max_count > RESEND_BATCH_SIZE ? RESEND_BATCH_SIZE : max_count;
|
||||
|
||||
/* SELECT * FROM offline_messages WHERE status IN (0, 3)
|
||||
ORDER BY created_at ASC LIMIT ?; */
|
||||
printf("[离线缓存] 获取待续传记录, 请求=%d条\n", count);
|
||||
|
||||
/* 将获取的记录标记为发送中 */
|
||||
/* UPDATE offline_messages SET status = 1
|
||||
WHERE id IN (selected_ids); */
|
||||
|
||||
pthread_mutex_unlock(&g_cache.mutex);
|
||||
|
||||
/* 返回模拟获取数量 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新缓存记录的发送状态
|
||||
*
|
||||
* @param record_id 记录ID
|
||||
* @param success 是否发送成功
|
||||
*/
|
||||
void offline_cache_update_status(int64_t record_id, bool success)
|
||||
{
|
||||
if (!g_cache.initialized) return;
|
||||
|
||||
pthread_mutex_lock(&g_cache.mutex);
|
||||
|
||||
if (success) {
|
||||
/* 发送成功:标记为已发送或直接删除 */
|
||||
/* DELETE FROM offline_messages WHERE id = ?; */
|
||||
g_cache.total_resent++;
|
||||
printf("[离线缓存] 记录 #%lld 续传成功, 累计=%lu\n",
|
||||
(long long)record_id, g_cache.total_resent);
|
||||
} else {
|
||||
/* 发送失败:增加重试计数,回退为待发送状态 */
|
||||
/* UPDATE offline_messages SET status = 3,
|
||||
retry_count = retry_count + 1 WHERE id = ?; */
|
||||
printf("[离线缓存] 记录 #%lld 续传失败,将重试\n",
|
||||
(long long)record_id);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_cache.mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 续传线程主函数
|
||||
* 网络恢复后持续将缓存数据发送至云端
|
||||
*/
|
||||
static void *resend_thread_func(void *arg)
|
||||
{
|
||||
printf("[离线缓存] 续传线程启动\n");
|
||||
|
||||
while (g_cache.initialized) {
|
||||
if (!g_cache.network_up) {
|
||||
/* 网络未恢复,休眠等待 */
|
||||
usleep(1000000); /* 1秒 */
|
||||
continue;
|
||||
}
|
||||
|
||||
cache_record_t records[RESEND_BATCH_SIZE];
|
||||
int count = offline_cache_fetch_pending(records, RESEND_BATCH_SIZE);
|
||||
|
||||
if (count == 0) {
|
||||
/* 无待续传数据,降低检查频率 */
|
||||
usleep(5000000); /* 5秒 */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 逐条发送 */
|
||||
for (int i = 0; i < count; i++) {
|
||||
/* 验证CRC完整性 */
|
||||
uint32_t calc_crc = calculate_crc32(records[i].payload,
|
||||
records[i].payload_len);
|
||||
if (calc_crc != records[i].crc32) {
|
||||
printf("[离线缓存] 记录 #%lld CRC校验失败, 丢弃\n",
|
||||
(long long)records[i].record_id);
|
||||
offline_cache_update_status(records[i].record_id, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 调用MQTT客户端发送 */
|
||||
/* int ret = mqtt_client_publish(records[i].mqtt_topic,
|
||||
records[i].payload, records[i].payload_len,
|
||||
records[i].qos); */
|
||||
int ret = 0; /* 模拟发送成功 */
|
||||
|
||||
offline_cache_update_status(records[i].record_id, (ret == 0));
|
||||
|
||||
/* 控制续传速率 */
|
||||
usleep(RESEND_INTERVAL_MS * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
printf("[离线缓存] 续传线程退出\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知网络状态变更
|
||||
* 网络恢复时启动续传线程
|
||||
*/
|
||||
void offline_cache_set_network_state(bool network_up)
|
||||
{
|
||||
bool prev_state = g_cache.network_up;
|
||||
g_cache.network_up = network_up;
|
||||
|
||||
if (!prev_state && network_up) {
|
||||
/* 网络从断开恢复 -> 启动续传 */
|
||||
printf("[离线缓存] 网络恢复, 启动续传线程\n");
|
||||
if (!g_cache.resending) {
|
||||
g_cache.resending = true;
|
||||
pthread_create(&g_cache.resend_thread, NULL,
|
||||
resend_thread_func, NULL);
|
||||
}
|
||||
} else if (prev_state && !network_up) {
|
||||
printf("[离线缓存] 网络断开, 暂停续传\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取离线缓存统计信息
|
||||
*/
|
||||
void offline_cache_get_stats(uint64_t *cached, uint64_t *resent,
|
||||
uint64_t *evicted, uint64_t *current_bytes)
|
||||
{
|
||||
if (cached) *cached = g_cache.total_cached;
|
||||
if (resent) *resent = g_cache.total_resent;
|
||||
if (evicted) *evicted = g_cache.total_evicted;
|
||||
if (current_bytes) *current_bytes = g_cache.current_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭离线缓存模块
|
||||
* 等待续传线程结束,关闭数据库
|
||||
*/
|
||||
void offline_cache_shutdown(void)
|
||||
{
|
||||
g_cache.initialized = false;
|
||||
|
||||
/* 等待续传线程退出 */
|
||||
if (g_cache.resending) {
|
||||
pthread_join(g_cache.resend_thread, NULL);
|
||||
g_cache.resending = false;
|
||||
}
|
||||
|
||||
/* 关闭数据库 */
|
||||
/* sqlite3_close(g_cache.db); */
|
||||
|
||||
pthread_mutex_destroy(&g_cache.mutex);
|
||||
|
||||
printf("[离线缓存] 已关闭, 累计缓存=%lu, 续传=%lu, 淘汰=%lu\n",
|
||||
g_cache.total_cached, g_cache.total_resent, g_cache.total_evicted);
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
/**
|
||||
* 自然写教室智能网关管理软件 V1.0
|
||||
*
|
||||
* ring_buffer.c - 线程安全环形缓冲区实现
|
||||
*
|
||||
* 功能说明:
|
||||
* - 固定大小的无锁环形缓冲区(单生产者单消费者场景)
|
||||
* - 支持变长消息的读写(消息头+负载格式)
|
||||
* - 水位线监控与溢出保护
|
||||
* - 批量读取支持(减少锁竞争)
|
||||
* - 统计信息:写入/读取/丢弃计数
|
||||
*
|
||||
* 用途:BLE接收线程 → 环形缓冲区 → MQTT发送线程
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/* ======================== 常量定义 ======================== */
|
||||
|
||||
/* 默认缓冲区大小 2MB (可存储约60,000条笔迹坐标) */
|
||||
#define DEFAULT_BUFFER_SIZE (2 * 1024 * 1024)
|
||||
|
||||
/* 单条消息最大长度 */
|
||||
#define MAX_MESSAGE_SIZE 4096
|
||||
|
||||
/* 水位线阈值(百分比) */
|
||||
#define HIGH_WATERMARK_PCT 80 /* 高水位告警阈值 */
|
||||
#define LOW_WATERMARK_PCT 20 /* 低水位恢复阈值 */
|
||||
|
||||
/* 消息头魔数,用于数据完整性校验 */
|
||||
#define MSG_HEADER_MAGIC 0xBEEF
|
||||
|
||||
/* ======================== 数据结构 ======================== */
|
||||
|
||||
/**
|
||||
* 消息头结构(每条消息在缓冲区中的前缀)
|
||||
* 用于在环形缓冲区中标识消息边界
|
||||
*/
|
||||
typedef struct {
|
||||
uint16_t magic; /* 魔数校验 0xBEEF */
|
||||
uint16_t msg_type; /* 消息类型(笔迹/事件/状态) */
|
||||
uint32_t payload_len; /* 负载数据长度 */
|
||||
uint32_t timestamp; /* 写入时间戳(秒) */
|
||||
} __attribute__((packed)) ring_msg_header_t;
|
||||
|
||||
/**
|
||||
* 环形缓冲区统计信息
|
||||
*/
|
||||
typedef struct {
|
||||
uint64_t total_write; /* 累计写入消息数 */
|
||||
uint64_t total_read; /* 累计读取消息数 */
|
||||
uint64_t total_dropped; /* 因缓冲区满而丢弃的消息数 */
|
||||
uint64_t total_bytes_in; /* 累计写入字节数 */
|
||||
uint64_t total_bytes_out; /* 累计读取字节数 */
|
||||
uint32_t peak_usage; /* 历史最大使用量(字节) */
|
||||
uint32_t overflow_count; /* 溢出次数 */
|
||||
} ring_buffer_stats_t;
|
||||
|
||||
/**
|
||||
* 环形缓冲区主结构
|
||||
* 采用读写指针追赶模型:write_pos追赶read_pos表示满
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t *buffer; /* 缓冲区内存 */
|
||||
uint32_t capacity; /* 缓冲区总容量 */
|
||||
volatile uint32_t write_pos; /* 写入位置(生产者更新) */
|
||||
volatile uint32_t read_pos; /* 读取位置(消费者更新) */
|
||||
pthread_mutex_t mutex; /* 互斥锁(多生产者场景) */
|
||||
pthread_cond_t not_empty; /* 非空条件变量 */
|
||||
pthread_cond_t not_full; /* 非满条件变量 */
|
||||
ring_buffer_stats_t stats; /* 统计信息 */
|
||||
bool high_watermark; /* 高水位标志 */
|
||||
bool initialized; /* 初始化标志 */
|
||||
} ring_buffer_t;
|
||||
|
||||
/* ======================== 内部工具函数 ======================== */
|
||||
|
||||
/**
|
||||
* 计算缓冲区当前已使用字节数
|
||||
*/
|
||||
static uint32_t ring_buffer_used(const ring_buffer_t *rb)
|
||||
{
|
||||
uint32_t wp = rb->write_pos;
|
||||
uint32_t rp = rb->read_pos;
|
||||
|
||||
if (wp >= rp) {
|
||||
return wp - rp;
|
||||
} else {
|
||||
/* 写指针已回绕 */
|
||||
return rb->capacity - rp + wp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算缓冲区剩余可用字节数
|
||||
* 预留1字节防止读写指针重合导致空/满状态混淆
|
||||
*/
|
||||
static uint32_t ring_buffer_free(const ring_buffer_t *rb)
|
||||
{
|
||||
return rb->capacity - ring_buffer_used(rb) - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据写入环形缓冲区(处理回绕)
|
||||
* 内部函数,调用者需确保空间足够
|
||||
*/
|
||||
static void ring_write_bytes(ring_buffer_t *rb, const uint8_t *data,
|
||||
uint32_t len)
|
||||
{
|
||||
uint32_t wp = rb->write_pos;
|
||||
|
||||
/* 计算到缓冲区末尾的连续空间 */
|
||||
uint32_t tail_space = rb->capacity - wp;
|
||||
|
||||
if (len <= tail_space) {
|
||||
/* 无需回绕,直接拷贝 */
|
||||
memcpy(rb->buffer + wp, data, len);
|
||||
} else {
|
||||
/* 需要回绕:先写尾部,再写头部 */
|
||||
memcpy(rb->buffer + wp, data, tail_space);
|
||||
memcpy(rb->buffer, data + tail_space, len - tail_space);
|
||||
}
|
||||
|
||||
/* 更新写指针(使用取模运算处理回绕) */
|
||||
rb->write_pos = (wp + len) % rb->capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从环形缓冲区读取数据(处理回绕)
|
||||
* 内部函数,调用者需确保数据充足
|
||||
*/
|
||||
static void ring_read_bytes(ring_buffer_t *rb, uint8_t *data, uint32_t len)
|
||||
{
|
||||
uint32_t rp = rb->read_pos;
|
||||
|
||||
/* 计算到缓冲区末尾的连续数据 */
|
||||
uint32_t tail_data = rb->capacity - rp;
|
||||
|
||||
if (len <= tail_data) {
|
||||
memcpy(data, rb->buffer + rp, len);
|
||||
} else {
|
||||
/* 回绕读取 */
|
||||
memcpy(data, rb->buffer + rp, tail_data);
|
||||
memcpy(data + tail_data, rb->buffer, len - tail_data);
|
||||
}
|
||||
|
||||
/* 更新读指针 */
|
||||
rb->read_pos = (rp + len) % rb->capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 窥探缓冲区数据但不移动读指针
|
||||
* 用于预读消息头判断消息长度
|
||||
*/
|
||||
static void ring_peek_bytes(const ring_buffer_t *rb, uint8_t *data,
|
||||
uint32_t len)
|
||||
{
|
||||
uint32_t rp = rb->read_pos;
|
||||
uint32_t tail_data = rb->capacity - rp;
|
||||
|
||||
if (len <= tail_data) {
|
||||
memcpy(data, rb->buffer + rp, len);
|
||||
} else {
|
||||
memcpy(data, rb->buffer + rp, tail_data);
|
||||
memcpy(data + tail_data, rb->buffer, len - tail_data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并更新水位线状态
|
||||
* 高水位时触发告警,低水位时恢复
|
||||
*/
|
||||
static void check_watermark(ring_buffer_t *rb)
|
||||
{
|
||||
uint32_t used = ring_buffer_used(rb);
|
||||
uint32_t usage_pct = (used * 100) / rb->capacity;
|
||||
|
||||
/* 更新峰值记录 */
|
||||
if (used > rb->stats.peak_usage) {
|
||||
rb->stats.peak_usage = used;
|
||||
}
|
||||
|
||||
if (!rb->high_watermark && usage_pct >= HIGH_WATERMARK_PCT) {
|
||||
rb->high_watermark = true;
|
||||
printf("[环形缓冲] 高水位告警: 使用率=%u%%, 已用=%u/%u字节\n",
|
||||
usage_pct, used, rb->capacity);
|
||||
} else if (rb->high_watermark && usage_pct <= LOW_WATERMARK_PCT) {
|
||||
rb->high_watermark = false;
|
||||
printf("[环形缓冲] 水位恢复正常: 使用率=%u%%\n", usage_pct);
|
||||
}
|
||||
}
|
||||
|
||||
/* ======================== 公共接口 ======================== */
|
||||
|
||||
/**
|
||||
* 创建并初始化环形缓冲区
|
||||
*
|
||||
* @param capacity 缓冲区容量(字节),0表示使用默认值2MB
|
||||
* @return 缓冲区指针,NULL表示失败
|
||||
*/
|
||||
ring_buffer_t *ring_buffer_create(uint32_t capacity)
|
||||
{
|
||||
ring_buffer_t *rb = (ring_buffer_t *)calloc(1, sizeof(ring_buffer_t));
|
||||
if (rb == NULL) {
|
||||
printf("[环形缓冲] 内存分配失败\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rb->capacity = (capacity > 0) ? capacity : DEFAULT_BUFFER_SIZE;
|
||||
rb->buffer = (uint8_t *)malloc(rb->capacity);
|
||||
if (rb->buffer == NULL) {
|
||||
printf("[环形缓冲] 缓冲区内存分配失败, 请求=%u字节\n", rb->capacity);
|
||||
free(rb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 初始化同步原语 */
|
||||
pthread_mutex_init(&rb->mutex, NULL);
|
||||
pthread_cond_init(&rb->not_empty, NULL);
|
||||
pthread_cond_init(&rb->not_full, NULL);
|
||||
|
||||
rb->write_pos = 0;
|
||||
rb->read_pos = 0;
|
||||
rb->high_watermark = false;
|
||||
rb->initialized = true;
|
||||
|
||||
memset(&rb->stats, 0, sizeof(rb->stats));
|
||||
|
||||
printf("[环形缓冲] 初始化完成, 容量=%u字节 (%.1f MB)\n",
|
||||
rb->capacity, (float)rb->capacity / (1024 * 1024));
|
||||
|
||||
return rb;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁环形缓冲区,释放所有资源
|
||||
*/
|
||||
void ring_buffer_destroy(ring_buffer_t *rb)
|
||||
{
|
||||
if (rb == NULL) return;
|
||||
|
||||
pthread_mutex_destroy(&rb->mutex);
|
||||
pthread_cond_destroy(&rb->not_empty);
|
||||
pthread_cond_destroy(&rb->not_full);
|
||||
|
||||
if (rb->buffer) {
|
||||
free(rb->buffer);
|
||||
}
|
||||
|
||||
printf("[环形缓冲] 已销毁, 总写入=%lu, 总读取=%lu, 丢弃=%lu\n",
|
||||
rb->stats.total_write, rb->stats.total_read,
|
||||
rb->stats.total_dropped);
|
||||
|
||||
free(rb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入一条消息到环形缓冲区
|
||||
* 消息格式:[ring_msg_header_t][payload_data]
|
||||
*
|
||||
* @param rb 缓冲区指针
|
||||
* @param msg_type 消息类型
|
||||
* @param payload 消息负载数据
|
||||
* @param payload_len 负载长度
|
||||
* @return 0=成功, -1=消息过大, -2=缓冲区满
|
||||
*/
|
||||
int ring_buffer_write(ring_buffer_t *rb, uint16_t msg_type,
|
||||
const uint8_t *payload, uint32_t payload_len)
|
||||
{
|
||||
if (rb == NULL || !rb->initialized) return -1;
|
||||
|
||||
/* 检查消息大小限制 */
|
||||
uint32_t total_size = sizeof(ring_msg_header_t) + payload_len;
|
||||
if (payload_len > MAX_MESSAGE_SIZE || total_size > rb->capacity / 2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&rb->mutex);
|
||||
|
||||
/* 检查剩余空间 */
|
||||
if (ring_buffer_free(rb) < total_size) {
|
||||
/* 缓冲区空间不足,丢弃消息 */
|
||||
rb->stats.total_dropped++;
|
||||
rb->stats.overflow_count++;
|
||||
pthread_mutex_unlock(&rb->mutex);
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* 构建消息头 */
|
||||
ring_msg_header_t header;
|
||||
header.magic = MSG_HEADER_MAGIC;
|
||||
header.msg_type = msg_type;
|
||||
header.payload_len = payload_len;
|
||||
header.timestamp = (uint32_t)time(NULL);
|
||||
|
||||
/* 写入消息头 */
|
||||
ring_write_bytes(rb, (const uint8_t *)&header, sizeof(header));
|
||||
|
||||
/* 写入消息负载 */
|
||||
if (payload_len > 0) {
|
||||
ring_write_bytes(rb, payload, payload_len);
|
||||
}
|
||||
|
||||
/* 更新统计 */
|
||||
rb->stats.total_write++;
|
||||
rb->stats.total_bytes_in += total_size;
|
||||
|
||||
/* 检查水位线 */
|
||||
check_watermark(rb);
|
||||
|
||||
/* 通知等待的消费者 */
|
||||
pthread_cond_signal(&rb->not_empty);
|
||||
|
||||
pthread_mutex_unlock(&rb->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从环形缓冲区读取一条消息
|
||||
*
|
||||
* @param rb 缓冲区指针
|
||||
* @param msg_type 输出: 消息类型
|
||||
* @param payload 输出: 消息负载缓冲区
|
||||
* @param payload_max 负载缓冲区最大长度
|
||||
* @param payload_len 输出: 实际负载长度
|
||||
* @return 0=成功, -1=缓冲区空, -2=消息头损坏
|
||||
*/
|
||||
int ring_buffer_read(ring_buffer_t *rb, uint16_t *msg_type,
|
||||
uint8_t *payload, uint32_t payload_max,
|
||||
uint32_t *payload_len)
|
||||
{
|
||||
if (rb == NULL || !rb->initialized) return -1;
|
||||
|
||||
pthread_mutex_lock(&rb->mutex);
|
||||
|
||||
/* 检查是否有数据可读 */
|
||||
uint32_t available = ring_buffer_used(rb);
|
||||
if (available < sizeof(ring_msg_header_t)) {
|
||||
pthread_mutex_unlock(&rb->mutex);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 预读消息头(不移动读指针) */
|
||||
ring_msg_header_t header;
|
||||
ring_peek_bytes(rb, (uint8_t *)&header, sizeof(header));
|
||||
|
||||
/* 验证消息头魔数 */
|
||||
if (header.magic != MSG_HEADER_MAGIC) {
|
||||
/* 消息头损坏 - 尝试跳过一个字节寻找下一个有效消息头 */
|
||||
rb->read_pos = (rb->read_pos + 1) % rb->capacity;
|
||||
pthread_mutex_unlock(&rb->mutex);
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* 检查完整消息是否可用 */
|
||||
uint32_t total_size = sizeof(ring_msg_header_t) + header.payload_len;
|
||||
if (available < total_size) {
|
||||
/* 消息不完整,等待更多数据 */
|
||||
pthread_mutex_unlock(&rb->mutex);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 跳过消息头 */
|
||||
rb->read_pos = (rb->read_pos + sizeof(ring_msg_header_t)) % rb->capacity;
|
||||
|
||||
/* 读取消息负载 */
|
||||
uint32_t read_len = header.payload_len;
|
||||
if (read_len > payload_max) {
|
||||
read_len = payload_max;
|
||||
/* 跳过剩余无法容纳的部分 */
|
||||
uint8_t discard_buf[256];
|
||||
uint32_t skip = header.payload_len - payload_max;
|
||||
while (skip > 0) {
|
||||
uint32_t chunk = (skip > sizeof(discard_buf)) ?
|
||||
sizeof(discard_buf) : skip;
|
||||
ring_read_bytes(rb, discard_buf, chunk);
|
||||
skip -= chunk;
|
||||
}
|
||||
}
|
||||
|
||||
if (read_len > 0) {
|
||||
ring_read_bytes(rb, payload, read_len);
|
||||
}
|
||||
|
||||
/* 输出结果 */
|
||||
if (msg_type) *msg_type = header.msg_type;
|
||||
if (payload_len) *payload_len = read_len;
|
||||
|
||||
/* 更新统计 */
|
||||
rb->stats.total_read++;
|
||||
rb->stats.total_bytes_out += total_size;
|
||||
|
||||
/* 通知等待的生产者 */
|
||||
pthread_cond_signal(&rb->not_full);
|
||||
|
||||
pthread_mutex_unlock(&rb->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓冲区使用率百分比
|
||||
*/
|
||||
uint32_t ring_buffer_usage_percent(const ring_buffer_t *rb)
|
||||
{
|
||||
if (rb == NULL || rb->capacity == 0) return 0;
|
||||
return (ring_buffer_used(rb) * 100) / rb->capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓冲区统计信息副本
|
||||
*/
|
||||
void ring_buffer_get_stats(const ring_buffer_t *rb, ring_buffer_stats_t *stats)
|
||||
{
|
||||
if (rb == NULL || stats == NULL) return;
|
||||
memcpy(stats, &rb->stats, sizeof(ring_buffer_stats_t));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓冲区所有数据
|
||||
*/
|
||||
void ring_buffer_flush(ring_buffer_t *rb)
|
||||
{
|
||||
if (rb == NULL) return;
|
||||
|
||||
pthread_mutex_lock(&rb->mutex);
|
||||
rb->write_pos = 0;
|
||||
rb->read_pos = 0;
|
||||
rb->high_watermark = false;
|
||||
printf("[环形缓冲] 已清空, 丢弃消息=%lu\n", rb->stats.total_dropped);
|
||||
pthread_mutex_unlock(&rb->mutex);
|
||||
}
|
||||
Reference in New Issue
Block a user