153 lines
5.1 KiB
Markdown
153 lines
5.1 KiB
Markdown
# 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. ⏳ 添加加密支持(敏感数据)
|