/** * 自然写教室智能网关管理软件 V1.0 * * offline_cache.c - 断网离线缓存模块 (SQLite) * * 功能说明: * - 网络断开时将笔迹数据持久化到SQLite数据库 * - 网络恢复后按FIFO顺序自动续传 * - 缓存容量管理(64MB上限,超出时淘汰最旧数据) * - 数据完整性校验(CRC32) * - 续传进度跟踪与断点恢复 */ #include #include #include #include #include #include #include #include #include /* ======================== 常量定义 ======================== */ /* 离线缓存数据库路径 */ #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); }