# DEBUG 模式测试说明 ## 问题描述 之前遇到的 "connection reset by peer" 错误的根本原因是: 1. **TCP 流式传输特性**: 数据可能分多次到达,不能假设一次 `read()` 就能读到完整数据 2. **部分读取问题**: 原代码只调用一次 `read()`,导致读取不完整的消息头或载荷 3. **错误表现**: 客户端读取消息头时只收到 12 字节(期望 16 字节),导致解析失败并关闭连接 ## 修复方案 ### C 代码修复 (`socket_protocol.c`) 1. **添加 `read_full()` 函数**: 循环读取直到获取完整的指定字节数 ```c static ssize_t read_full(int sock, void* buf, size_t count) { // 循环读取,处理 EINTR 和部分读取 // 确保读取到 count 字节或连接关闭 } ``` 2. **添加 `write_full()` 函数**: 循环写入直到发送完整的指定字节数 ```c static ssize_t write_full(int sock, const void* buf, size_t count) { // 循环写入,处理 EINTR 和部分写入 // 确保写入 count 字节 } ``` 3. **修改 `read_message()`**: 使用 `read_full()` 读取消息头和载荷 4. **修改 `write_message()`**: 使用 `write_full()` 发送消息头和载荷 ### Go 代码修复 (`protocol.go`) 1. **使用 `io.ReadFull()`**: 确保完整读取消息头和载荷 ```go // 读取消息头 headerBuf := make([]byte, 16) if _, err := io.ReadFull(conn, headerBuf); err != nil { return 0, nil, err } // 读取载荷 if _, err := io.ReadFull(conn, payload); err != nil { return 0, nil, err } ``` 2. **完整写入**: 构建完整缓冲区后循环写入 ```go written := 0 for written < len(buf) { n, err := conn.Write(buf[written:]) if err != nil { return err } written += n } ``` ## DEBUG 日志系统 ### C 代码 DEBUG 功能 1. **`debug.h` 和 `debug.c`**: - `DEBUG_LOG(fmt, ...)`: 带时间戳、文件位置的日志 - `DEBUG_HEX(prefix, data, len)`: 十六进制数据转储 - 只在 `DEBUG=1` 编译时启用 2. **关键日志点**: - 消息发送/接收的开始和完成 - 消息头和载荷的十六进制转储(前64字节) - 读写进度(部分读取时) - 终端模式切换 - 连接生命周期事件 ### 编译和测试 ```bash # 1. 编译 DEBUG 模式的 C 客户端 cd /home/qcqcqc/workspace/Projects/bash_smart/execve_hook make clean make DEBUG=1 test_socket_client # 2. 在一个终端启动 Go 服务端 cd /home/qcqcqc/workspace/Projects/bash_smart/go_service sudo GOPROXY=https://goproxy.cn,direct go run ./cmd/tests/test_socket_terminal/progress-animated/main.go # 3. 在另一个终端运行 DEBUG 客户端 cd /home/qcqcqc/workspace/Projects/bash_smart/execve_hook ./build/test_socket_client 2> debug.log # 4. 查看 DEBUG 日志 tail -f debug.log ``` ### DEBUG 日志示例 ``` [DEBUG 16:54:33.123] [src/client.c:320:seeking_solutions] 开始 seeking_solutions: filename=/usr/bin/ls [DEBUG 16:54:33.124] [src/client.c:355:seeking_solutions] 尝试连接到 /var/run/bash-smart.sock [DEBUG 16:54:33.125] [src/client.c:365:seeking_solutions] 连接成功 [DEBUG 16:54:33.126] [src/socket_protocol.c:35:write_message] 写入消息: type=1, payload_len=1024 [DEBUG HEX] 消息头 (16 bytes): 54 4d 53 42 01 00 00 00 00 04 00 00 00 00 00 00 [DEBUG 16:54:33.127] [src/socket_protocol.c:65:write_full] 进度 1024/1024 字节 [DEBUG 16:54:33.128] [src/socket_protocol.c:76:read_message] 开始读取消息... [DEBUG 16:54:33.129] [src/socket_protocol.c:96:read_full] 进度 16/16 字节 [DEBUG HEX] 收到消息头 (16 bytes): 54 4d 53 42 03 00 00 00 6c 03 00 00 00 00 00 00 [DEBUG 16:54:33.130] [src/socket_protocol.c:100:read_message] 消息类型=3, 载荷长度=876 ``` ## 验证修复 修复后应该看到: 1. ✅ 所有消息完整读取,没有 "Failed to read message header" 错误 2. ✅ 连接正常完成,TUI 显示完整流程 3. ✅ 终端正确恢复,光标在新行开始 4. ✅ DEBUG 日志显示所有读写操作成功完成 ## 技术要点 ### 为什么需要完整读写? 1. **TCP 流特性**: TCP 是字节流协议,不保证消息边界 2. **内核缓冲区**: 数据可能在内核缓冲区中分批到达 3. **网络延迟**: 跨网络通信时更容易出现部分读取 4. **信号中断**: `EINTR` 会导致系统调用提前返回 ### `read_full()` vs `read()` | 函数 | 行为 | 适用场景 | |------|------|---------| | `read()` | 返回当前可用的数据,可能少于请求的字节数 | 不确定数据长度的场景 | | `read_full()` | 循环读取直到获取完整的请求字节数或错误 | 已知数据长度的协议(如我们的消息协议) | ### Go 的 `io.ReadFull()` Go 标准库提供了 `io.ReadFull()`,其行为等同于我们实现的 `read_full()`: - 返回 `io.ErrUnexpectedEOF` 如果只读取了部分数据就遇到 EOF - 返回 `io.EOF` 如果一个字节都没读到就遇到 EOF - 只在成功读取完整字节数时返回 `nil` 错误 ## 后续改进 1. ✅ 添加读写超时机制 2. ✅ 添加消息大小限制(防止内存攻击) 3. ✅ 实现消息队列缓冲 4. ⏳ 添加压缩支持(大载荷) 5. ⏳ 添加加密支持(敏感数据)