5.1 KiB
5.1 KiB
DEBUG 模式测试说明
问题描述
之前遇到的 "connection reset by peer" 错误的根本原因是:
- TCP 流式传输特性: 数据可能分多次到达,不能假设一次
read()就能读到完整数据 - 部分读取问题: 原代码只调用一次
read(),导致读取不完整的消息头或载荷 - 错误表现: 客户端读取消息头时只收到 12 字节(期望 16 字节),导致解析失败并关闭连接
修复方案
C 代码修复 (socket_protocol.c)
-
添加
read_full()函数: 循环读取直到获取完整的指定字节数static ssize_t read_full(int sock, void* buf, size_t count) { // 循环读取,处理 EINTR 和部分读取 // 确保读取到 count 字节或连接关闭 } -
添加
write_full()函数: 循环写入直到发送完整的指定字节数static ssize_t write_full(int sock, const void* buf, size_t count) { // 循环写入,处理 EINTR 和部分写入 // 确保写入 count 字节 } -
修改
read_message(): 使用read_full()读取消息头和载荷 -
修改
write_message(): 使用write_full()发送消息头和载荷
Go 代码修复 (protocol.go)
-
使用
io.ReadFull(): 确保完整读取消息头和载荷// 读取消息头 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 } -
完整写入: 构建完整缓冲区后循环写入
written := 0 for written < len(buf) { n, err := conn.Write(buf[written:]) if err != nil { return err } written += n }
DEBUG 日志系统
C 代码 DEBUG 功能
-
debug.h和debug.c:DEBUG_LOG(fmt, ...): 带时间戳、文件位置的日志DEBUG_HEX(prefix, data, len): 十六进制数据转储- 只在
DEBUG=1编译时启用
-
关键日志点:
- 消息发送/接收的开始和完成
- 消息头和载荷的十六进制转储(前64字节)
- 读写进度(部分读取时)
- 终端模式切换
- 连接生命周期事件
编译和测试
# 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
验证修复
修复后应该看到:
- ✅ 所有消息完整读取,没有 "Failed to read message header" 错误
- ✅ 连接正常完成,TUI 显示完整流程
- ✅ 终端正确恢复,光标在新行开始
- ✅ DEBUG 日志显示所有读写操作成功完成
技术要点
为什么需要完整读写?
- TCP 流特性: TCP 是字节流协议,不保证消息边界
- 内核缓冲区: 数据可能在内核缓冲区中分批到达
- 网络延迟: 跨网络通信时更容易出现部分读取
- 信号中断:
EINTR会导致系统调用提前返回
read_full() vs read()
| 函数 | 行为 | 适用场景 |
|---|---|---|
read() |
返回当前可用的数据,可能少于请求的字节数 | 不确定数据长度的场景 |
read_full() |
循环读取直到获取完整的请求字节数或错误 | 已知数据长度的协议(如我们的消息协议) |
Go 的 io.ReadFull()
Go 标准库提供了 io.ReadFull(),其行为等同于我们实现的 read_full():
- 返回
io.ErrUnexpectedEOF如果只读取了部分数据就遇到 EOF - 返回
io.EOF如果一个字节都没读到就遇到 EOF - 只在成功读取完整字节数时返回
nil错误
后续改进
- ✅ 添加读写超时机制
- ✅ 添加消息大小限制(防止内存攻击)
- ✅ 实现消息队列缓冲
- ⏳ 添加压缩支持(大载荷)
- ⏳ 添加加密支持(敏感数据)