网络主要内容测试完成

This commit is contained in:
2026-08-26 13:54:52 +08:00
parent 0a707034cc
commit 88160a5134
18 changed files with 2174 additions and 928 deletions

View File

@@ -35,7 +35,7 @@ Phase 8 压力测试2025-07
多连接模式下多个 Socket 数据互相覆盖,收到错误数据或客户端挂死。
### 根因
CH395F 默认只为 Socket 0~3 各分配 4 个缓冲区块(共 32 块用完Socket 4~7 为零块。`net_listen()` 未对 Socket 4~7 显式分配独立缓冲区块,后续代码误用相同块号(如 28~31导致数据重叠。
CH395F 默认只为 Socket 0~3 各分配独立收发缓冲(共 48 块 × 512B 用满,见手册 §5.45Socket 4~7 为零块。`net_listen()` 未对 Socket 4~7 显式分配独立缓冲区块,后续代码误用相同块号(如 28~31导致数据重叠。
### 解决方案
每个数据 Socket 独占 4 个缓冲区块2 发 + 2 收),按 `ds * 4` 基址分配,确保不重叠:
@@ -198,7 +198,7 @@ CH395F 的 RECV 中断是电平触发的——只要接收缓冲区有数据就
- **KeepAlive 默认关闭**:需在 `SINT_STAT_CONNECT` 后调用 `ch395f_set_keepalive_enable(sock, 1)` 启用
### Socket 4~7 缓冲区
- CH395F 默认只给 Socket 0~3 分配 4 个缓冲区块(共 32 块用完Socket 4~7 为零块
- CH395F 默认只给 Socket 0~3 分配独立收发缓冲(共 48 块 × 512B 用满Socket 4~7 为零块
- 多连接模式下使用 Socket 4~7 时,**必须在 `open_socket` 之前**显式分配:
- `ch395f_set_send_buf(sock, start_block, count)`
- `ch395f_set_recv_buf(sock, start_block, count)`
@@ -371,6 +371,7 @@ static const char *to_fatfs_path(const char *path) {
| 12 | `malloc` 嵌入式环境失败 | Trap 13 子类 | `lftpd.c: static buffer` |
| 13 | `net_listen_locked` 缓冲区覆盖s0/s1 争用 block 0-3 | Trap 02 翻版 | `net_socket.c: 单连接用默认 buf` |
| 14 | CH395F TIMEOUT 立即触发(数据连接无数据) | Trap 09 子类 | `net_socket.c: CLOSED 状态处理` |
| 15 | 发送单次写 >1KB 写穿 1KB 硬件发送 FIFO | Trap 20 | `net_socket.c: NET_SEND_CHUNK_MAX` |
| 15 | `NET_RECV_TIMEOUT_MS=5s` 控制通道超时断开 | 配置 | `net_config.h: 30000` |
| 16 | `receive_file` 0 bytes 当作成功 | 逻辑 | `lftpd.c: total==0 则失败` |
| 17 | MobaXterm 连数据端口不发数据(客户端特殊行为) | 外部 | Python 抓包确认,超时恢复 |
@@ -431,3 +432,107 @@ DHCP 状态位仅代表协商流程完成IP/MASK/GW 寄存器的提交与状
### 参考
`test/ch395f_test_task.c: phase4_run()``docs/CH395F_Test_Guide.md` 阶段 4 风险表 #4
---
## Trap 16NET 层 TCP Client 目的 IP 字节序写反 → CONNECT TIMEOUT
### 现象
NET 层 TCP Client阶段 7`net_connect()` 返回 0`net_poll()``sock0 CONNECT TIMEOUT`,连接永远建立不起来;而硬件层 TCP Client阶段 2同一 `net_init`/FUN_PARA=0x08同一目标 192.168.1.2:8081完全正常。PC 同网段、防火墙关闭、服务端正常监听。
### 根因
`net_connect_locked()``sockaddr_in.sin_addr.s_addr`(网络字节序,`net_inet_addr` 返回)按 **低字节在前** 拆成 `remote_ip_arr[4]`,得到 `[2,1,168,192]`,再交给 `ch395f_set_des_ip()`。但 **CH395F 的目的 IP/端口寄存器是大端(网络字节序)**(阶段 2 用 `test_parse_ip("192.168.1.2")` 直接得到 `[192,168,1,2]` 即成功,可证)。于是芯片把 SYN 发往 `2.1.168.192`——一个跨网段、不可达的地址SYN 被网关丢弃 → 无 SYN-ACK → `CONNECT TIMEOUT`
**误导性排查**`CMD_GET_REMOT_IPP_SN` 回读的是**小端**字节序(`ch395f.c` 注释 "IP和端口均为低字节在前")。把写反的 `[2,1,168,192]` 读回再按小端解释,恰好又得到 `192.168.1.2`,使回读日志看起来"目的地址正确",掩盖了真实写反。
### 解决
1. `net_connect_locked()` 提取 `remote_ip_arr` 改为 **大端**`arr[0]=(ip>>24)&0xFF … arr[3]=ip&0xFF`ip 为网络字节序 s_addr
2. 同样修正 UDP 发送路径 `net_send_locked()``set_des_ip` 的目的 IP 数组(同样低字节在前 bug
3. 诊断回读 `GET_REMOT_IPP` 时按其小端语义还原:`dip=(rip[3]<<24)|…|rip[0]`,再按 `>>24…` 打印,方可显示芯片实际目的 IP。
### 注意事项
- `net_inet_addr` 返回网络字节序;`sin_addr.s_addr` 一律按网络字节序理解。
- 任何写 CH395F IP/端口寄存器set_des_ip / set_ip_addr / set_gwip_addr / set_mask_addr都传**大端数组**,与 `test_parse_ip` 产物一致。
- 回读类命令GET_REMOT_IPP / GET_IP_INF 等)若文档标注"低字节在前",打印时务必先按小端重组再格式化,否则会误判。
### 对应文件
`Drivers/BSP/NET/net_socket.c``net_connect_locked` 提取 `remote_ip_arr` 大端;`net_send_locked` UDP 目的 IP 同样修正;`[CONN]` 回读按小端重组);`Drivers/BSP/NET/net_types.h``remote_ip_arr` 注释改为大端);`test/ch395f_test_task.c``test_parse_ip` 可作为大端构建范本)。
### 发现时间
阶段 7 NET 层 TCP Client 上板实测2026-08-25。
---
## Trap 17单连接模式 client 重连被上一条连接的残留 DISCONNECT 秒断
### 现象
阶段 7 TC701首次 TCP Client 连接 + 回显正常TC702close 后同 Socket 0 重连 ×3每轮不同本地端口每次都 `[CONN] openOK` 成功、但紧接着 `sock0 DISCONNECTED, releasing`连接被秒断PC 端看到"已建立连接但对端立即关闭、未收到数据"。阶段 6 的 `auto_relisten`/断线处理逻辑同源,但 `auto_relisten` 标志本身不是元凶client 该标志为 0
### 根因
单连接模式只有 Socket 0新旧连接复用它。`net_close_locked()``close_socket()` 后芯片要等对端 FIN/ACK 才把 Socket 置 `SOCK_CLOSED` 并上报 `DISCONNECT` 中断耗时数毫秒。TC702 几乎在 TC701 关闭同时复用 Socket 0 去 `tcp_connect`:旧连接的 `DISCONNECT` 中断在 TC702 已 `ESTABLISHED` 之后才到达,`net_poll()``handle_disconnect_event()` 把状态置 `CLOSED` → 新连接被误判为"刚建立就断开"。`GET_REMOT_IPP` 回读仅确认目的地址正确,掩盖不了此事件串扰。
### 解决
1. `net_close_locked()``close_socket()` 后轮询 `ch395f_get_socket_status()` 直到 `CH395F_SOCKET_CLOSED`,再读 `ch395f_get_sock_int_status()` / `ch395f_get_glob_int_status_all()` 清掉中断状态(上限 500msLAN 通常几毫秒),彻底排空上一条连接残留的 `DISCONNECT`
2. `net_connect_locked()``open_socket()` 成功后再次读空本 Socket 中断状态,作为双保险。
### 注意事项
- 任何"关闭→复用同一 Socket 0 重新建连"的场景都必须先排空旧连接的中断,否则旧 `DISCONNECT` 会串扰新连接。
- 若关闭后阻塞等待影响实时性(多连接模式另有 Socket应改为非阻塞的"代次/epoch"机制;单连接模式直接排空即可。
- `net_close_locked` 内阻塞 `osDelay` 仅在 netTask 上下文,单连接模式无其它并发 Socket安全。
### 对应文件
`Drivers/BSP/NET/net_socket.c``net_close_locked` 排空残留 DISCONNECT`net_connect_locked` open 后清中断)。
### 发现时间
阶段 7 NET 层 TCP Client 上板实测2026-08-25。
## Trap 18多 Socket 模式切换 + 8 个 Socket 缓冲分配
### 现象
阶段 7 仅测了 Socket 0单连接模式只有 Socket 0 可用)。要验证 Socket 1~7 也能作为 client必须把 `FUN_PARA` 从单 Socket 模式切到多 Socket 模式,否则 `open_socket(1~7)` 被芯片拒绝/无响应1~7 完全不可用。
### 根因
`CMD_SET_FUN_PARA`**bit0** 决定 Socket 数量模式:`0`=单 Socket 模式(仅 Socket 08KB 缓冲全归它);`1`=多 Socket 模式Socket 0~7 全部可用,缓冲需自行分配)。之前 `net_init``0x08`bit0=0即单 Socket 模式,故 1~7 不存在。注意 bit1TCP Server 单/多连接)与 bit0 是两回事:本方案只翻 bit0多 Socketbit1 保持 0TCP Server 单连接),即"8 个 Socket 可用,但 Socket 0 作服务端时只接 1 客户端"。
### 解决
1. `net_init``ch395f_set_fun_para(0x09)`bit0=1, bit1=0, bit3=1
2. `ch395f_init()` 之后,循环 0~7 用 `ch395f_set_send_buf(sock, i*6+4, 2)` / `ch395f_set_recv_buf(sock, i*6, 4)` 给每个 Socket 分配独立收发缓冲。按手册 §5.4548 块 × 512B = 24KB+ §9.2.8 最优分配:每 Socket 6 块 = 接收 4 块(2KB) + 发送 2 块(1KB)`ch395f_set_tcp_mss(1024)`,恰好用尽 0~47 块、互不重叠。
3. MSS 选取依据 §9.2.8:接收缓冲(2KB) ≥ 2×MSS(1024) 满足建议、≥ MSS 满足必须;发送缓冲(1KB) ≤ 8KB 满足必须。MSS 取 1024 为 8-Socket 共享 24KB 时的最大值。
4. `net_init` 缓冲分配顺序遵循 Trap 01先配数据 Socket再配监听 Socket 0本 Net 层 Socket 按需 open故统一在 init 一次性配好。UDP 路径 `net_socket()` 中对 Socket 4~7 的兜底重设也同步为同一布局。
5. PC 端 `tcp_server` 改为每连接一线程的并发回显(`--max-conn` 为并发计数),否则 Phase 7 全量连接会触发累计计数上限而拒掉 4~7Trap 19
### 注意事项
- 4~7 的缓冲必须显式分配,否则其 CONNECT/RECV 中断不触发Trap 01/02
- 上述布局**以手册 §5.45 的 48×512B 缓冲几何为准**,并已由 Phase 7 TC70440/40 PASS实测确认块 0~47 全部有效(此前文档中"32×1KB"记录有误。Socket 4~7 分配与 8 路并发回显均验证通过。
- 多 Socket 模式下 TCP Server 单连接语义不变Socket 0 监听且连接停留在本 SocketSockets 1~7 由应用当作独立 client 使用。
- `net_poll``GET_GLOB_INT_STATUS_ALL`2 字节,支持 0~7并遍历 0~7Socket n 中断位即 `bit(n+4)`Socket 0~3→bit4~7Socket 4~7→bit8~11原代码正确无需改动。
### 对应文件
`Drivers/BSP/NET/net_socket.c``net_init` FUN_PARA + 缓冲分配 + MSS`test/net_test_task.c`(新增 TC704 八 Socket 并发回显);`test/ch395f_socket_test.py``tcp_server` 并发 + `--max-conn`)。
### 发现时间
阶段 7 扩展 8 Socket 测试2026-08-25。
## Trap 20发送缓冲区单次写入超过硬件 FIFO 上限(写穿)
### 现象
大文件/批量发送(如阶段 8 的 100KB或 FTP 上传)时,数据在中间某处错乱、连接被对端 RST、或在 `WRITE_SEND_BUF` 后芯片不再产生 `SEND_OK`,表现为发送卡死或后续字节丢失。该问题只在单次发送长度 > 1KB 时稳定复现。
### 根因
`net_send_locked`TCP`net_sendto`UDP在分块时以 **`NET_DMA_MAX_PAYLOAD`(2KB) / `NET_SEND_BUF_SIZE`(4KB)** 作为单次写长度上限,但 CH395F 每个 Socket 的**硬件发送 FIFO 只有 `CH395F_SEND_BLOCKS × CH395F_RAM_BLOCK_SIZE` = 2×512 = 1KB**(由 MSS=1024 推导,见 `ch395f.h`)。当某次调用 `net_send/sendto` 传入 >1KB 数据,驱动会在 `send_ready` 置位后一次性 `WRITE_SEND_BUF(2KB/4KB)`,直接**写穿 1KB 发送 FIFO**(超出部分覆盖/丢失或破坏 FIFO 指针),导致后续数据错位。原代码误把"SPI DMA 缓冲上限(2KB)"或"软件 API 缓冲(4KB)"当成硬件发送缓冲上限。
### 解决
1. `ch395f.h` 新增硬件缓冲字节宏:`CH395F_SEND_BUF_SIZE_BYTES = CH395F_SEND_BLOCKS × CH395F_RAM_BLOCK_SIZE`=1KB并补充 `CH395F_RECV_BUF_SIZE_BYTES`
2. `net_socket.c` 新增 `NET_SEND_CHUNK_MAX = min(NET_DMA_MAX_PAYLOAD, CH395F_SEND_BUF_SIZE_BYTES)`,即**以硬件发送 FIFO 为硬上限**(当前 1KB < 2KB DMA 载荷,故生效 1KB
3. `net_send_locked``net_sendto` 的单次写长度上限均由原来的 `NET_DMA_MAX_PAYLOAD`/`NET_SEND_BUF_SIZE` 改为 `NET_SEND_CHUNK_MAX``send_ready` 门控保证只有 FIFO 空闲时才写入,故写入 ≤1KB 安全(写满后等 `SEND_OK` 再写下一块)。
4. 接收方向不受影响:`net_recv_locked` 只读 `ch395f_get_recv_len`(≤ 接收 FIFO 2KB并受调用方 `len` 截断,不会写穿。
### 注意事项
- 该上限由 `CH395F_TCP_MSS` 推导,若将来调大 MSS 使发送缓冲 >2KB`NET_SEND_CHUNK_MAX` 自动跟随(仍取硬件缓冲与 DMA 载荷的较小者),无需改此处逻辑。
- 调用方(阶段 8、FTP `lftpd`)仍可按任意长度调用 `net_send`,实际分块由驱动保证 ≤ 硬件 FIFO。
### 对应文件
`Drivers/BSP/CH395F/ch395f.h``CH395F_SEND_BUF_SIZE_BYTES` 等);`Drivers/BSP/NET/net_socket.c``NET_SEND_CHUNK_MAX` + `net_send_locked`/`net_sendto` 分块上限)。
### 发现时间
阶段 8 大文件传输实现2026-08-26。