execve_hook/SOCKET_PROTOCOL.md

4.7 KiB
Raw Blame History

Socket 实时终端信息同步

概述

本项目实现了一个现代化的 Unix Socket 通信机制,支持 C 客户端和 Go 服务端之间的双向消息传递。特别地,实现了实时监听终端窗口大小变化并同步给服务端的功能。

架构设计

协议设计

采用基于消息头的结构化协议:

// 消息头16字节固定大小
typedef struct {
    uint32_t magic;       // 魔数 0x42534D54 ("BSMT")
    uint32_t type;        // 消息类型
    uint32_t payload_len; // 载荷长度
    uint32_t reserved;    // 保留字段
} MessageHeader;

消息类型

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 信号监听终端窗口大小变化:

// 信号处理器
static void handle_sigwinch(int sig) {
    g_window_size_changed = 1;
}

// 注册信号
struct sigaction sa;
sa.sa_handler = handle_sigwinch;
sigaction(SIGWINCH, &sa, NULL);

2. 独立监控线程

启动专门的线程监控窗口变化:

static void* window_monitor_thread(void* arg) {
    while (1) {
        if (g_window_size_changed) {
            g_window_size_changed = 0;
            // 发送窗口大小更新消息
            send_terminal_info(sock, MSG_TYPE_WINDOW_SIZE_UPDATE);
        }
        usleep(100000); // 100ms
    }
}

3. Go服务端实时接收

服务端启动goroutine持续监听客户端消息

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结构体

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环境变量)

编译和测试

编译

cd execve_hook
make clean
make

测试

  1. 启动 Go 服务端:
cd go_service
sudo ./build/bash_go_service-amd64 daemon
  1. 在另一个终端运行测试:
cd execve_hook
./build/test_client
  1. 在测试运行时调整终端窗口大小,观察服务端日志输出

技术特性

1. 线程安全

  • 使用 pthread_mutex 保护共享的 socket 文件描述符
  • 使用 sig_atomic_t 类型处理信号标志

2. 优雅关闭

  • 使用 channel 控制 goroutine 生命周期
  • 通过 MSG_TYPE_CLOSE 消息通知客户端关闭

3. 错误处理

  • 魔数验证防止协议错误
  • 完整的错误检查和日志记录

4. 现代化设计

  • 结构化消息协议
  • 分离的消息读写函数
  • 清晰的职责划分

文件结构

execve_hook/src/
├── socket_protocol.h       # 协议定义头文件
├── socket_protocol.c       # 协议实现
├── client.h               # 客户端头文件  
├── client.c               # 客户端实现含SIGWINCH处理
└── ...

go_service/internal/services/tasks/
└── socket.go              # Go服务端实现

性能考虑

  1. 轮询间隔:窗口监控线程使用 100ms 轮询间隔平衡响应性和CPU使用
  2. 消息大小:终端信息消息约 60-80 字节,网络开销小
  3. 并发设计:独立的监听线程不阻塞主流程

扩展建议

  1. 动态轮询间隔:可以根据窗口变化频率动态调整轮询间隔
  2. 去重机制:连续相同的窗口大小可以不发送更新
  3. 压缩传输:对于大量终端信息,可以考虑压缩
  4. 心跳机制:添加心跳消息检测连接状态

调试

开启 DEBUG 模式编译:

make DEBUG=1

这将输出详细的调试信息,包括:

  • 消息发送/接收详情
  • 窗口大小变化事件
  • 线程生命周期信息