execve_hook/SOCKET_PROTOCOL.md

321 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Socket 实时终端信息同步
## 概述
本项目实现了一个现代化的 Unix Socket 通信机制,支持 C 客户端和 Go 服务端之间的双向消息传递。特别地,实现了实时监听终端窗口大小变化并同步给服务端的功能。
## 架构设计
### 协议设计
采用基于消息头的结构化协议:
```c
// 消息头16字节固定大小
typedef struct {
uint32_t magic; // 魔数 0x42534D54 ("BSMT")
uint32_t type; // 消息类型
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
typedef enum {
MSG_TYPE_INIT = 1, // 初始化连接,发送命令信息
MSG_TYPE_WINDOW_SIZE_UPDATE = 2, // 终端窗口大小更新
MSG_TYPE_SERVER_RESPONSE = 3, // 服务器响应消息
MSG_TYPE_CLOSE = 4 // 关闭连接
} MessageType;
```
## 核心功能
### 1. 窗口大小实时监听C端
使用 SIGWINCH 信号监听终端窗口大小变化:
```c
// 信号处理器
static void handle_sigwinch(int sig) {
g_window_size_changed = 1;
}
// 注册信号
struct sigaction sa;
sa.sa_handler = handle_sigwinch;
sigaction(SIGWINCH, &sa, NULL);
```
### 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 (!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);
}
}
}
```
### 3. Go服务端实时接收
服务端启动goroutine持续监听客户端消息
```go
go func() {
for {
msgType, payload, err := readMessage(conn)
if err != nil {
return
}
if msgType == MsgTypeWindowSizeUpdate {
termInfo, _ := parseTerminalInfo(payload)
params.TerminalInfo = *termInfo
logging.Info("窗口大小已更新 - %dx%d", termInfo.Rows, termInfo.Cols)
}
}
}()
```
## 终端信息结构
### 固定部分C结构体
```c
typedef struct {
uint32_t is_tty; // 是否为TTY
uint16_t rows; // 行数
uint16_t cols; // 列数
uint16_t x_pixel; // X像素
uint16_t y_pixel; // Y像素
uint32_t has_termios; // 是否有termios属性
uint32_t input_flags; // termios输入标志
uint32_t output_flags; // termios输出标志
uint32_t control_flags; // termios控制标志
uint32_t local_flags; // termios本地标志
} TerminalInfoFixed;
```
### 可变部分
- `term_type_len` + `term_type` (TERM环境变量)
- `shell_type_len` + `shell_type` (SHELL环境变量)
## 编译和测试
### 编译
```bash
cd execve_hook
make clean
make
```
### 测试
1. 启动 Go 服务端:
```bash
cd go_service
sudo ./build/bash_go_service-amd64 daemon
```
2. 在另一个终端运行测试:
```bash
cd execve_hook
./build/test_client
```
3. 在测试运行时调整终端窗口大小,观察服务端日志输出
## 技术特性
### 1. 线程安全
- 使用 `pthread_mutex` 保护共享的 socket 文件描述符
- 使用 `sig_atomic_t` 类型处理信号标志
### 2. 优雅关闭
- 使用 channel 控制 goroutine 生命周期
- 通过 `MSG_TYPE_CLOSE` 消息通知客户端关闭
### 3. 错误处理
- 魔数验证防止协议错误
- 完整的错误检查和日志记录
### 4. 现代化设计
- 结构化消息协议
- 分离的消息读写函数
- 清晰的职责划分
## 文件结构
```
execve_hook/src/
├── socket_protocol.h # 协议定义头文件
├── socket_protocol.c # 协议实现
├── compression.h # 压缩模块头文件
├── compression.c # LZ4 压缩实现
├── client.h # 客户端头文件
├── client.c # 客户端实现含SIGWINCH处理
└── ...
go_service/internal/socket/
├── protocol.go # Go 协议实现
├── compression.go # Go 压缩支持
└── ...
```
## 性能考虑
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)
```
**编译时启用 LZ4C 端)**
```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. **动态轮询间隔**可以根据窗口变化频率动态调整超时时间
2. **去重机制**连续相同的窗口大小可以不发送更新
3. ~~**压缩传输**~~:✅ 已完成 - 支持 LZ4 压缩
4. **心跳机制**添加心跳消息检测连接状态
5. ~~**条件变量优化**~~:✅ 已完成 - 使用 `pthread_cond_t` 替代轮询实现事件驱动
## 调试
开启 DEBUG 模式编译
```bash
make DEBUG=1
```
这将输出详细的调试信息包括
- 消息发送/接收详情
- 窗口大小变化事件
- 线程生命周期信息