4.7 KiB
4.7 KiB
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
测试
- 启动 Go 服务端:
cd go_service
sudo ./build/bash_go_service-amd64 daemon
- 在另一个终端运行测试:
cd execve_hook
./build/test_client
- 在测试运行时调整终端窗口大小,观察服务端日志输出
技术特性
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服务端实现
性能考虑
- 轮询间隔:窗口监控线程使用 100ms 轮询间隔,平衡响应性和CPU使用
- 消息大小:终端信息消息约 60-80 字节,网络开销小
- 并发设计:独立的监听线程不阻塞主流程
扩展建议
- 动态轮询间隔:可以根据窗口变化频率动态调整轮询间隔
- 去重机制:连续相同的窗口大小可以不发送更新
- 压缩传输:对于大量终端信息,可以考虑压缩
- 心跳机制:添加心跳消息检测连接状态
调试
开启 DEBUG 模式编译:
make DEBUG=1
这将输出详细的调试信息,包括:
- 消息发送/接收详情
- 窗口大小变化事件
- 线程生命周期信息