feat: 优化窗口大小的上报机制,新增LZ4的压缩协议实现(不启用)
This commit is contained in:
parent
0b73d9917a
commit
63b7398f56
|
|
@ -0,0 +1,141 @@
|
|||
# 协议压缩支持
|
||||
|
||||
## 概述
|
||||
|
||||
协议栈支持可选的 LZ4 压缩,可以显著减少网络带宽消耗,特别适用于终端输出等重复性较高的数据。
|
||||
|
||||
## 压缩算法选择
|
||||
|
||||
我们选择 **LZ4** 作为压缩算法,原因如下:
|
||||
|
||||
| 特性 | LZ4 | zlib | zstd |
|
||||
|------|-----|------|------|
|
||||
| 压缩速度 | ~500 MB/s | ~50 MB/s | ~300 MB/s |
|
||||
| 解压速度 | ~1500 MB/s | ~250 MB/s | ~800 MB/s |
|
||||
| 压缩比 | 2:1 ~ 3:1 | 3:1 ~ 5:1 | 3:1 ~ 5:1 |
|
||||
| 延迟影响 | 极低 | 中等 | 低 |
|
||||
|
||||
对于终端数据流,LZ4 的极低延迟和高速度是最佳选择。
|
||||
|
||||
## 消息头格式
|
||||
|
||||
```
|
||||
+--------+--------+------------+------------+
|
||||
| Magic | Type | PayloadLen | Reserved |
|
||||
| 4字节 | 4字节 | 4字节 | 4字节 |
|
||||
+--------+--------+------------+------------+
|
||||
```
|
||||
|
||||
**Reserved 字段用法:**
|
||||
- **低 16 位**:压缩标志
|
||||
- bit 0: `MSG_FLAG_COMPRESSED` (0x01) - 载荷已压缩
|
||||
- bit 1: `MSG_FLAG_COMPRESS_LZ4` (0x02) - 使用 LZ4 压缩
|
||||
- bit 2: `MSG_FLAG_COMPRESS_HC` (0x04) - 使用高压缩比模式
|
||||
- **高 16 位**:原始大小 / 256(用于解压缓冲区分配)
|
||||
|
||||
## 使用方法
|
||||
|
||||
### C 端
|
||||
|
||||
```c
|
||||
#include "socket_protocol.h"
|
||||
#include "compression.h"
|
||||
|
||||
// 初始化协议上下文(启用 LZ4 压缩)
|
||||
ProtocolContext ctx;
|
||||
protocol_init(&ctx, COMPRESS_LZ4, 0); // 0 = 默认级别
|
||||
|
||||
// 发送压缩消息
|
||||
write_message_compressed(sock, &ctx, MSG_TYPE_TERMINAL_OUTPUT, data, data_len);
|
||||
|
||||
// 接收并解压消息
|
||||
MessageType type;
|
||||
void* payload;
|
||||
uint32_t payload_len, original_len;
|
||||
read_message_decompressed(sock, &type, &payload, &payload_len, &original_len);
|
||||
|
||||
// 获取压缩统计
|
||||
uint64_t in, out, count, skip;
|
||||
compression_get_stats(&ctx.compress_ctx, &in, &out, &count, &skip);
|
||||
printf("压缩比: %d%%\n", compression_get_ratio(&ctx.compress_ctx));
|
||||
```
|
||||
|
||||
### Go 端
|
||||
|
||||
```go
|
||||
import "go_service/internal/socket"
|
||||
|
||||
// 创建带压缩的连接
|
||||
conn := socket.NewConnectionWithCompression(netConn, socket.CompressLZ4, 0)
|
||||
|
||||
// 或者在现有连接上启用压缩
|
||||
conn.EnableCompression(socket.CompressLZ4, 0)
|
||||
|
||||
// 发送压缩消息
|
||||
ctx := socket.NewCompressionContext(socket.CompressLZ4, 0)
|
||||
socket.WriteMessageCompressed(netConn, ctx, socket.MsgTypeServerResponse, data)
|
||||
|
||||
// 接收并自动解压
|
||||
msgType, payload, err := socket.ReadMessageWithDecompression(netConn)
|
||||
|
||||
// 获取统计信息
|
||||
bytesIn, bytesOut, compCount, skipCount, ratio := conn.GetCompressionStats()
|
||||
fmt.Printf("压缩比: %d%%\n", ratio)
|
||||
```
|
||||
|
||||
## 编译配置
|
||||
|
||||
### C 端
|
||||
|
||||
```bash
|
||||
# 安装 LZ4 库
|
||||
make install-lz4
|
||||
|
||||
# 编译(自动检测 LZ4)
|
||||
make
|
||||
|
||||
# 禁用压缩
|
||||
make LZ4=0
|
||||
|
||||
# 查看是否启用了压缩
|
||||
make pre_build
|
||||
```
|
||||
|
||||
### Go 端
|
||||
|
||||
LZ4 库会自动通过 `go mod tidy` 安装。
|
||||
|
||||
## 压缩阈值
|
||||
|
||||
- 小于 64 字节的数据不会被压缩(开销大于收益)
|
||||
- 如果压缩后数据更大,会自动回退到原始数据
|
||||
|
||||
## 兼容性
|
||||
|
||||
- 新版本可以读取旧版本(无压缩)的消息
|
||||
- 旧版本可以读取新版本的未压缩消息
|
||||
- 旧版本无法读取压缩消息(会因为 reserved 字段不为 0 而有不同行为,但基本功能仍可工作)
|
||||
|
||||
## 性能预期
|
||||
|
||||
典型终端场景压缩效果:
|
||||
|
||||
| 数据类型 | 原始大小 | 压缩后 | 节省 |
|
||||
|----------|----------|--------|------|
|
||||
| ls 输出 | 1000 B | 400 B | 60% |
|
||||
| 代码文件 | 10 KB | 3.5 KB | 65% |
|
||||
| 日志输出 | 5 KB | 1.2 KB | 76% |
|
||||
| 二进制数据 | 1 KB | 900 B | 10% |
|
||||
|
||||
## 调试
|
||||
|
||||
启用调试模式可以看到压缩详情:
|
||||
|
||||
```bash
|
||||
# C 端
|
||||
make DEBUG=1
|
||||
|
||||
# 运行后会输出类似:
|
||||
# [DEBUG] 压缩成功: 1024 -> 412 (40.2%)
|
||||
# [DEBUG] 解压成功: 412 -> 1024 字节
|
||||
```
|
||||
270
Makefile
270
Makefile
|
|
@ -1,7 +1,16 @@
|
|||
# 检测架构
|
||||
# ==========================================
|
||||
# 编译选项(在文件开头定义)
|
||||
# ==========================================
|
||||
DEBUG ?= 0
|
||||
HOOK ?= 0
|
||||
NO_CONFIG_CHECK ?= 0
|
||||
LZ4 ?= 0
|
||||
|
||||
# ==========================================
|
||||
# 架构检测与交叉编译配置
|
||||
# ==========================================
|
||||
ARCH ?= $(shell uname -m)
|
||||
|
||||
# 交叉编译工具链配置
|
||||
ifeq ($(ARCH),arm64)
|
||||
CC = aarch64-linux-gnu-gcc
|
||||
TARGET_SUFFIX = _arm64
|
||||
|
|
@ -20,52 +29,113 @@ else
|
|||
TARGET_SUFFIX = _x86_64
|
||||
endif
|
||||
|
||||
CFLAGS = -shared -fPIC -Wall -Wextra -Werror -fno-strict-aliasing -fPIC -fno-omit-frame-pointer -fno-stack-protector -Wl,-z,relro,-z,now
|
||||
LDFLAGS = -ldl
|
||||
|
||||
TARGET_NAME = libbash_smart$(TARGET_SUFFIX).so
|
||||
|
||||
# ==========================================
|
||||
# 目录配置
|
||||
# ==========================================
|
||||
BUILD_DIR = build
|
||||
SRC_DIR = src
|
||||
TESTS_DIR = tests
|
||||
|
||||
SRC = $(wildcard $(SRC_DIR)/*.c)
|
||||
# ==========================================
|
||||
# LZ4 压缩支持检测(必须在使用前完成)
|
||||
# ==========================================
|
||||
LZ4_CFLAGS =
|
||||
LZ4_LDFLAGS =
|
||||
HAVE_LZ4 = 0
|
||||
|
||||
OBJ = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRC))
|
||||
ifeq ($(LZ4),1)
|
||||
LZ4_AVAILABLE := $(shell pkg-config --exists liblz4 2>/dev/null && echo 1 || echo 0)
|
||||
ifeq ($(LZ4_AVAILABLE),1)
|
||||
LZ4_CFLAGS := -DHAVE_LZ4 $(shell pkg-config --cflags liblz4)
|
||||
LZ4_LDFLAGS := $(shell pkg-config --libs liblz4)
|
||||
HAVE_LZ4 = 1
|
||||
else
|
||||
LZ4_DIRECT := $(shell echo 'int LZ4_compress_default(void);int main(){return 0;}' | $(CC) -x c - -llz4 -o /dev/null 2>/dev/null && echo 1 || echo 0)
|
||||
ifeq ($(LZ4_DIRECT),1)
|
||||
LZ4_CFLAGS := -DHAVE_LZ4
|
||||
LZ4_LDFLAGS := -llz4
|
||||
HAVE_LZ4 = 1
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
# ==========================================
|
||||
# 编译标志
|
||||
# ==========================================
|
||||
CFLAGS_BASE = -Wall -Wextra -Werror -fno-strict-aliasing -fno-omit-frame-pointer -fno-stack-protector
|
||||
CFLAGS_SHARED = -shared -fPIC -Wl,-z,relro,-z,now
|
||||
LDFLAGS_BASE = -ldl
|
||||
|
||||
# 条件编译标志
|
||||
ifeq ($(DEBUG),1)
|
||||
CFLAGS_BASE += -DDEBUG -g
|
||||
endif
|
||||
|
||||
ifeq ($(HOOK),1)
|
||||
CFLAGS_BASE += -DHOOK
|
||||
endif
|
||||
|
||||
ifeq ($(NO_CONFIG_CHECK),1)
|
||||
CFLAGS_BASE += -DNO_CONFIG_CHECK
|
||||
endif
|
||||
|
||||
# 添加 LZ4 标志
|
||||
CFLAGS_BASE += $(LZ4_CFLAGS)
|
||||
LDFLAGS_BASE += $(LZ4_LDFLAGS)
|
||||
|
||||
# 最终编译标志
|
||||
CFLAGS = $(CFLAGS_BASE) $(CFLAGS_SHARED)
|
||||
LDFLAGS = $(LDFLAGS_BASE)
|
||||
|
||||
# 测试程序编译标志(不需要 -shared)
|
||||
TEST_CFLAGS = $(CFLAGS_BASE) -pthread
|
||||
TEST_LDFLAGS = $(LZ4_LDFLAGS)
|
||||
|
||||
# ==========================================
|
||||
# 源文件与目标文件
|
||||
# ==========================================
|
||||
TARGET_NAME = libbash_smart$(TARGET_SUFFIX).so
|
||||
TARGET = $(BUILD_DIR)/$(TARGET_NAME)
|
||||
|
||||
# 条件编译的 compression.o
|
||||
ifeq ($(HAVE_LZ4),1)
|
||||
COMPRESSION_OBJ = $(BUILD_DIR)/compression.o
|
||||
else
|
||||
COMPRESSION_OBJ =
|
||||
endif
|
||||
|
||||
# 源文件(排除 compression.c,它是条件编译的)
|
||||
SRC = $(filter-out $(SRC_DIR)/compression.c, $(wildcard $(SRC_DIR)/*.c))
|
||||
OBJ = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRC)) $(COMPRESSION_OBJ)
|
||||
|
||||
# ==========================================
|
||||
# 测试程序配置
|
||||
# ==========================================
|
||||
# 公共依赖
|
||||
TEST_COMMON_DEPS = $(BUILD_DIR)/client.o $(BUILD_DIR)/socket_protocol.o $(BUILD_DIR)/debug.o $(COMPRESSION_OBJ)
|
||||
|
||||
# 测试客户端
|
||||
TEST_CLIENT = $(BUILD_DIR)/test_client
|
||||
TEST_CLIENT_SRC = $(TESTS_DIR)/test_client.c
|
||||
TEST_CLIENT_DEPS = $(BUILD_DIR)/client.o $(BUILD_DIR)/socket_protocol.o $(BUILD_DIR)/debug.o
|
||||
|
||||
# 并发测试客户端
|
||||
TEST_CONCURRENT_CLIENT = $(BUILD_DIR)/test_concurrent_client
|
||||
TEST_CONCURRENT_CLIENT_SRC = $(TESTS_DIR)/test_concurrent_client.c
|
||||
TEST_CONCURRENT_CLIENT_DEPS = $(BUILD_DIR)/client.o $(BUILD_DIR)/socket_protocol.o $(BUILD_DIR)/debug.o
|
||||
|
||||
# Socket通信测试客户端
|
||||
TEST_SOCKET_CLIENT = $(BUILD_DIR)/test_socket_client
|
||||
TEST_SOCKET_CLIENT_SRC = $(TESTS_DIR)/test_socket_client.c
|
||||
TEST_SOCKET_CLIENT_DEPS = $(BUILD_DIR)/client.o $(BUILD_DIR)/socket_protocol.o $(BUILD_DIR)/debug.o
|
||||
|
||||
# 如果需要开启 debug,只需执行 make DEBUG=1
|
||||
ifeq ($(DEBUG),1)
|
||||
CFLAGS += -DDEBUG -g
|
||||
endif
|
||||
|
||||
# 如果需要开启 hook,只需执行 make HOOK=1
|
||||
ifeq ($(HOOK),1)
|
||||
CFLAGS += -DHOOK
|
||||
endif
|
||||
|
||||
# 如果跳过检查,只需执行 make NO_CONFIG_CHECK=1
|
||||
ifeq ($(NO_CONFIG_CHECK),1)
|
||||
CFLAGS += -DNO_CONFIG_CHECK
|
||||
endif
|
||||
|
||||
.PHONY: all clean debug hook rebuild pre_build test_client test_concurrent_client test_socket_client arm64 arm install-cross-tools
|
||||
# ==========================================
|
||||
# 伪目标声明
|
||||
# ==========================================
|
||||
.PHONY: all clean debug hook rebuild pre_build \
|
||||
test_client test_concurrent_client test_socket_client \
|
||||
arm64 arm install-cross-tools install-lz4
|
||||
|
||||
# ==========================================
|
||||
# 主要构建目标
|
||||
# ==========================================
|
||||
all: pre_build $(TARGET)
|
||||
|
||||
# 快捷方式:交叉编译到 ARM64
|
||||
|
|
@ -76,6 +146,78 @@ arm64:
|
|||
arm:
|
||||
$(MAKE) ARCH=arm
|
||||
|
||||
# ==========================================
|
||||
# 测试程序构建
|
||||
# ==========================================
|
||||
test_client: pre_build $(TEST_CLIENT)
|
||||
|
||||
test_concurrent_client: pre_build $(TEST_CONCURRENT_CLIENT)
|
||||
|
||||
test_socket_client: pre_build $(TEST_SOCKET_CLIENT)
|
||||
|
||||
# ==========================================
|
||||
# 预构建信息输出
|
||||
# ==========================================
|
||||
pre_build:
|
||||
@echo "=========================================="
|
||||
@echo "编译配置:"
|
||||
@echo " 架构: $(ARCH)"
|
||||
@echo " 编译器: $(CC)"
|
||||
@echo " 目标文件: $(TARGET_NAME)"
|
||||
@echo " DEBUG: $(DEBUG)"
|
||||
@echo " HOOK: $(HOOK)"
|
||||
@echo " NO_CONFIG_CHECK: $(NO_CONFIG_CHECK)"
|
||||
@echo " LZ4: $(LZ4) (可用: $(HAVE_LZ4))"
|
||||
@echo "=========================================="
|
||||
|
||||
# ==========================================
|
||||
# 编译规则
|
||||
# ==========================================
|
||||
# 通用 .o 文件编译规则
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# 主目标链接
|
||||
$(TARGET): $(OBJ)
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
@rm -f $(OBJ)
|
||||
|
||||
# 测试程序编译
|
||||
$(TEST_CLIENT): $(TEST_CLIENT_SRC) $(TEST_COMMON_DEPS)
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
$(CC) $(TEST_CFLAGS) -o $@ $< $(TEST_COMMON_DEPS) $(TEST_LDFLAGS)
|
||||
|
||||
$(TEST_CONCURRENT_CLIENT): $(TEST_CONCURRENT_CLIENT_SRC) $(TEST_COMMON_DEPS)
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
$(CC) $(TEST_CFLAGS) -o $@ $< $(TEST_COMMON_DEPS) $(TEST_LDFLAGS)
|
||||
|
||||
$(TEST_SOCKET_CLIENT): $(TEST_SOCKET_CLIENT_SRC) $(TEST_COMMON_DEPS)
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
$(CC) $(TEST_CFLAGS) -o $@ $< $(TEST_COMMON_DEPS) $(TEST_LDFLAGS)
|
||||
|
||||
# ==========================================
|
||||
# 清理与重建
|
||||
# ==========================================
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
|
||||
rebuild: clean all
|
||||
|
||||
# 快捷方式:debug 模式重建
|
||||
debug:
|
||||
$(MAKE) clean
|
||||
$(MAKE) DEBUG=1
|
||||
|
||||
# 快捷方式:hook 模式重建
|
||||
hook:
|
||||
$(MAKE) clean
|
||||
$(MAKE) HOOK=1
|
||||
|
||||
# ==========================================
|
||||
# 工具安装
|
||||
# ==========================================
|
||||
# 安装交叉编译工具链
|
||||
install-cross-tools:
|
||||
@echo "安装 ARM 交叉编译工具链..."
|
||||
|
|
@ -95,59 +237,19 @@ install-cross-tools:
|
|||
fi
|
||||
@echo "交叉编译工具链安装完成!"
|
||||
|
||||
test_client: pre_build $(TEST_CLIENT)
|
||||
|
||||
test_concurrent_client: pre_build $(TEST_CONCURRENT_CLIENT)
|
||||
|
||||
test_socket_client: pre_build $(TEST_SOCKET_CLIENT)
|
||||
|
||||
pre_build:
|
||||
@echo "=========================================="
|
||||
@echo "编译配置:"
|
||||
@echo " 架构: $(ARCH)"
|
||||
@echo " 编译器: $(CC)"
|
||||
@echo " 目标文件: $(TARGET_NAME)"
|
||||
@echo "=========================================="
|
||||
ifeq ($(DEBUG),1)
|
||||
@echo "Building with debug flags..."
|
||||
endif
|
||||
ifeq ($(HOOK),1)
|
||||
@echo "Building with hook flags..."
|
||||
endif
|
||||
ifeq ($(NO_CONFIG_CHECK),1)
|
||||
@echo "Building with NO_CONFIG_CHECK defined..."
|
||||
endif
|
||||
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
@rm -f $(OBJ)
|
||||
|
||||
$(TEST_CLIENT): $(TEST_CLIENT_SRC) $(TEST_CLIENT_DEPS)
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
$(CC) -Wall -Wextra -pthread -o $@ $(TEST_CLIENT_SRC) $(TEST_CLIENT_DEPS)
|
||||
|
||||
$(TEST_CONCURRENT_CLIENT): $(TEST_CONCURRENT_CLIENT_SRC) $(TEST_CONCURRENT_CLIENT_DEPS)
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
$(CC) -Wall -Wextra -pthread -o $@ $(TEST_CONCURRENT_CLIENT_SRC) $(TEST_CONCURRENT_CLIENT_DEPS)
|
||||
|
||||
$(TEST_SOCKET_CLIENT): $(TEST_SOCKET_CLIENT_SRC) $(TEST_SOCKET_CLIENT_DEPS)
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
$(CC) -Wall -Wextra -pthread -o $@ $(TEST_SOCKET_CLIENT_SRC) $(TEST_SOCKET_CLIENT_DEPS)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
|
||||
debug:
|
||||
rm -rf $(BUILD_DIR)
|
||||
$(MAKE) DEBUG=1
|
||||
|
||||
hook:
|
||||
rm -rf $(BUILD_DIR)
|
||||
$(MAKE) HOOK=1
|
||||
|
||||
rebuild: clean all
|
||||
# 安装 LZ4 库
|
||||
install-lz4:
|
||||
@echo "安装 LZ4 压缩库..."
|
||||
@if command -v apt-get > /dev/null 2>&1; then \
|
||||
sudo apt-get install -y liblz4-dev; \
|
||||
elif command -v yum > /dev/null 2>&1; then \
|
||||
sudo yum install -y lz4-devel; \
|
||||
elif command -v pacman > /dev/null 2>&1; then \
|
||||
sudo pacman -S --noconfirm lz4; \
|
||||
elif command -v brew > /dev/null 2>&1; then \
|
||||
brew install lz4; \
|
||||
else \
|
||||
echo "无法自动安装,请手动安装 liblz4-dev"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "LZ4 安装完成!"
|
||||
|
|
@ -241,7 +241,8 @@ flowchart LR
|
|||
|
||||
3. **窗口监控线程** (`window_monitor_thread`)
|
||||
- 监听 `SIGWINCH` 信号(窗口大小变化)
|
||||
- 定期检查 `g_window_size_changed` 标志
|
||||
- 使用 **条件变量** (`pthread_cond_t`) 实现事件驱动等待
|
||||
- 信号触发时立即唤醒,无需轮询
|
||||
- 自动发送 `MSG_TYPE_WINDOW_SIZE_UPDATE` 消息
|
||||
|
||||
4. **输入监听线程** (`terminal_input_thread`)
|
||||
|
|
@ -726,7 +727,9 @@ go_service/
|
|||
|
||||
## 性能考虑
|
||||
|
||||
- **轮询间隔**:100ms(平衡响应性和 CPU)
|
||||
- **窗口监听**:使用条件变量实现事件驱动,零 CPU 占用等待
|
||||
- **输入监听轮询间隔**:100ms(平衡响应性和 CPU)
|
||||
- **响应监听轮询间隔**:500ms(用于检测退出标志)
|
||||
- **消息大小**:终端事件 20-80 字节
|
||||
- **线程开销**:固定 4 个线程,不会增长
|
||||
- **内存使用**:动态分配,用完即释放
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
### 2. 实时窗口大小监听
|
||||
|
||||
- **信号驱动**:使用 `SIGWINCH` 信号捕获终端窗口大小变化
|
||||
- **条件变量唤醒**:使用 `pthread_cond_t` 实现高效的事件驱动等待
|
||||
- **零延迟响应**:信号触发时立即唤醒处理线程,无需轮询
|
||||
- **独立线程**:专门的监控线程处理窗口事件,不阻塞主流程
|
||||
- **自动同步**:窗口大小变化时自动发送更新消息给服务端
|
||||
|
||||
|
|
@ -115,7 +117,9 @@ make test_window_resize
|
|||
### 1. 现代C编程实践
|
||||
|
||||
- 使用 `pthread` 多线程
|
||||
- 使用 `pthread_cond_t` 条件变量实现事件驱动
|
||||
- 使用 `sigaction` 处理信号(而非过时的 `signal`)
|
||||
- 使用 `clock_gettime` 实现带超时的条件等待
|
||||
- 结构化错误处理
|
||||
- 内存管理规范(malloc/free配对)
|
||||
|
||||
|
|
@ -129,8 +133,9 @@ make test_window_resize
|
|||
|
||||
- **C端**:
|
||||
- 主线程:发送初始消息
|
||||
- 窗口监控线程:监听SIGWINCH信号
|
||||
- 窗口监控线程:监听 SIGWINCH 信号(**条件变量驱动**)
|
||||
- 响应监听线程:接收服务端消息
|
||||
- 输入监听线程:捕获键盘和鼠标事件
|
||||
|
||||
- **Go端**:
|
||||
- 主 goroutine:处理业务逻辑
|
||||
|
|
@ -155,10 +160,11 @@ typedef enum {
|
|||
|
||||
## 性能考虑
|
||||
|
||||
- **轮询间隔**:100ms,平衡响应性和CPU使用
|
||||
- **窗口监听**:使用条件变量实现事件驱动,零 CPU 占用等待,即时响应
|
||||
- **输入监听轮询间隔**:100ms,平衡响应性和 CPU 使用
|
||||
- **消息大小**:终端信息约 60-80 字节,网络开销小
|
||||
- **内存使用**:动态分配,用完即释放
|
||||
- **线程数量**:固定3个线程(C端),不会无限增长
|
||||
- **线程数量**:固定 4 个线程(C端),不会无限增长
|
||||
|
||||
## 兼容性
|
||||
|
||||
|
|
@ -173,6 +179,7 @@ typedef enum {
|
|||
3. **心跳机制**:检测连接状态
|
||||
4. **压缩传输**:对大载荷使用压缩
|
||||
5. **加密通信**:添加消息加密层
|
||||
6. ~~**条件变量优化**~~:✅ 已完成 - 使用 `pthread_cond_t` 替代轮询
|
||||
|
||||
## 测试覆盖
|
||||
|
||||
|
|
|
|||
|
|
@ -15,11 +15,24 @@
|
|||
typedef struct {
|
||||
uint32_t magic; // 魔数 0x42534D54 ("BSMT")
|
||||
uint32_t type; // 消息类型
|
||||
uint32_t payload_len; // 载荷长度
|
||||
uint32_t reserved; // 保留字段
|
||||
uint32_t payload_len; // 载荷长度(压缩后)
|
||||
uint32_t reserved; // 低16位: 压缩标志; 高16位: 原始大小/256
|
||||
} MessageHeader;
|
||||
```
|
||||
|
||||
### 压缩标志(reserved 字段)
|
||||
|
||||
```c
|
||||
#define MSG_FLAG_COMPRESSED 0x01 // 载荷已压缩
|
||||
#define MSG_FLAG_COMPRESS_LZ4 0x02 // 使用 LZ4 压缩
|
||||
#define MSG_FLAG_COMPRESS_HC 0x04 // 使用高压缩比模式
|
||||
|
||||
// 提取/构造 reserved 字段
|
||||
#define GET_COMPRESS_FLAGS(reserved) ((reserved) & 0xFFFF)
|
||||
#define GET_ORIGINAL_SIZE_HINT(reserved) (((reserved) >> 16) * 256)
|
||||
#define MAKE_RESERVED(flags, orig_size) (((flags) & 0xFFFF) | (((orig_size) / 256) << 16))
|
||||
```
|
||||
|
||||
### 消息类型
|
||||
|
||||
```c
|
||||
|
|
@ -49,19 +62,39 @@ sa.sa_handler = handle_sigwinch;
|
|||
sigaction(SIGWINCH, &sa, NULL);
|
||||
```
|
||||
|
||||
### 2. 独立监控线程
|
||||
### 2. 独立监控线程(条件变量驱动)
|
||||
|
||||
启动专门的线程监控窗口变化:
|
||||
启动专门的线程监控窗口变化,使用条件变量实现高效等待:
|
||||
|
||||
```c
|
||||
// 全局条件变量
|
||||
static pthread_mutex_t g_winch_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t g_winch_cond = PTHREAD_COND_INITIALIZER;
|
||||
|
||||
// 信号处理器中唤醒等待线程
|
||||
static void handle_sigwinch(int sig) {
|
||||
g_window_size_changed = 1;
|
||||
pthread_cond_signal(&g_winch_cond); // 立即唤醒
|
||||
}
|
||||
|
||||
static void* window_monitor_thread(void* arg) {
|
||||
while (1) {
|
||||
while (!g_should_exit) {
|
||||
pthread_mutex_lock(&g_winch_mutex);
|
||||
|
||||
// 使用条件变量等待信号,带超时以便检查退出标志
|
||||
while (!g_window_size_changed && !g_should_exit) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += 1; // 1秒超时
|
||||
pthread_cond_timedwait(&g_winch_cond, &g_winch_mutex, &ts);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_winch_mutex);
|
||||
|
||||
if (g_window_size_changed) {
|
||||
g_window_size_changed = 0;
|
||||
// 发送窗口大小更新消息
|
||||
send_terminal_info(sock, MSG_TYPE_WINDOW_SIZE_UPDATE);
|
||||
}
|
||||
usleep(100000); // 100ms
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -160,26 +193,118 @@ cd execve_hook
|
|||
execve_hook/src/
|
||||
├── socket_protocol.h # 协议定义头文件
|
||||
├── socket_protocol.c # 协议实现
|
||||
├── compression.h # 压缩模块头文件
|
||||
├── compression.c # LZ4 压缩实现
|
||||
├── client.h # 客户端头文件
|
||||
├── client.c # 客户端实现(含SIGWINCH处理)
|
||||
└── ...
|
||||
|
||||
go_service/internal/services/tasks/
|
||||
└── socket.go # Go服务端实现
|
||||
go_service/internal/socket/
|
||||
├── protocol.go # Go 协议实现
|
||||
├── compression.go # Go 压缩支持
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 性能考虑
|
||||
|
||||
1. **轮询间隔**:窗口监控线程使用 100ms 轮询间隔,平衡响应性和CPU使用
|
||||
2. **消息大小**:终端信息消息约 60-80 字节,网络开销小
|
||||
3. **并发设计**:独立的监听线程不阻塞主流程
|
||||
1. **事件驱动**:窗口监控线程使用条件变量等待 SIGWINCH 信号,零 CPU 占用
|
||||
2. **即时响应**:窗口大小变化时立即唤醒处理,无延迟
|
||||
3. **消息大小**:终端信息消息约 60-80 字节,网络开销小
|
||||
4. **并发设计**:独立的监听线程不阻塞主流程
|
||||
|
||||
## 数据压缩
|
||||
|
||||
### 压缩算法
|
||||
|
||||
本协议支持 **LZ4** 压缩算法,具有以下特点:
|
||||
- 压缩速度:~500 MB/s
|
||||
- 解压速度:~1500 MB/s
|
||||
- 压缩比:约 2:1 到 3:1
|
||||
- 内存占用:极低
|
||||
|
||||
### 何时使用压缩
|
||||
|
||||
| 场景 | 是否推荐压缩 | 原因 |
|
||||
|------|-------------|------|
|
||||
| **本地 Unix Socket** | ❌ 不推荐 | 本地通信带宽充足,压缩反而增加 CPU 开销 |
|
||||
| **远程 TCP/网络通信** | ✅ 推荐 | 减少网络带宽,特别是在弱网环境 |
|
||||
| **大量终端输出** | ✅ 推荐 | 终端输出通常是文本,压缩效果好(3:1~5:1) |
|
||||
| **小消息(<64字节)** | ❌ 自动跳过 | 小数据压缩效果差,压缩后可能更大 |
|
||||
| **二进制数据** | ⚠️ 视情况 | 已压缩的数据(如图片)压缩效果差 |
|
||||
| **低延迟要求** | ✅ LZ4 适用 | LZ4 延迟极低(<1ms),适合实时场景 |
|
||||
| **CPU 受限环境** | ❌ 不推荐 | 压缩会消耗 CPU,嵌入式设备需权衡 |
|
||||
|
||||
### 压缩模式选择
|
||||
|
||||
| 模式 | 压缩比 | 速度 | 适用场景 |
|
||||
|------|--------|------|----------|
|
||||
| `COMPRESS_NONE` | 1:1 | 最快 | 本地通信 |
|
||||
| `COMPRESS_LZ4` | ~2.5:1 | 快 | 实时终端、网络通信 |
|
||||
| `COMPRESS_LZ4_HC` | ~3:1 | 中等 | 带宽敏感、可接受少量延迟 |
|
||||
|
||||
### 启用压缩
|
||||
|
||||
**C 端(客户端)**:
|
||||
|
||||
```c
|
||||
// 初始化协议上下文并启用压缩
|
||||
ProtocolContext ctx;
|
||||
protocol_init(&ctx, COMPRESS_LZ4, 0); // LZ4 快速模式
|
||||
|
||||
// 使用压缩写入消息
|
||||
write_message_compressed(sock, &ctx, MSG_TYPE_TERMINAL_INPUT, data, len);
|
||||
```
|
||||
|
||||
**Go 端(服务端)**:
|
||||
|
||||
```go
|
||||
// 创建带压缩的连接
|
||||
conn := socket.NewConnectionWithCompression(netConn, socket.CompressLZ4, 0)
|
||||
|
||||
// 或者后期启用
|
||||
conn.EnableCompression(socket.CompressLZ4, 0)
|
||||
|
||||
// 使用自动解压读取
|
||||
msgType, payload, err := socket.ReadMessageWithDecompression(conn)
|
||||
|
||||
// 使用压缩写入
|
||||
socket.WriteMessageCompressed(conn, compCtx, msgType, payload)
|
||||
```
|
||||
|
||||
**编译时启用 LZ4(C 端)**:
|
||||
|
||||
```bash
|
||||
# 启用 LZ4 压缩支持
|
||||
make LZ4=1
|
||||
|
||||
# 或者 DEBUG + LZ4
|
||||
make DEBUG=1 LZ4=1
|
||||
```
|
||||
|
||||
### 压缩统计
|
||||
|
||||
```go
|
||||
// 获取压缩统计
|
||||
bytesIn, bytesOut, compressCount, skipCount, ratio := conn.GetCompressionStats()
|
||||
fmt.Printf("压缩比: %d%%, 压缩次数: %d, 跳过: %d\n", ratio, compressCount, skipCount)
|
||||
```
|
||||
|
||||
### 典型压缩效果
|
||||
|
||||
| 数据类型 | 原始大小 | 压缩后 | 压缩比 |
|
||||
|----------|----------|--------|--------|
|
||||
| 终端文本输出 | 4KB | ~1KB | 4:1 |
|
||||
| ANSI 转义序列 | 1KB | ~400B | 2.5:1 |
|
||||
| 初始化消息 | 2KB | ~600B | 3.3:1 |
|
||||
| 小按键输入 | 10B | 跳过 | - |
|
||||
|
||||
## 扩展建议
|
||||
|
||||
1. **动态轮询间隔**:可以根据窗口变化频率动态调整轮询间隔
|
||||
1. **动态轮询间隔**:可以根据窗口变化频率动态调整超时时间
|
||||
2. **去重机制**:连续相同的窗口大小可以不发送更新
|
||||
3. **压缩传输**:对于大量终端信息,可以考虑压缩
|
||||
3. ~~**压缩传输**~~:✅ 已完成 - 支持 LZ4 压缩
|
||||
4. **心跳机制**:添加心跳消息检测连接状态
|
||||
5. ~~**条件变量优化**~~:✅ 已完成 - 使用 `pthread_cond_t` 替代轮询,实现事件驱动
|
||||
|
||||
## 调试
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ case MsgTypeMouseEvent:
|
|||
|
||||
1. **主线程**:发送初始化消息,等待其他线程
|
||||
2. **响应监听线程**:接收服务器响应
|
||||
3. **窗口监控线程**:监听SIGWINCH信号
|
||||
3. **窗口监控线程**:监听 SIGWINCH 信号(使用**条件变量驱动**,零 CPU 占用等待)
|
||||
4. **输入监听线程**:监听stdin,捕获键盘和鼠标
|
||||
|
||||
## 使用示例
|
||||
|
|
@ -177,8 +177,9 @@ cd execve_hook
|
|||
|
||||
## 性能考虑
|
||||
|
||||
- **窗口监听**:使用条件变量事件驱动,零 CPU 占用,即时响应
|
||||
- **输入延迟**: < 10ms(poll超时100ms)
|
||||
- **CPU使用**: 每个线程休眠,不占用CPU
|
||||
- **CPU使用**: 每个线程休眠或等待条件变量,不占用CPU
|
||||
- **内存**: 每个连接 < 100KB
|
||||
- **带宽**: 鼠标事件20字节,键盘事件按实际输入
|
||||
|
||||
|
|
|
|||
35
src/client.c
35
src/client.c
|
|
@ -12,6 +12,7 @@
|
|||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
#include <poll.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "socket_protocol.h"
|
||||
|
|
@ -25,6 +26,10 @@ static int g_socket_fd = -1;
|
|||
static pthread_mutex_t g_socket_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static volatile sig_atomic_t g_terminal_modified = 0; // 标记终端是否被修改
|
||||
|
||||
// 用于窗口大小变化通知的条件变量
|
||||
static pthread_mutex_t g_winch_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t g_winch_cond = PTHREAD_COND_INITIALIZER;
|
||||
|
||||
// 恢复终端状态的清理函数
|
||||
static void cleanup_terminal(void) {
|
||||
// 总是尝试恢复终端,即使标志未设置
|
||||
|
|
@ -40,12 +45,15 @@ static void cleanup_terminal(void) {
|
|||
static void handle_sigwinch(int sig) {
|
||||
(void)sig;
|
||||
g_window_size_changed = 1;
|
||||
// 通知等待的线程(信号处理器中使用 pthread_cond_signal 是安全的)
|
||||
pthread_cond_signal(&g_winch_cond);
|
||||
}
|
||||
|
||||
// SIGINT/SIGTERM信号处理器
|
||||
static void handle_exit_signal(int sig) {
|
||||
(void)sig;
|
||||
g_should_exit = 1;
|
||||
pthread_cond_signal(&g_winch_cond); // 唤醒窗口监听线程
|
||||
cleanup_terminal(); // 立即恢复终端
|
||||
}
|
||||
|
||||
|
|
@ -211,7 +219,30 @@ static void* window_monitor_thread(void* arg) {
|
|||
(void)arg;
|
||||
|
||||
while (!g_should_exit) {
|
||||
// 等待窗口大小变化信号
|
||||
pthread_mutex_lock(&g_winch_mutex);
|
||||
|
||||
// 使用条件变量等待信号,带超时以便检查退出标志
|
||||
while (!g_window_size_changed && !g_should_exit) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += 1; // 1秒超时,用于检查退出标志
|
||||
|
||||
int ret = pthread_cond_timedwait(&g_winch_cond, &g_winch_mutex, &ts);
|
||||
if (ret == ETIMEDOUT) {
|
||||
// 超时,继续检查退出标志
|
||||
continue;
|
||||
}
|
||||
// 被信号唤醒或其他原因,检查标志
|
||||
break;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_winch_mutex);
|
||||
|
||||
if (g_should_exit) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 处理窗口大小变化
|
||||
if (g_window_size_changed) {
|
||||
g_window_size_changed = 0;
|
||||
|
||||
|
|
@ -231,8 +262,6 @@ static void* window_monitor_thread(void* arg) {
|
|||
|
||||
DEBUG_LOG("Window size updated sent to server\n");
|
||||
}
|
||||
|
||||
usleep(100000); // 睡眠100ms
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* compression.c - LZ4 压缩支持
|
||||
*
|
||||
* 使用 LZ4 库实现高性能数据压缩,适用于终端数据流。
|
||||
* LZ4 特点:
|
||||
* - 压缩速度:~500 MB/s
|
||||
* - 解压速度:~1500 MB/s
|
||||
* - 压缩比:约 2:1 到 3:1
|
||||
*/
|
||||
|
||||
#include "compression.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// 检查是否有 LZ4 库
|
||||
#ifdef HAVE_LZ4
|
||||
#include <lz4.h>
|
||||
#include <lz4hc.h>
|
||||
#define LZ4_AVAILABLE 1
|
||||
#else
|
||||
// 如果没有 LZ4 库,提供一个简单的回退实现
|
||||
#define LZ4_AVAILABLE 0
|
||||
#endif
|
||||
|
||||
void compression_init(CompressionContext* ctx, CompressionType type, int level) {
|
||||
if (ctx == NULL) return;
|
||||
|
||||
memset(ctx, 0, sizeof(CompressionContext));
|
||||
ctx->type = type;
|
||||
ctx->level = level > 0 ? level : 9; // 默认级别
|
||||
ctx->enabled = (type != COMPRESS_NONE);
|
||||
|
||||
#if !LZ4_AVAILABLE
|
||||
if (ctx->enabled) {
|
||||
DEBUG_LOG("警告: LZ4 库未编译,压缩功能已禁用");
|
||||
ctx->enabled = 0;
|
||||
ctx->type = COMPRESS_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
DEBUG_LOG("压缩初始化: type=%d, level=%d, enabled=%d",
|
||||
ctx->type, ctx->level, ctx->enabled);
|
||||
}
|
||||
|
||||
size_t get_compress_bound(size_t src_size) {
|
||||
#if LZ4_AVAILABLE
|
||||
return LZ4_compressBound(src_size);
|
||||
#else
|
||||
// 无压缩时,输出大小等于输入大小
|
||||
return src_size;
|
||||
#endif
|
||||
}
|
||||
|
||||
int compress_data(CompressionContext* ctx,
|
||||
const void* src, size_t src_size,
|
||||
void* dst, size_t dst_capacity,
|
||||
uint32_t* flags) {
|
||||
if (ctx == NULL || src == NULL || dst == NULL || flags == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*flags = 0;
|
||||
|
||||
// 如果压缩未启用或数据太小,直接复制
|
||||
if (!ctx->enabled || src_size < COMPRESSION_THRESHOLD || src_size > MAX_COMPRESS_INPUT_SIZE) {
|
||||
if (dst_capacity < src_size) {
|
||||
DEBUG_LOG("compress_data: 缓冲区不足 %zu < %zu", dst_capacity, src_size);
|
||||
return -1;
|
||||
}
|
||||
memcpy(dst, src, src_size);
|
||||
ctx->skip_count++;
|
||||
return (int)src_size;
|
||||
}
|
||||
|
||||
#if LZ4_AVAILABLE
|
||||
int compressed_size = 0;
|
||||
|
||||
switch (ctx->type) {
|
||||
case COMPRESS_LZ4:
|
||||
compressed_size = LZ4_compress_default(
|
||||
(const char*)src, (char*)dst,
|
||||
(int)src_size, (int)dst_capacity);
|
||||
break;
|
||||
|
||||
case COMPRESS_LZ4_HC:
|
||||
compressed_size = LZ4_compress_HC(
|
||||
(const char*)src, (char*)dst,
|
||||
(int)src_size, (int)dst_capacity, ctx->level);
|
||||
break;
|
||||
|
||||
default:
|
||||
// 不应该到达这里
|
||||
memcpy(dst, src, src_size);
|
||||
return (int)src_size;
|
||||
}
|
||||
|
||||
if (compressed_size <= 0) {
|
||||
// 压缩失败,复制原始数据
|
||||
DEBUG_LOG("压缩失败,使用原始数据");
|
||||
if (dst_capacity < src_size) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(dst, src, src_size);
|
||||
ctx->skip_count++;
|
||||
return (int)src_size;
|
||||
}
|
||||
|
||||
// 如果压缩后更大,使用原始数据
|
||||
if ((size_t)compressed_size >= src_size) {
|
||||
DEBUG_LOG("压缩后更大 (%d >= %zu),使用原始数据", compressed_size, src_size);
|
||||
memcpy(dst, src, src_size);
|
||||
ctx->skip_count++;
|
||||
return (int)src_size;
|
||||
}
|
||||
|
||||
// 压缩成功
|
||||
*flags = MSG_FLAG_COMPRESSED | MSG_FLAG_COMPRESS_LZ4;
|
||||
if (ctx->type == COMPRESS_LZ4_HC) {
|
||||
*flags |= MSG_FLAG_COMPRESS_HC;
|
||||
}
|
||||
|
||||
ctx->bytes_in += src_size;
|
||||
ctx->bytes_out += compressed_size;
|
||||
ctx->compress_count++;
|
||||
|
||||
DEBUG_LOG("压缩成功: %zu -> %d (%.1f%%)",
|
||||
src_size, compressed_size,
|
||||
(float)compressed_size / src_size * 100);
|
||||
|
||||
return compressed_size;
|
||||
#else
|
||||
// 无 LZ4 库,直接复制
|
||||
if (dst_capacity < src_size) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(dst, src, src_size);
|
||||
ctx->skip_count++;
|
||||
return (int)src_size;
|
||||
#endif
|
||||
}
|
||||
|
||||
int decompress_data(uint32_t flags,
|
||||
const void* src, size_t src_size,
|
||||
void* dst, size_t dst_capacity) {
|
||||
if (src == NULL || dst == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 如果数据未压缩,直接复制
|
||||
if (!(flags & MSG_FLAG_COMPRESSED)) {
|
||||
if (dst_capacity < src_size) {
|
||||
DEBUG_LOG("decompress_data: 缓冲区不足 %zu < %zu", dst_capacity, src_size);
|
||||
return -1;
|
||||
}
|
||||
memcpy(dst, src, src_size);
|
||||
return (int)src_size;
|
||||
}
|
||||
|
||||
#if LZ4_AVAILABLE
|
||||
// 使用 LZ4 解压
|
||||
if (flags & MSG_FLAG_COMPRESS_LZ4) {
|
||||
int decompressed_size = LZ4_decompress_safe(
|
||||
(const char*)src, (char*)dst,
|
||||
(int)src_size, (int)dst_capacity);
|
||||
|
||||
if (decompressed_size < 0) {
|
||||
DEBUG_LOG("LZ4 解压失败");
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG_LOG("解压成功: %zu -> %d", src_size, decompressed_size);
|
||||
return decompressed_size;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 不支持的压缩格式或无 LZ4 库
|
||||
DEBUG_LOG("不支持的压缩格式: flags=0x%x", flags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void compression_get_stats(const CompressionContext* ctx,
|
||||
uint64_t* bytes_in, uint64_t* bytes_out,
|
||||
uint64_t* compress_count, uint64_t* skip_count) {
|
||||
if (ctx == NULL) return;
|
||||
|
||||
if (bytes_in) *bytes_in = ctx->bytes_in;
|
||||
if (bytes_out) *bytes_out = ctx->bytes_out;
|
||||
if (compress_count) *compress_count = ctx->compress_count;
|
||||
if (skip_count) *skip_count = ctx->skip_count;
|
||||
}
|
||||
|
||||
void compression_reset_stats(CompressionContext* ctx) {
|
||||
if (ctx == NULL) return;
|
||||
|
||||
ctx->bytes_in = 0;
|
||||
ctx->bytes_out = 0;
|
||||
ctx->compress_count = 0;
|
||||
ctx->skip_count = 0;
|
||||
}
|
||||
|
||||
int compression_get_ratio(const CompressionContext* ctx) {
|
||||
if (ctx == NULL || ctx->bytes_in == 0) {
|
||||
return 100; // 无压缩
|
||||
}
|
||||
|
||||
return (int)(ctx->bytes_out * 100 / ctx->bytes_in);
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#ifndef COMPRESSION_H
|
||||
#define COMPRESSION_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// 压缩算法类型
|
||||
typedef enum {
|
||||
COMPRESS_NONE = 0, // 无压缩
|
||||
COMPRESS_LZ4 = 1, // LZ4 快速压缩
|
||||
COMPRESS_LZ4_HC = 2, // LZ4 高压缩比模式
|
||||
} CompressionType;
|
||||
|
||||
// 压缩标志位(用于消息头的 reserved 字段)
|
||||
#define MSG_FLAG_COMPRESSED 0x01 // 载荷已压缩
|
||||
#define MSG_FLAG_COMPRESS_LZ4 0x02 // 使用 LZ4 压缩
|
||||
#define MSG_FLAG_COMPRESS_HC 0x04 // 使用高压缩比模式
|
||||
|
||||
// 压缩阈值:只有超过此大小的载荷才会被压缩
|
||||
#define COMPRESSION_THRESHOLD 64
|
||||
|
||||
// 最大压缩输入大小 (LZ4 限制)
|
||||
#define MAX_COMPRESS_INPUT_SIZE (1024 * 1024 * 64) // 64MB
|
||||
|
||||
// 压缩上下文
|
||||
typedef struct {
|
||||
CompressionType type; // 压缩类型
|
||||
int level; // 压缩级别 (仅用于 LZ4_HC)
|
||||
int enabled; // 是否启用压缩
|
||||
uint64_t bytes_in; // 压缩前总字节数
|
||||
uint64_t bytes_out; // 压缩后总字节数
|
||||
uint64_t compress_count; // 压缩次数
|
||||
uint64_t skip_count; // 跳过压缩次数(小于阈值)
|
||||
} CompressionContext;
|
||||
|
||||
// 初始化压缩上下文
|
||||
void compression_init(CompressionContext* ctx, CompressionType type, int level);
|
||||
|
||||
// 压缩数据
|
||||
// 返回: 压缩后的大小,失败返回 -1
|
||||
// 如果 dst_capacity 不足,返回 -1
|
||||
// 如果压缩后数据更大,返回原始大小并复制原始数据
|
||||
int compress_data(CompressionContext* ctx,
|
||||
const void* src, size_t src_size,
|
||||
void* dst, size_t dst_capacity,
|
||||
uint32_t* flags);
|
||||
|
||||
// 解压数据
|
||||
// 返回: 解压后的大小,失败返回 -1
|
||||
int decompress_data(uint32_t flags,
|
||||
const void* src, size_t src_size,
|
||||
void* dst, size_t dst_capacity);
|
||||
|
||||
// 获取压缩所需的最大输出缓冲区大小
|
||||
size_t get_compress_bound(size_t src_size);
|
||||
|
||||
// 获取压缩统计信息
|
||||
void compression_get_stats(const CompressionContext* ctx,
|
||||
uint64_t* bytes_in, uint64_t* bytes_out,
|
||||
uint64_t* compress_count, uint64_t* skip_count);
|
||||
|
||||
// 重置统计信息
|
||||
void compression_reset_stats(CompressionContext* ctx);
|
||||
|
||||
// 计算压缩比 (返回百分比,100 = 无压缩)
|
||||
int compression_get_ratio(const CompressionContext* ctx);
|
||||
|
||||
#endif // COMPRESSION_H
|
||||
|
|
@ -12,6 +12,18 @@
|
|||
static struct termios g_original_termios;
|
||||
static int g_termios_saved = 0;
|
||||
|
||||
#ifdef HAVE_LZ4
|
||||
// 初始化协议上下文
|
||||
void protocol_init(ProtocolContext* ctx, CompressionType compress_type, int compress_level) {
|
||||
if (ctx == NULL) return;
|
||||
|
||||
compression_init(&ctx->compress_ctx, compress_type, compress_level);
|
||||
ctx->compression_enabled = (compress_type != COMPRESS_NONE);
|
||||
|
||||
DEBUG_LOG("协议上下文初始化: compression=%d", ctx->compression_enabled);
|
||||
}
|
||||
#endif
|
||||
|
||||
// 向 socket 完整写入指定字节数(处理部分写入)
|
||||
static ssize_t write_full(int sock, const void* buf, size_t count) {
|
||||
size_t total_written = 0;
|
||||
|
|
@ -76,6 +88,73 @@ int write_message(int sock, MessageType type, const void* payload,
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LZ4
|
||||
// 带压缩支持的消息写入
|
||||
int write_message_compressed(int sock, ProtocolContext* ctx, MessageType type,
|
||||
const void* payload, uint32_t payload_len) {
|
||||
DEBUG_LOG("写入压缩消息: type=%d, payload_len=%u", type, payload_len);
|
||||
|
||||
// 如果没有上下文或压缩未启用,使用普通写入
|
||||
if (ctx == NULL || !ctx->compression_enabled || payload_len == 0) {
|
||||
return write_message(sock, type, payload, payload_len);
|
||||
}
|
||||
|
||||
// 分配压缩缓冲区
|
||||
size_t compress_bound = get_compress_bound(payload_len);
|
||||
void* compressed_buf = malloc(compress_bound);
|
||||
if (compressed_buf == NULL) {
|
||||
DEBUG_LOG("分配压缩缓冲区失败");
|
||||
return write_message(sock, type, payload, payload_len);
|
||||
}
|
||||
|
||||
// 压缩数据
|
||||
uint32_t flags = 0;
|
||||
int compressed_size = compress_data(&ctx->compress_ctx,
|
||||
payload, payload_len,
|
||||
compressed_buf, compress_bound,
|
||||
&flags);
|
||||
|
||||
if (compressed_size < 0) {
|
||||
DEBUG_LOG("压缩失败,使用原始数据");
|
||||
free(compressed_buf);
|
||||
return write_message(sock, type, payload, payload_len);
|
||||
}
|
||||
|
||||
// 构建消息头
|
||||
MessageHeader header;
|
||||
header.magic = MESSAGE_MAGIC;
|
||||
header.type = type;
|
||||
header.payload_len = (uint32_t)compressed_size;
|
||||
header.reserved = MAKE_RESERVED(flags, payload_len);
|
||||
|
||||
DEBUG_LOG("压缩消息头: type=%d, compressed_len=%d, original_len=%u, flags=0x%x",
|
||||
type, compressed_size, payload_len, flags);
|
||||
DEBUG_HEX("压缩消息头", &header, sizeof(header));
|
||||
|
||||
// 发送消息头
|
||||
ssize_t written = write_full(sock, &header, sizeof(header));
|
||||
if (written != sizeof(header)) {
|
||||
DEBUG_LOG("写入消息头失败");
|
||||
free(compressed_buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 发送压缩后的载荷
|
||||
if (compressed_size > 0) {
|
||||
written = write_full(sock, compressed_buf, compressed_size);
|
||||
if (written != compressed_size) {
|
||||
DEBUG_LOG("写入压缩载荷失败");
|
||||
free(compressed_buf);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
free(compressed_buf);
|
||||
DEBUG_LOG("压缩消息写入完成: %u -> %d 字节", payload_len, compressed_size);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 从 socket 完整读取指定字节数(处理部分读取)
|
||||
static ssize_t read_full(int sock, void* buf, size_t count) {
|
||||
size_t total_read = 0;
|
||||
|
|
@ -166,6 +245,115 @@ int read_message(int sock, MessageType* type, void** payload,
|
|||
return 1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LZ4
|
||||
// 带解压支持的消息读取
|
||||
int read_message_decompressed(int sock, MessageType* type, void** payload,
|
||||
uint32_t* payload_len, uint32_t* original_len) {
|
||||
MessageHeader header;
|
||||
|
||||
DEBUG_LOG("开始读取消息(带解压)...");
|
||||
|
||||
// 读取消息头
|
||||
ssize_t bytes_read = read_full(sock, &header, sizeof(header));
|
||||
if (bytes_read != sizeof(header)) {
|
||||
if (bytes_read == 0) {
|
||||
DEBUG_LOG("连接正常关闭");
|
||||
return 0;
|
||||
}
|
||||
DEBUG_LOG("读取消息头失败: got %zd bytes, expected %zu", bytes_read, sizeof(header));
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG_HEX("收到消息头", &header, sizeof(header));
|
||||
|
||||
// 验证魔数
|
||||
if (header.magic != MESSAGE_MAGIC) {
|
||||
DEBUG_LOG("无效魔数: 0x%x", header.magic);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*type = (MessageType)header.type;
|
||||
uint32_t flags = GET_COMPRESS_FLAGS(header.reserved);
|
||||
uint32_t size_hint = GET_ORIGINAL_SIZE_HINT(header.reserved);
|
||||
|
||||
DEBUG_LOG("消息类型=%d, 压缩载荷=%u, flags=0x%x, size_hint=%u",
|
||||
*type, header.payload_len, flags, size_hint);
|
||||
|
||||
// 读取压缩载荷
|
||||
if (header.payload_len == 0) {
|
||||
*payload = NULL;
|
||||
*payload_len = 0;
|
||||
if (original_len) *original_len = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void* compressed_buf = malloc(header.payload_len);
|
||||
if (compressed_buf == NULL) {
|
||||
DEBUG_LOG("分配压缩缓冲区失败");
|
||||
return -1;
|
||||
}
|
||||
|
||||
bytes_read = read_full(sock, compressed_buf, header.payload_len);
|
||||
if (bytes_read != (ssize_t)header.payload_len) {
|
||||
DEBUG_LOG("读取压缩载荷失败: 期望%u字节, 实际%zd字节",
|
||||
header.payload_len, bytes_read);
|
||||
free(compressed_buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 检查是否需要解压
|
||||
if (!(flags & MSG_FLAG_COMPRESSED)) {
|
||||
// 数据未压缩,直接返回
|
||||
*payload = compressed_buf;
|
||||
*payload_len = header.payload_len;
|
||||
if (original_len) *original_len = header.payload_len;
|
||||
|
||||
// 添加字符串终止符(重新分配以确保空间)
|
||||
void* new_buf = realloc(compressed_buf, header.payload_len + 1);
|
||||
if (new_buf) {
|
||||
*payload = new_buf;
|
||||
((char*)(*payload))[header.payload_len] = '\0';
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 需要解压
|
||||
// 估算解压后大小(使用 size_hint 或默认 4 倍)
|
||||
size_t decompress_capacity = size_hint > 0 ? size_hint + 256 : header.payload_len * 4;
|
||||
if (decompress_capacity < 256) decompress_capacity = 256;
|
||||
|
||||
void* decompressed_buf = malloc(decompress_capacity + 1);
|
||||
if (decompressed_buf == NULL) {
|
||||
DEBUG_LOG("分配解压缓冲区失败");
|
||||
free(compressed_buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int decompressed_size = decompress_data(flags,
|
||||
compressed_buf, header.payload_len,
|
||||
decompressed_buf, decompress_capacity);
|
||||
|
||||
free(compressed_buf);
|
||||
|
||||
if (decompressed_size < 0) {
|
||||
DEBUG_LOG("解压失败");
|
||||
free(decompressed_buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 添加字符串终止符
|
||||
((char*)decompressed_buf)[decompressed_size] = '\0';
|
||||
|
||||
*payload = decompressed_buf;
|
||||
*payload_len = (uint32_t)decompressed_size;
|
||||
if (original_len) *original_len = header.payload_len;
|
||||
|
||||
DEBUG_LOG("解压成功: %u -> %d 字节", header.payload_len, decompressed_size);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 释放消息载荷
|
||||
void free_message_payload(void* payload) {
|
||||
if (payload != NULL) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@
|
|||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
|
||||
#ifdef HAVE_LZ4
|
||||
#include "compression.h"
|
||||
#endif
|
||||
|
||||
// 消息类型枚举
|
||||
typedef enum {
|
||||
MSG_TYPE_INIT = 1, // 初始化连接,发送命令信息
|
||||
|
|
@ -18,13 +22,23 @@ typedef enum {
|
|||
} MessageType;
|
||||
|
||||
// 消息头结构(固定大小)
|
||||
// reserved 字段现在用于存储压缩标志:
|
||||
// bit 0: MSG_FLAG_COMPRESSED - 载荷已压缩
|
||||
// bit 1: MSG_FLAG_COMPRESS_LZ4 - 使用 LZ4 压缩
|
||||
// bit 2: MSG_FLAG_COMPRESS_HC - 使用高压缩比模式
|
||||
// 高 16 位: 压缩前的原始大小(用于解压缓冲区分配)
|
||||
typedef struct {
|
||||
uint32_t magic; // 魔数,用于验证 0x42534D54 ("BSMT")
|
||||
uint32_t type; // 消息类型
|
||||
uint32_t payload_len; // 载荷长度
|
||||
uint32_t reserved; // 保留字段,用于对齐
|
||||
uint32_t payload_len; // 载荷长度(压缩后)
|
||||
uint32_t reserved; // 低 16 位: 压缩标志; 高 16 位: 原始大小/256
|
||||
} __attribute__((packed)) MessageHeader;
|
||||
|
||||
// 从 reserved 字段提取压缩标志和原始大小
|
||||
#define GET_COMPRESS_FLAGS(reserved) ((reserved) & 0xFFFF)
|
||||
#define GET_ORIGINAL_SIZE_HINT(reserved) (((reserved) >> 16) * 256)
|
||||
#define MAKE_RESERVED(flags, orig_size) (((flags) & 0xFFFF) | (((orig_size) / 256) << 16))
|
||||
|
||||
// 终端信息结构
|
||||
typedef struct {
|
||||
uint32_t is_tty; // 是否为TTY
|
||||
|
|
@ -67,9 +81,27 @@ typedef struct {
|
|||
// 魔数定义
|
||||
#define MESSAGE_MAGIC 0x42534D54 // "BSMT"
|
||||
|
||||
#ifdef HAVE_LZ4
|
||||
// 协议上下文(包含压缩状态)
|
||||
typedef struct {
|
||||
CompressionContext compress_ctx; // 压缩上下文
|
||||
int compression_enabled; // 是否启用压缩
|
||||
} ProtocolContext;
|
||||
|
||||
// 初始化协议上下文
|
||||
void protocol_init(ProtocolContext* ctx, CompressionType compress_type, int compress_level);
|
||||
|
||||
// 带压缩支持的消息读写
|
||||
int write_message_compressed(int sock, ProtocolContext* ctx, MessageType type,
|
||||
const void* payload, uint32_t payload_len);
|
||||
int read_message_decompressed(int sock, MessageType* type, void** payload,
|
||||
uint32_t* payload_len, uint32_t* original_len);
|
||||
#endif
|
||||
|
||||
// 函数声明
|
||||
int write_message(int sock, MessageType type, const void* payload, uint32_t payload_len);
|
||||
int read_message(int sock, MessageType* type, void** payload, uint32_t* payload_len);
|
||||
|
||||
void free_message_payload(void* payload);
|
||||
|
||||
// 终端输入捕获相关函数
|
||||
|
|
|
|||
Loading…
Reference in New Issue