Files
STM32F4-Base/Drivers/BSP/NET/net_socket.c
T
wandering1 1a17d73e35 docs(net): N-32 监听生命周期文档同步(4 tick/五层自愈/net_listener.c)
N-32 把监听生命周期抽为 net_listener.c,并把周期性重建收敛到
唯一入口 net_listener_pump;文档与代码注释滞后于实现,本次同步:

- 主循环自愈 tick 3 → 4(新增 net_listener_pump,100ms)
- 自愈分层 四层 → 五层(新增 N-32 pump 行)
- net_reconcile_tick 对账收窄为 ①②,③④ 移交 pump
- 函数更名:net_relisten_try/clear/retry_tick → net_listener_*
- Trap 23 沉降等级订正:异常 400ms / 正常 100ms(正常 FIN 亦沉降)
- net_socket.c 注释同步(四个自愈 tick、函数名、pump 条目)

权威设计见 docs/子系统设计/网络Socket监听生命周期设计说明.md §10。
2026-10-12 19:16:31 +08:00

3672 lines
178 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:Network Socket Core
* 模块功能:BSD Socket API 核心实现,包含 Socket 管理、状态机、消息队列(线程安全)、事件处理、数据收发等功能,底层调用 CH395F 驱动
* 适用平台:STM32F407ZGTx + CH395F 以太网芯片
* 作者:王建锋
* 创建日期:2026-07-18
* 修改记录:
* 2026-07-18 王建锋 创建初始版本
* 2026-07-19 王建锋 添加消息队列线程安全机制,替换 xQueueCreate 为 CubeMX osMessageQueue
*/
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
#include "cmsis_os.h"
#include "net_socket.h"
#include "net_listener.h"
#include "ch395f.h"
#include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */
#include "net_latency_test.h" /* L3 探针接口(chunk_wait 采样) */
#include "main.h"
/*
* 调试输出配置
* 深调用链模块(net_bind/net_send/net_poll 是 netTask/ftpTask/timeSyncTask
* 最坏链的主干):日志行缓冲收到 128 B(默认 192)。本模块最长行渲染后
* ≈ 100 B(含时间戳 21 + 标签)< 128 → 不截断
*/
#define DBG_LINE_BUF_MAX 128U
#define DBG_TAG "[NET]"
#include "dbg_log.h"
/*
* 私有宏定义区
*/
#define NET_PHY_CHECK_INTERVAL_MS 5000U /* PHY 状态定期检查间隔(ms) */
/*
* 全局变量定义区 - 尽量避免使用全局变量
*/
static net_sock_t s_net_socks[NET_MAX_SOCKETS]; /* Socket 控制块数组 */
static uint8_t s_net_initialized = 0; /* 初始化标志 */
static uint8_t s_local_ip[4]; /* 本机 IP 缓存(网络序,net_getsockname 用) */
static uint16_t s_dynamic_port = 49152; /* 动态端口分配起始值 */
static volatile uint32_t s_net_generation = 0; /* 网络代次号:网络重建完成时递增(服务据此重建监听) */
/*
* 消息队列(线程安全核心)
* 外部任务经 net_send/net_recv/net_close 等公开 API 投递请求
* netTask 调 net_process_messages() 串行处理所有 CH395F 操作
* 由 CubeMX 在 MX_FREERTOS_Init()(osKernelInitialize 之后)中创建
*/
extern osMessageQueueId_t netMsgQueueHandle; /* CubeMX 生成的消息队列句柄 */
/* ================= 错误码存储(E-03 修复:按任务隔离,消除跨任务覆写竞态) =================
*
* 旧实现是单个全局 `static int s_net_errno`:netTask 在处理消息时写入本层的
* 「最后错误码」,调用方包装器随后在**自己的上下文**再写一次。两条写路径分属不同
* 任务且无互斥,A 任务刚被置好的码会被 B 任务的通知处理抢先覆盖 → A 读到 B 的码。
*
* 新实现把错误码存进「每个任务自己的槽位」:任务读写自己的槽位,天然无竞态,
* 且保留 net_get_errno() 原语义(本任务最后一次失败的精确码、成功不清零)。
*
* 容量:NET_ERRNO_TASK_SLOTS 个任务句柄槽位(应用任务 + netTask + 测试任务够用)。
* 超出时**不新增共享槽位**(那会把竞态带回来),而是统一落到 [0] 槽——
* 语义退化为全局共享,仅影响排障可读性,不影响任何功能判定。
*/
#define NET_ERRNO_TASK_SLOTS 8U
/* 每任务错误码槽位:slot[i].owner == 当前任务句柄 时命中 */
static struct {
void *owner; /* 槽主任务句柄(NULL = 空槽) */
int err; /* 该任务最后一次失败的精确错误码 */
} s_net_errno_slots[NET_ERRNO_TASK_SLOTS];
/*
* 函数功能:取本任务的错误码槽位(命中/分配),无 RTOS 时回退 [0]
* 入口参数:无
* 出口参数:无
* 返回值:槽位下标(0 ~ NET_ERRNO_TASK_SLOTS-1)
* 限定条件:内核未启动或中断上下文返回 0(不做槽位分配,避免在 ISR 里改槽位表)
* 函数说明:1. 已存在的槽位(owner 命中)直接复用;空槽(owner == NULL)首次占用
* 2. 快路径**只读不写**:并发读者绝不会看到"owner 已写、err 未写"的中间态
* 3. 槽位与任务不强绑定释放:任务句柄被回收复用时,新任务沿用该槽位的
* err 初值无碍(语义本就允许残留码,与旧全局变量行为一致)
*/
static uint32_t net_errno_slot(void) {
/* 任务句柄(未启动内核/中断上下文时为 NULL) */
void *me;
/* 空槽候选与循环变量 */
uint32_t i;
uint32_t empty = NET_ERRNO_TASK_SLOTS;
/* 内核未启动或中断上下文:统一用 [0] 槽(语义退化为共享,仅排障用) */
if (osKernelGetState() != osKernelRunning) {
return 0U;
}
me = (void *)xTaskGetCurrentTaskHandle();
if (me == NULL) {
return 0U;
}
/* 无锁快路径:找自己的槽位,顺带记下第一个空槽,全程**绝不写**任何槽
* (写会让并发读者看到半成品槽位) */
for (i = 0U; i < NET_ERRNO_TASK_SLOTS; i++) {
const void *owner = s_net_errno_slots[i].owner;
if (owner == me) {
return i; /* 命中:本任务已有槽位 */
}
if (owner == NULL && empty == NET_ERRNO_TASK_SLOTS) {
empty = i; /* 记录第一个空槽(尚未占用) */
}
}
/* 无自己的槽位:尝试占用空槽。本函数只在任务上下文走到这里(中断/内核未启动
* 已在上方返回 0),故用任务上下文临界区(与 App/log/console_log.c 同一写法) */
if (empty < NET_ERRNO_TASK_SLOTS) {
taskENTER_CRITICAL();
if (s_net_errno_slots[empty].owner == NULL) {
s_net_errno_slots[empty].err = NET_OK;
s_net_errno_slots[empty].owner = me;
taskEXIT_CRITICAL();
return empty;
}
/* 复查失败(被并发占用):不重试,回退 [0] 槽(这是**特意避免**再次循环
* 的取舍——重试会与另一任务形成写入竞争,那就把要修的竞态又带回来了) */
taskEXIT_CRITICAL();
}
/* 槽位用尽:回退 [0] 槽(共享语义,仅排障可读性下降) */
return 0U;
}
/*
* 函数功能:记录**本任务**最后一次失败的精确错误码(取代原 `s_net_errno = X` 赋值)
* 入口参数:err - 错误码(NET_ERR_xxx,负值)
* 出口参数:无
* 返回值:err 原值(便于写成 `return net_set_errno(NET_ERR_XXX);`)
* 限定条件:无
* 函数说明:1. 只写本任务的槽位,其他任务读不到、也改不了 → 消除跨任务覆写竞态
* 2. N-32 §10 模块化:由 static 提升为**模块间可见**(net_listener.c
* 也需要设 errno),声明与 NET_ERR_RETURN_1 宏见 net_socket.h
*/
int net_set_errno(int err) {
s_net_errno_slots[net_errno_slot()].err = err;
return err;
}
/*
* 私有函数声明区
*/
static int alloc_socket(void);
static void handle_connect_event(net_sock_t *p_sock);
static void handle_disconnect_event(net_sock_t *p_sock);
static void handle_recv_event(net_sock_t *p_sock);
static void handle_send_event(net_sock_t *p_sock);
static void handle_timeout_event(net_sock_t *p_sock);
static void fire_event(net_sock_t *p_sock, net_event_t event);
static void parse_ip_string(const char *str, uint8_t *ip_arr);
static int net_send_locked(net_sock_t *p_sock, const void *buf, int len);
static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
uint32_t timeout_ms, TaskHandle_t caller);
static int net_close_locked(net_sock_t *p_sock);
static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen);
static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int addrlen);
/* L3 pending 化(一期,E-02 结构演进):阻塞等待 = 挂起请求 + 主循环节拍完成
* "无数据 + 阻塞等待"不再自旋占用 netTask:登记 pending 后立即返回
* net_pending_tick(与 D10 重监听重试同节奏,net_process_messages 开头每轮调用)
* 按 ①数据 ②到点 ③断连 顺序检查完成条件并通知调用方(语义逐项对齐旧自旋循环)
* 关闭/网络重建/陈旧重同步走显式 settle/cancel 路径(取消必通知,调用方永不挂死)
* 公开 API 返回契约不变:>0 字节数 / 0 EOF / -1 错误(错误码经通知值回传)
*/
#define NET_PENDING_REGISTERED 0x70000000 /* 内部哨兵:pending 已登记(分派跳过即时通知;取值不与字节数空间冲突) */
#define NET_PENDING_WATCHDOG_MS 1000U /* 看门狗:到点后 1s 仍未了结 = 完成逻辑缺位 → 强制了结 + 错误日志 */
typedef struct {
uint8_t active; /* 1 = 本 fd 有挂起 recv */
void *buf; /* 调用方缓冲(仅指针,须保持有效至完成) */
int len; /* 缓冲容量 */
uint32_t deadline; /* 截止时刻(tick)= 登记时刻 + 本次预算 */
uint32_t registered; /* 登记时刻(tick;看门狗/日志) */
TaskHandle_t caller; /* 调用方任务(完成通知目标) */
} net_pending_t;
static net_pending_t s_net_pending[NET_MAX_SOCKETS]; /* 每 fd 一个挂起(禁止覆盖:覆盖丢失前调用方通知 → 挂死) */
static TaskHandle_t s_net_task_handle; /* 执行任务(net_init 捕获;防自通知死锁) */
static void net_pending_tick(void);
static void net_pending_complete(int sockfd, int result);
static int net_pending_register(int sockfd, void *buf, int len,
uint32_t to_ms, TaskHandle_t caller);
static void net_pending_clear(int sockfd);
static void net_pending_cancel(int sockfd, int err);
static void net_pending_cancel_all(int err);
static void net_pending_settle_close(net_sock_t *p_sock);
/*
* 函数功能:判断 6 字节 MAC 是否全零
* 入口参数:p_mac - MAC 指针(不可为 NULL) const uint8_t *
* 返回值:true - 全零(= 使用芯片内置 MAC);false - 非全零
* 限定条件:p_mac 非空
*/
static bool s_mac_is_zero(const uint8_t *p_mac) {
for (int i = 0; i < 6; i++) {
if (p_mac[i] != 0u) {
return false;
}
}
return true;
}
/*
* 函数功能:网络子系统初始化(配置 CH395F + 启用多连接模式)
* 入口参数:ip - IP 地址字符串 "192.168.1.100",NULL 使用 DHCP 自动获取 const char *
* mask - 子网掩码字符串 "255.255.255.0",NULL 使用默认值 const char *
* gateway - 网关地址字符串 "192.168.1.1",NULL 使用默认值 const char *
* mac - MAC 地址(6 字节,低字节在前),NULL/全零使用芯片内置 const uint8_t *
* 返回值:0 - 初始化成功 int
* -1 - 初始化失败(芯片检测/复位/协议栈初始化失败) int
* 限定条件:SPI2 已正确初始化;netTask 尚未启动
* 函数说明:1. 检查 CH395F 芯片是否存在并复位
* 2. 启用 TCP Server 多连接模式(bit1=1),设置 TCP MSS、重传参数、KeepAlive 参数
* 3. 配置 IP/网关/子网掩码,或启用 DHCP
* 4. 初始化 CH395F 协议栈并使能 PING 响应
* 5. mac 非空且非全零:初始化**之前**设置 MAC(§9.2.1 ①,Trap 24;
* 写芯片 EEPROM,每上电重放同值幂等)
*/
int net_init(const char *ip, const char *mask, const char *gateway, const uint8_t *mac) {
ch395f_status_t ret;
/* L3:网络重建前取消全部挂起请求并通知调用方(调用方阻塞等回执,
* 取消不通知 = 永久挂死);必须早于控制块清零与芯片复位 */
net_pending_cancel_all(NET_ERR_CONNRESET);
/* 清零 Socket 控制块 */
memset(s_net_socks, 0, sizeof(s_net_socks));
net_set_errno(NET_OK);
s_net_initialized = 0;
s_net_task_handle = xTaskGetCurrentTaskHandle(); /* 执行任务 = net_init 调用者(NET 层 = netTask / 硬件层 = testTask) */
/* 检查 CH395F 芯片 */
if (ch395f_check_exist() != CH395F_STATUS_OK) {
DBG_ERROR("net_init: ch395f_check_exist FAILED");
return NET_ERR_RETURN_1(NET_ERR);
}
/* 复位芯片 */
ch395f_reset();
/* 芯片版本留痕:SOCKET_CLOSE 需 ≥0x46、TCP_SERVER 需 ≥0x44(§5.52 表),
* 版本门槛排查曾是盲区(Trap 25 定案过程确认本芯片 SOCKET_CLOSE 实际生效) */
DBG_INFO("ch395f ver=0x%02X fun_para=0x08", ch395f_get_version());
/* 功能参数(§5.52 表):bit3=1 SOCKET_CLOSE——DISCONNECT/TIMEOUT 后芯片
* 不清缓冲,由 MCU 读出残留数据后再关 Socket(Trap 25 三次定案的排空契约);
* bit1=0 TCP_SERVER 多连接模式不用(本项目单连接模型:每监听器独占一个
* Socket,监听转数据通道);bit0 保留必须为 0——旧注释"bit0=1 多 Socket
* 模式"手册与网络资料均无出处(多 Socket 由 SN 命令族寻址实现,与 FUN_PARA
* 无关),且违反保留位规定 → 0x09 改 0x08(2026-10-11 定案:官方手册
* V1C/V1E/V2.2/V2.3 的 bit0 全是保留位,官方例程 bit0=0 下 Socket 0~7 照常
* 可用、FTP 例程本身就传 0x08;证据链见 Trap 18「定案更正」,
* 0x08 下的多 Socket 台架复测登记 N-31) */
ch395f_set_fun_para(0x08);
/* 设置 TCP MSS:维持 1024(512 裕量方案未采纳,当年支撑它的台架判据不可信,
* 已随 TC-NET-604 结案为非缺陷一并撤回(2026-09-23,见 Trap 21)) */
/* 手册(§5.45):内部缓冲共 48 块 × 512B = 24KB,每 Socket 分配 6 块(接收 4 块 = 2KB + 发送 2 块 = 1KB) */
/* 块布局固定(与 MSS 解耦);MSS=1024 为满足布局的最大值(接收 = 2×MSS),
* 真值源为 ch395f.h 的 CH395F_TCP_MSS */
ch395f_set_tcp_mss(CH395F_TCP_MSS);
/* 设置 TCP 重传参数(必须在 INIT_CH395 之前调用;P0-a:周期 500→200ms。
* 台架验收 2026-09-22 完成:GET_CMD_STATUS 对 SET 类命令无判别力(500/2000ms
* 行为一致,均为 RESET_ALL 的陈旧 BUSY 残留),值的接受性以手册范围(≤1000ms)
* + 全套件无回归为准——验收探针已移除,结论见《链路延迟测试说明》§10.1 D1 */
ch395f_set_retrans_count(NET_RETRANS_COUNT);
ch395f_set_retrans_period(NET_RETRANS_PERIOD_MS);
/* 设置 TCP KeepAlive 参数(必须在 INIT_CH395 之前,单位 ms,必须为 500 的倍数,且 IDLE > INTVL)
* 值来自 net_config.h 的 NET_KEEPALIVE_*(60s/5s/3)*/
ch395f_set_keepalive_idle(NET_KEEPALIVE_IDLE_MS); /* 空闲 60 秒后开始探测*/
ch395f_set_keepalive_intvl(NET_KEEPALIVE_INTVL_MS); /* 5 秒探测一次*/
ch395f_set_keepalive_cnt(NET_KEEPALIVE_CNT); /* 最多探 3 次*/
/* 设置 MAC(手册 §9.2.1 步骤①:SET_MAC 必须在 INIT_CH395 之前——SET_MAC 写芯片
* EEPROM,而 INIT_CH395 会初始化/加载 MAC;顺序反了则本次上电芯片仍用旧 MAC,
* UI 配置的 MAC 不生效(回落芯片内置/上次值),设备在线身份与 UI 显示不符。
* P1-7,2026-10-04);NULL 或全零 = 保持芯片内置 MAC,不下发;
* 写 EEPROM,每上电重放同值幂等 */
if (mac != NULL && s_mac_is_zero(mac) == false) {
ch395f_set_mac_addr((uint8_t *)mac);
}
/* 设置网络参数(必须在 INIT_CH395 之前调用 */
if (ip != NULL) {
uint8_t ip_arr[4];
uint8_t mask_arr[4];
uint8_t gw_arr[4];
parse_ip_string(ip, ip_arr);
parse_ip_string(mask, mask_arr);
parse_ip_string(gateway, gw_arr);
memcpy(s_local_ip, ip_arr, 4);
ch395f_set_ip_addr(ip_arr);
ch395f_set_gwip_addr(gw_arr);
ch395f_set_mask_addr(mask_arr);
} else {
/* 启用 DHCP */
ch395f_set_dhcp(1);
}
/* 初始化 CH395F 协议栈 */
ret = ch395f_init();
if (ret != CH395F_STATUS_OK) {
return NET_ERR_RETURN_1(NET_ERR);
}
/* 多 Socket 模式:为 Socket 0~7 各分配独立收发缓冲(必须在 OPEN_SOCKET 之前) */
/* 手册(§5.45):48 块 × 512B = 24KB;上电芯片默认仅给 Socket 0~3 分配(接收 8 / 发送 4 块) */
/* Socket 4~7 为 NULL,用到 8 个 Socket 必须重分配;单 Socket 块数为固定布局(ch395f.h,2026-09-22 起与 MSS 解耦) */
/* 每 Socket 6 块:接收 CH395F_RECV_BLOCKS(=4 块=2KB) + 发送 CH395F_SEND_BLOCKS(=2 块=1KB),用满 48 块 */
/* recv_start = i×6,send_start = recv_start+4;Socket 7 占块 42~47,恰好用尽 0~47,互不重叠 */
/* 布局以手册 §5.45 为准;若实际芯片为其它缓冲规格需重新核算 */
/* Trap 01:先配数据 Socket 1~7,再配监听 Socket 0 */
uint8_t blocks_per_socket = (uint8_t)(CH395F_RECV_BLOCKS + CH395F_SEND_BLOCKS);
for (int i = NET_MAX_SOCKETS - 1; i >= 0; i--) {
uint8_t recv_start = (uint8_t)(i * blocks_per_socket);
uint8_t send_start = (uint8_t)(recv_start + CH395F_RECV_BLOCKS);
ch395f_set_send_buf((uint8_t)i, send_start, (uint8_t)CH395F_SEND_BLOCKS);
ch395f_set_recv_buf((uint8_t)i, recv_start, (uint8_t)CH395F_RECV_BLOCKS);
}
/* 使能 PING 响应 */
ch395f_ping_enable(1);
s_net_initialized = 1;
return 0;
}
/*
* 函数功能:取网络代次号
* 入口参数:无
* 出口参数:无
* 返回值:代次号(每次网络重建完成递增) uint32_t
* 限定条件:无
* 函数说明:见 net_socket.h 同名函数说明
*/
uint32_t net_get_generation(void) {
/* 直接返回当前代次号(只读,无需锁) */
return s_net_generation;
}
/*
* 函数功能:递增网络代次号(网络重建完成、对外可用时调用)
* 入口参数:无
* 出口参数:无
* 返回值:无
* 限定条件:net_init() 成功且 PHY/链路就绪后调用(当前仅 net_stack_bring_up)
* 函数说明:见 net_socket.h 同名函数说明
*/
void net_generation_inc(void) {
/* 代次号 +1:各 TCP 服务检测到代次变化后重建监听(配置变更免重启生效) */
s_net_generation++;
}
#if defined(NET_BENCH_CMD_LOG)
/*
* 函数功能:台架 Bench 芯片命令序列日志(每条真实 SPI 命令记一行,排障用)
* 入口参数:tag - 命令类别 (P=poll全局中断 R=recv S=send C=close
* A=accept L=listen N=connect U=relisten M=其他) char
* sock - CH395F socket 号 int
* detail - 结果/中断状态 int
* 出口参数:无
* 返回值:无
* 限定条件:NET_BENCH_CMD_LOG 启用(2026-09-22 排障台架工具,默认关);netTask 上下文
* 函数说明:格式 netcmd: <tag> fd=<sock> t=<FreeRTOS tick> d=<距上条命令 tick 间隔>
* x=<detail>;台架专用,实验后回退
*/
static uint32_t s_bench_prev_tick = 0U;
static uint8_t s_bench_prev_valid = 0U;
static void net_bench_cmd_log(char tag, int sock, int detail) {
/* 当前 tick(命令时间戳) */
uint32_t now = (uint32_t)xTaskGetTickCount();
/* 距上一条命令的 tick 间隔(首条命令记 0) */
uint32_t delta = (s_bench_prev_valid != 0U) ? (now - s_bench_prev_tick) : 0U;
/* 更新"上一条命令"记录,供下次计算间隔 */
s_bench_prev_tick = now;
s_bench_prev_valid = 1U;
/* 台架日志:命令类别 + fd + tick + 间隔 + 结果/中断状态 */
DBG_INFO("netcmd: %c fd=%d t=%u d=%u x=%d", tag, sock, now, delta, detail);
}
#endif
/*
* 函数功能:轮询 CH395F 中断状态,更新 Socket 连接状态并处理事件
* 入口参数:无
* 出口参数:无
* 返回值:本次轮询中发生事件的 Socket 数量 int >= 0
* 限定条件:s_net_initialized == 1;必须在 netTask 上下文中调用
* 函数说明:1. GPIO 轮询 CH395F INT# 引脚电平(低电平有效),无需 EXTI 中断
* 2. 读取 GET_GLOB_INT_STATUS_ALL(2 字节版)获取所有 Socket 的中断状态
* 3. 处理连接/断开/数据接收/发送完成/超时等事件,更新 Socket 状态机
* 4. RECV 中断为电平触发,每次 net_poll() 只处理一批中断,避免主循环饿死
*/
int net_poll(void) {
/* 本次轮询累计发生的事件数(连接/断开/接收/发送/超时各计 1) */
int event_count = 0;
/* 全局中断状态寄存器值(CH395F GET_GLOB_INT_STATUS_ALL 为 16 位) */
/* 防御性检查:尚未初始化则不处理任何中断,直接返回 */
if (!s_net_initialized) {
return 0;
}
/* do-while(0) "单次批处理":每次调用只读取并处理一批中断,处理完立即返回,不循环等待 */
/* 避免 RECV 电平中断导致主循环(如 echo)饿死 */
/* 循环体内部用 break 提前退出(INT# 无效或无中断时) */
do
{
uint16_t int_status;
/* GPIO 轮询 INT# 引脚:低电平有效(有中断待处理),仅当低电平时才读取状态,减少不必要 SPI 事务 */
/* 采用 GPIO 轮询而非 EXTI 边沿中断;若 INT# 为高(无效),直接退出本次轮询 */
if (HAL_GPIO_ReadPin(CH395_INT_GPIO_Port, CH395_INT_Pin) == GPIO_PIN_SET) {
break;
}
/* 读取全局中断状态(2 字节):读操作本身会清除 CH395F 中断并使其拉高 INT#,
* 若此后仍有新中断,CH395F 会再次拉低 INT#,由下一次 net_poll() 处理 */
int_status = ch395f_get_glob_int_status_all();
#if defined(NET_BENCH_CMD_LOG)
/* 仅状态跳变时记(电平型 RECV_OK 会每 poll 重复,全记会冲爆日志) */
static uint16_t s_bench_last_glob = 0xFFFF;
if (int_status != s_bench_last_glob) {
s_bench_last_glob = (uint16_t)int_status;
net_bench_cmd_log('P', -1, (int)int_status);
}
#endif
/* 无有效中断(读到的状态为 0),提前退出 */
if (int_status == 0) {
break;
}
/* 遍历所有 Socket,处理各自的中断 */
for (int i = 0; i < NET_MAX_SOCKETS; i++) {
/* 取本 Socket 控制块(直接用数组下标,无需 net_get_sock 的越界判断) */
net_sock_t *p_sock = &s_net_socks[i];
/* 全局中断状态(GET_GLOB_INT_STATUS_ALL 16 位):Socket n 的中断位即 bit(n+4)。
* Socket 0~3 在低位字节 bit4~bit7;Socket 4~7 在高位字节的 bit0~3
* (即 16 位值的 bit8~bit11)。对应位为 0 说明该 Socket 无中断,跳过以节省时间。 */
if (!(int_status & (1U << (i + 4U)))) {
continue;
}
/* 读取该 Socket 的中断状态寄存器(SINT) */
uint8_t sock_int = ch395f_get_sock_int_status(i);
#if defined(NET_BENCH_CMD_LOG)
static uint8_t s_bench_last_sint[NET_MAX_SOCKETS] = { 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF };
if (sock_int != s_bench_last_sint[i]) {
s_bench_last_sint[i] = sock_int;
net_bench_cmd_log('p', i, (int)sock_int);
}
#endif
/* 连接建立事件:交给状态机处理(监听态/客户端/数据 Socket 分别处理) */
if (sock_int & CH395F_SINT_STAT_CONNECT) {
handle_connect_event(p_sock);
event_count++;
}
/* 连接断开事件:更新状态机并触发断开回调 */
if (sock_int & CH395F_SINT_STAT_DISCONNECT) {
handle_disconnect_event(p_sock);
event_count++;
}
/* 接收数据就绪事件:仅通知上层,实际数据由 net_recv_locked 读出(避免重复读 recv_len) */
if (sock_int & CH395F_SINT_STAT_RECV_OK) {
handle_recv_event(p_sock);
event_count++;
}
/* 发送完成事件:通知上层发送缓冲已空,可继续发送 */
if (sock_int & CH395F_SINT_STAT_SEND_OK) {
handle_send_event(p_sock);
event_count++;
}
/* 发送缓冲区空闲:置 send_ready 标志,唤醒等待发送的逻辑 */
if (sock_int & CH395F_SINT_STAT_SENBUF_FREE) {
p_sock->send_ready = 1;
}
/* 超时事件:连接超时或收发超时,更新状态机 */
if (sock_int & CH395F_SINT_STAT_SOCK_TIMEOUT) {
handle_timeout_event(p_sock);
event_count++;
}
}
/* 处理完本批中断后退出 do-while(0):RECV 中断为电平触发,只要缓冲仍有数据 INT# 就保持低 */
/* 若继续循环会反复处理 RECV 中断,饿死主循环中的应用代码(如 echo 回显) */
/* 因此每次 net_poll() 只处理“一批”,剩余数据在后续轮询中读取 */
} while (0);
/* 返回本次累计发生的事件数量(0 表示无事件) */
return event_count;
}
/*
* 函数功能:芯片/本层 socket 状态周期对账(Trap 22 丢中断自愈)
* 入口参数:无
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文(由 net_process_messages 每轮调用)
* 函数说明:1. 芯片在快速复连场景会丢 CONNECT/DISCONNECT 中断;丢事件后本层无法靠事件自愈
* 2. 典型:对端连接占着单连接监听的"后备"(芯片 TCP 仍 ESTABLISHED),后续 SYN 全被拒
* 3. 应用因未收到 DISCONNECT 仍以为有客户端,永不调用 accept,事件驱动自愈无路可走
* 4. 故周期回读芯片状态,修正**两类**失同步:
* 5. ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(只置事实;重建交 pump)
* 6. ② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(复用 handle_connect_event)
* 7. ⚠ N-32 P2(2026-10-12):原③(本层 LISTENING 丢监听)与④(孤儿监听)
* 已**整体移交** `net_listener_pump` —— 本函数内不再有任何重建动作
* (单一所有者);认领门槛相应收窄为 ESTABLISHED/LISTENING
* 8. 单次状态读可能落在"刚 open/listen 尚未生效"的瞬态
* 9. 同一失配需连续 NET_RECONCILE_STABLE_N 次才动手(2026-09-23 实测:无去抖时监听被反复重建直至放弃)
* 10. 同一 socket 动手后冷却 NET_RECONCILE_COOLDOWN_MS,防去抖成风暴
* 11. 周期 NET_RECONCILE_PERIOD_MS;每次每在用 socket 一条状态命令,开销可忽略
*/
#define NET_RECONCILE_PERIOD_MS 200U /* 对账周期 */
#define NET_RECONCILE_STABLE_N 2U /* 连续失配次数(去抖,≈400ms) */
#define NET_RECONCILE_COOLDOWN_MS 1000U /* 同一 socket 两次动手的最小间隔(防抖成风暴) */
static void net_reconcile_tick(void) {
/* 上次对账轮的时刻(tick) */
static uint32_t s_last = 0U;
/* 连续失配计数(去抖) */
static uint8_t s_bad[NET_MAX_SOCKETS];
/* 冷却到期时刻 */
static uint32_t s_cool[NET_MAX_SOCKETS];
/* 当前 tick */
uint32_t now = HAL_GetTick();
/* Socket 循环索引 */
int i;
/* 未到对账周期(200ms):直接返回,省 SPI 命令 */
if ((uint32_t)(now - s_last) < NET_RECONCILE_PERIOD_MS) {
return;
}
/* 更新本轮起始时刻 */
s_last = now;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
/* 取控制块(直接数组下标,无需越界判断) */
net_sock_t *p_sock = &s_net_socks[i];
/* 芯片侧状态两字节:[0]=socket 状态,[1]=TCP 状态 */
uint8_t st[2] = {0, 0};
/* 失同步类型:1=连接已失 2=握手已成丢中断 3=监听已失 */
int bad = 0;
/* 对账认领范围:**仅 ESTABLISHED / LISTENING**。
* ⚠ 原先还认领"孤儿监听"(CLOSED + auto_relisten + 已绑端口)以驱动对账④;
* N-32 P2 把③④整体移交给 net_listener_pump(唯一重建入口),本函数只剩
* ①②——两者都以 ESTABLISHED/LISTENING 为前提,故认领门槛相应收窄。 */
if ((p_sock->in_use == 0U) ||
((p_sock->state != NET_SOCK_STATE_ESTABLISHED) &&
(p_sock->state != NET_SOCK_STATE_LISTENING))) {
s_bad[i] = 0U;
continue;
}
/* 读芯片侧两字节状态(每 socket 一条 SPI 命令) */
ch395f_get_socket_status((uint8_t)i, st);
/* ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理 */
if ((p_sock->state == NET_SOCK_STATE_ESTABLISHED) &&
((st[0] == CH395F_SOCKET_CLOSED) || (st[1] == CH395F_TCP_CLOSED))) {
bad = 1;
} else if ((p_sock->state == NET_SOCK_STATE_LISTENING) &&
(st[1] == CH395F_TCP_ESTABLISHED)) {
/* ② 本层 LISTENING 而芯片已完成握手(丢 CONNECT 中断)→ 补做 accept
* N-30(Trap 25)幽灵态甄别:拆链收尾窗口内芯片假报 ESTABLISHED,
* 回读对端端口 = 上一条连接端口 → 不补 accept(补了会把数据发给
* 已死的旧连接 = 生产 LIST#4 数据被吞的直接成因),等拆净或新 SYN */
uint8_t remote[6] = {0, 0, 0, 0, 0, 0};
ch395f_get_remot_ipp((uint8_t)i, remote);
uint16_t peer = (uint16_t)(remote[4] | (remote[5] << 8));
/* 仅"端口非零且 = 上一条连接端口"判幽灵拦截;peer==0 不拦截
* (对端端口提交滞后时不得挡掉丢中断救援,见 net_lost_connect_probe 注释) */
if (!((peer != 0U) && (peer == p_sock->last_peer_port))) {
bad = 2;
}
}
/* ③④(本层 LISTENING 丢监听 / 本层 CLOSED 孤儿监听)已移交
* net_listener_pump —— N-32 P2 单一所有者,本函数不再做重建动作 */
/* 无失同步:清零计数,转下一 socket */
if (bad == 0) {
s_bad[i] = 0U;
continue;
}
/* 累加连续失配计数(封顶至去抖门槛) */
if (s_bad[i] < NET_RECONCILE_STABLE_N) {
s_bad[i]++;
}
/* 去抖未满或处于冷却期:本轮不动手 */
if ((s_bad[i] < NET_RECONCILE_STABLE_N) ||
((int32_t)(now - s_cool[i]) < 0)) {
continue;
}
/* 动手前:清失配计数并置冷却到期时刻(1s) */
s_bad[i] = 0U;
s_cool[i] = now + NET_RECONCILE_COOLDOWN_MS;
if (bad == 1) {
/* 类型①:按断开处理(应用读到 EOF 后由 pump 重建监听) */
DBG_INFO("sock%d reconcile: chip closed (sock=0x%02X tcp=0x%02X) -> disconnect",
i, st[0], st[1]);
/* 复用断开状态机(含陈旧事件甄别 + 状态迁移 + 事件派发);
* 只置事实,重建由 net_listener_pump 统一做(N-32 P2) */
handle_disconnect_event(p_sock);
} else {
/* 类型②:补做 standalone accept */
DBG_INFO("sock%d reconcile: connection completed w/o CONNECT irq -> accept", i);
/* 复用 CONNECT 事件的簿记路径 */
handle_connect_event(p_sock);
}
}
}
/*
* 函数功能:netTask 主循环核心 —— 自愈 tick 宿主 + 消息队列分派器。每轮
* 先跑三个非阻塞自愈 tick(D10 重监听 / L3 挂起 recv /
* Trap 22 状态对账),再非阻塞排空队列按 msg.type 分派到 _locked 实现。
* 入口参数:无
* 出口参数:无
* 返回值:无(void);消息结果经 xTaskNotify 通知值回传(成功=字节数/描述符,
* 失败=具体错误码),调用方 net_wait_result 拾取,对外统一 -1。
* 限定条件:必须在 netTask 上下文中调用(独占 CH395F 与 s_net_socks 访问权);
* 由 net_task.c 在 net_stack_bring_up 之后每次 1ms 循环调用。
* 函数说明:1. 四个自愈 tick 均非阻塞(绝不 osDelay 忙等,netTask 永不冻结),先于消息分派执行
* 2. net_listener_retry_tick(D10):断连后按 30ms
* (NET_RELISTEN_RETRY_MS)节拍重试 open+listen;2s(NET_RELISTEN_DEADLINE_MS)
* 总时限到期放弃留痕
* 3. D10:本 fd 有在途挂起 recv 时跳过(L3 保险)
* 4. net_pending_tick(L3):按节拍了结挂起 recv(数据/到点/断连)
* 5. L3 看门狗:到点后 1s(NET_PENDING_WATCHDOG_MS)未了结 → 强制了结 + DBG_ERROR 留痕
* 6. net_reconcile_tick(Trap 22):每 200ms(NET_RECONCILE_PERIOD_MS)对账①②
* 7. 对账以连续 2 次失配去抖 + 1s 冷却,自愈两类失同步
* 8. net_listener_pump(N-32 P2):每 100ms 监听器 pump,本层**唯一**重建入口
* (原对账③④;本层 CLOSED 而芯片在听时只重同步)——置于对账之后
* 9. net_msg_t 按值入队,netTask 侧副本调用方不可见 → 结果只能搭任务通知值回传
* 10. 32 位整体即结果,不做位域压缩
* 11. RECV 特例:无数据+阻塞 → 登记 pending 哨兵 NET_PENDING_REGISTERED(0x70000000)
* 12. RECV 特例 do_notify=0,回执推迟到 net_pending_tick 完成时
*/
void net_process_messages(void) {
net_msg_t msg;
/* 队列句柄未就绪(net_init 未完成):直接返回 */
if (netMsgQueueHandle == NULL) {
return;
}
/* 三个非阻塞自愈 tick 置于消息处理之前:断连后即使无应用消息,监听器也能自主恢复 */
/* D10 重监听重试(Trap 09):沉降后重开芯片监听器 */
net_listener_retry_tick();
/* L3 挂起 recv 完成检查:数据到达 / 到点 / 断连时通知调用方 */
net_pending_tick();
/* Trap 22 状态对账:周期回读芯片状态,自愈丢失的中断(①②;③④ 已归 pump) */
net_reconcile_tick();
/* N-32 P2:监听器 pump —— 本层唯一重建入口(置于对账之后:对账①②
* 可能刚把会话置 CLOSED,pump 紧接着就能看到并重建) */
net_listener_pump();
/* 队列排空:超时 0 非阻塞取,每条处理一轮,直到本轮队列为空 */
while (osMessageQueueGet(netMsgQueueHandle, &msg, NULL, 0) == osOK) {
/* 默认处理完即发即时回执;RECV 挂起登记时置 0(回执推迟到
* net_pending_tick 完成检查,避免"登记成功"被误报为处理结果) */
int do_notify = 1;
switch (msg.type) {
case NET_MSG_SEND: {
/* 取控制块;NULL = fd 越界或已关闭 */
net_sock_t *p_sock = net_get_sock(msg.sockfd);
if (p_sock == NULL) {
/* 描述符无效 → BADF,结果 -1 */
net_set_errno(NET_ERR_BADF);
msg.result = -1;
} else if (p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
/* 未建立连接(或已断开)→ NOTCONN */
net_set_errno(NET_ERR_NOTCONN);
msg.result = -1;
} else {
/* 真正写入芯片:1KB/块等 SENBUF_FREE 中断(§5.32/§5.23①),
* 单块停滞 1s 超时,中途断连立即 CONNRESET;短写留痕,调用方须检查返回值 */
msg.result = net_send_locked(p_sock, msg.buf, msg.len);
}
break;
}
case NET_MSG_RECV: {
/* 取控制块;NULL = fd 越界或已关闭 */
net_sock_t *p_sock = net_get_sock(msg.sockfd);
if (p_sock == NULL) {
/* 描述符无效 → BADF,结果 -1 */
net_set_errno(NET_ERR_BADF);
msg.result = -1;
} else {
/* 三条出口:数据就绪 → 即时 SPI 读出;DONTWAIT 无数据 → 0(对端关闭)
* / WOULDBLOCK;阻塞无数据 → 登记挂起请求(哨兵,netTask 零占用) */
msg.result = net_recv_locked(p_sock, msg.buf, msg.len, msg.flags,
msg.timeout_ms, msg.caller);
if (msg.result == NET_PENDING_REGISTERED) {
/* 挂起已登记:不发即时回执,完成时才通知(防"登记成功"误报为结果) */
do_notify = 0;
}
}
break;
}
case NET_MSG_CLOSE: {
/* 取控制块;NULL = fd 越界或已关闭 */
net_sock_t *p_sock = net_get_sock(msg.sockfd);
if (p_sock == NULL) {
/* 描述符无效 → BADF,结果 -1 */
net_set_errno(NET_ERR_BADF);
msg.result = -1;
} else {
/* 先了结本 fd 挂起 recv(排空或取消,必通知),再关芯片侧 Socket;
* 单连接模型监听 fd 断开后自动恢复监听,应用不得对它在 EOF 时 close */
msg.result = net_close_locked(p_sock);
}
break;
}
case NET_MSG_CONNECT: {
/* 保存对端地址(公开 API 已 memcpy 入 msg.addr)并配置芯片:
* open(失败重试 3 次)+ 源/目的端口 + 目的 IP,清残留中断,
* 下发 TCP CONNECT 并置 CONNECTING(异步,CONNECT 中断后才成立) */
msg.result = net_connect_locked(msg.sockfd,
(const struct net_sockaddr *)&msg.addr,
msg.addrlen);
break;
}
case NET_MSG_LISTEN: {
/* 芯片侧 open + TCP_LISTEN(失败重试 3 次),置 LISTENING 并启用
* auto_relisten(断开后自动重监听,模拟 BSD 监听常驻);单连接模型下
* 该 fd 后续即数据通道本身(g_client==g_listen) */
msg.result = net_listen_locked(msg.sockfd);
break;
}
case NET_MSG_ACCEPT: {
/* 对端地址指针与长度经 msg.buf/msg.len 字段携带(公开 API net_accept 填入) */
struct net_sockaddr *p_addr = (struct net_sockaddr *)msg.buf;
int addrlen = msg.len;
/* 仅在调用方给出有效 addr 且 addrlen>0 时,成功后回填对端地址长度 */
int *p_addrlen = (p_addr != NULL && addrlen > 0) ? &addrlen : NULL;
/* 单连接:ESTABLISHED 即有可接受连接(返回同一 fd,监听 Socket
* 自身已转数据通道);否则先尝试恢复监听,仍无则 WOULDBLOCK */
msg.result = net_accept_locked(msg.sockfd, p_addr, p_addrlen);
break;
}
case NET_MSG_RESYNC_LISTEN: {
/* 取控制块;NULL = fd 越界或已关闭 */
net_sock_t *p_sock = net_get_sock(msg.sockfd);
if (p_sock == NULL) {
/* 描述符无效 → BADF,结果 -1 */
net_set_errno(NET_ERR_BADF);
msg.result = -1;
} else if (p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
/* 非 ESTABLISHED(LISTENING/CLOSED 等):两侧已一致,无需动作 */
msg.result = 0;
} else {
/* 陈旧 ESTABLISHED:芯片已静默回收连接(无 DISCONNECT 中断,
* 2026-09-22 台架实证)→ 仅修 MCU 侧状态;严禁发芯片
* close/重监听命令(会摧毁芯片侧仍健康的监听器 → 端口永拒,
* run8 实证 170s 拒绝);新连接的 CONNECT 中断会把两侧重新同步 */
p_sock->state = NET_SOCK_STATE_LISTENING;
/* N-30(Trap 25):死连接对端端口转登幽灵甄别基准 + 去抖清零 */
p_sock->last_peer_port = p_sock->remote_port;
p_sock->lost_conn_seen = 0U;
/* 取消本 fd 在死连接上的挂起 recv(必通知,防调用方挂死) */
net_pending_cancel(msg.sockfd, NET_ERR_CONNRESET);
/* 仅修正 MCU 侧状态,不发芯片命令,直接返回成功 */
msg.result = 0;
}
break;
}
case NET_MSG_RELISTEN: {
/* 描述符有效性检查(NULL = fd 越界或已关闭)→ 无效即 BADF;
* 有效则交 net_listener_try(它内部自行取控制块,本处不再持有指针,
* N-32 §10 入口收敛) */
if (net_get_sock(msg.sockfd) == NULL) {
/* 描述符无效 → BADF,结果 -1 */
net_set_errno(NET_ERR_BADF);
msg.result = -1;
} else {
/* 无条件芯片侧重监听:单次尝试,失败登记非阻塞重试
* (芯片已在监听 → 下一轮到点再来;两种陈旧场景都收敛到"芯片监听器在位") */
msg.result = net_listener_try(msg.sockfd);
}
break;
}
default:
/* 未知消息类型(枚举闭合,正常不应触发):置通用错误码与结果 -1,
* 保证通知值恒为本条消息的确定结果(无上一条残留) */
net_set_errno(NET_ERR);
msg.result = -1;
break;
}
#if defined(NET_BENCH_CMD_LOG)
{
/* 台架构建:每条消息留一行命令日志(R/S/C/A/L/N/M) */
char bench_tag = (msg.type == NET_MSG_RECV) ? 'R' :
(msg.type == NET_MSG_SEND) ? 'S' :
(msg.type == NET_MSG_CLOSE) ? 'C' :
(msg.type == NET_MSG_ACCEPT) ? 'A' :
(msg.type == NET_MSG_LISTEN) ? 'L' :
(msg.type == NET_MSG_CONNECT) ? 'N' : 'M';
/* 记录命令类型、fd 与结果(负错误码)供台架判读 */
net_bench_cmd_log(bench_tag, msg.sockfd, (int)msg.result);
}
#endif
/* 结果回传:消息体按值拷入队列,调用方看不到 netTask 对 msg 的修改
* → 只能搭 32 位任务通知值回来(整体即结果:位域会把上限压到
* 32767,TEST_BUF_SIZE=32768 会溢出为负) */
if (do_notify != 0) {
/* 成功:结果的 32 位原值(字节数 / 描述符) */
uint32_t notify_val = (uint32_t)msg.result;
/* 失败:回传本条消息**自身**的精确错误码(NET_ERR_WOULDBLOCK 等)。
* E-03 后取的是 netTask 自己的错误码槽位——锁定实现(*_locked)在
* 失败返回前记录的码,不受其他任务影响;未置位时回退通用 NET_ERR。 */
if (msg.result < 0) {
int te = s_net_errno_slots[net_errno_slot()].err;
notify_val = (uint32_t)((te != 0) ? te : NET_ERR);
}
/* 覆盖式写入通知值:调用方零等待拾取即得最新回执 */
xTaskNotify(msg.caller, notify_val, eSetValueWithOverwrite);
}
}
}
/*
* 函数功能:结果等待(两段式,防丢失通知;结果按 32 位原值回传)
* 入口参数:kind = 操作简称;sockfd = Socket(两者仅用于调试留痕,避免未用参数告警)
* 出口参数:无
* 返回值:netTask 回传的结果(成功=字节数/描述符;失败=负错误码)
* 限定条件:netTask 上下文(消息分派路径内部使用,非公开 API)
* 函数说明:1. 零等待先拾取:"入队→等待入口"间隙内的通知已在任务通知值里,
* 单段阻塞等待会错过它(2026-09-22 台架实证)
* 2. 清除掩码必须全 1:xTaskNotifyWait 以位掩码清值,传 0 = 不清除
* 3. 否则上次结果残留、被每次零等待重读成"本次结果"(run3~run11 L7 挂死/整段错位的真正根因)
* 4. 必须以返回值(pdTRUE/pdFALSE)判定是否已落地,不能判 v != 0
* 5. 合法的 0 结果(recv 0 = 对端关闭)会被误判成"无通知" → 阻塞等待 → 永久挂死
* 6. 通知值 32 位整体作为结果(不切分位域),保证 TEST_BUF_SIZE=32768 这类大单次收发不被截断
* 7. 禁止在本路径加逐调用日志:曾加 pick/block 两行,占日志 98.6%,
* 115200 下冲掉 [LAT] 证据行、并让 wait-idle 永远判不出空闲(run14 实证)
* 8. 诊断需求请在 [LAT] 段报告侧做聚合
*/
static int net_wait_result(const char *kind, int sockfd) {
/* 32 位结果值(任务通知值整体) */
uint32_t v = 0U;
/* kind/sockfd 仅供调用链调试留痕,函数体不直接使用 */
(void)kind;
(void)sockfd;
/* 零等待:拾取并消费"入队→此处"间隙内已落地的通知(含结果为 0 的通知) */
if (xTaskNotifyWait(0xFFFFFFFFU, 0xFFFFFFFFU, &v, 0) == pdTRUE) {
return (int)v;
}
/* 无通知在途:阻塞等待本消息回执(进入临界区的瞬间若有通知落地,
* xTaskNotifyWait 的状态位判定会立即返回,不会睡过去) */
xTaskNotifyWait(0xFFFFFFFFU, 0xFFFFFFFFU, &v, portMAX_DELAY);
return (int)v;
}
/*
* 函数功能:创建 Socket,分配空闲 Socket 控制块并初始化状态
* 入口参数:domain - 地址族,仅支持 NET_AF_INET int
* type - SOCK_STREAM(TCP) 或 SOCK_DGRAM(UDP) int
* 出口参数:无
* 返回值:Socket 描述符 (0~7),失败返回 -1 int
* 限定条件:net_init() 已调用成功;s_net_initialized == 1
* 函数说明:自动分配一个空闲的 Socket 控制块(in_use=0),初始化为 CREATED 状态
*/
int net_socket(int domain, int type) {
/* 新分配的 Socket 描述符 */
int sockfd;
/* 分配到的控制块指针 */
net_sock_t *p_sock;
/* 参数检查:地址族仅支持 NET_AF_INET */
if (domain != NET_AF_INET) {
/* 不支持的地址族 → INVAL */
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 分配空闲控制块(in_use=0 的槽位) */
sockfd = alloc_socket();
if (sockfd < 0) {
/* 无空闲控制块 → NOMEM */
return NET_ERR_RETURN_1(NET_ERR_NOMEM);
}
/* 初始化控制块:芯片侧 Socket 编号与描述符一一对应 */
p_sock = &s_net_socks[sockfd];
/* 芯片侧 Socket 索引(CH395F 命令的 sock 参数) */
p_sock->ch395_sock = (uint8_t)sockfd;
/* 协议类型:SOCK_STREAM(TCP) / SOCK_DGRAM(UDP) */
p_sock->type = (uint8_t)type;
/* 初始状态 CREATED(TCP 待 bind+listen;UDP 在 bind 时直接开芯片侧) */
p_sock->state = NET_SOCK_STATE_CREATED;
/* 标记在用 */
p_sock->in_use = 1;
/* 发送缓冲初始为空闲(可发) */
p_sock->send_ready = 1;
/* 无可读数据 */
p_sock->readable = 0;
/* 新 fd 不得继承上一条连接的释放原因(Trap 23 待办 #3) */
p_sock->released_abnormal = 0;
/* N-32(P1):新 fd 不得继承"上一条会话已结束"电平(否则新建即 EOF,
* 消费侧连数据都收不到)——与 released_abnormal 同理,显式清零 */
p_sock->session_dead = 0;
/* 新 fd 不得继承上一次的待重试状态(D10) */
net_listener_clear(sockfd);
/* 新 fd 不得继承上一次挂起的 recv 请求(L3) */
net_pending_clear(sockfd);
/* 返回描述符(控制块已就绪) */
return sockfd;
}
/*
* 函数功能:绑定 Socket 到本地 IP 地址和端口号
* 入口参数:sockfd - Socket 描述符 (0~7) int
* addr - 本地地址结构体指针 const struct net_sockaddr *
* addrlen - 地址结构体长度 int >= sizeof(struct net_sockaddr_in)
* 出口参数:无
* 返回值:0 - 绑定成功 int
* -1 - 绑定失败(参数无效、Socket 未使用等) int
* 限定条件:Socket 已通过 net_socket() 创建且未被其他操作占用;net_init() 已调用
* 函数说明:1. TCP Socket 仅记录本地端口,进入 BOUND 状态等待 listen
* 2. UDP Socket(SOCK_DGRAM)立即打开 CH395F Socket 并配置为 Server 模式(Socket 4~7 需显式分配收发缓冲区)
*/
int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) {
/* Socket 控制块指针 */
net_sock_t *p_sock;
/* 本地地址(IPv4)强转 */
const struct net_sockaddr_in *p_addr_in;
/* 参数检查:描述符范围 */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
/* 越界 → BADF */
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 取控制块 */
p_sock = &s_net_socks[sockfd];
/* 控制块未在用(未创建/已关闭)→ BADF */
if (!p_sock->in_use) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 地址指针/长度无效 → INVAL */
if (addr == NULL || addrlen < (int)sizeof(struct net_sockaddr_in)) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 地址强转为 IPv4 结构体 */
p_addr_in = (const struct net_sockaddr_in *)addr;
/* 设置本地端口(结构体中为网络序 → 转主机序) */
p_sock->local_port = net_ntohs(p_addr_in->sin_port);
/* port=0 时自动分配动态端口(49152~65535) */
if (p_sock->local_port == 0) {
/* 取当前动态端口值 */
p_sock->local_port = s_dynamic_port;
/* 推进动态端口,供下次分配 */
s_dynamic_port++;
/* 回绕:越过 65535 后回到动态区起始 */
if (s_dynamic_port == 0) {
s_dynamic_port = 49152;
}
}
/* UDP (SOCK_DGRAM):直接在此打开 CH395F Socket(无需等 listen) */
if (p_sock->type == NET_SOCK_DGRAM) {
/* open_socket 返回状态 */
uint8_t status;
/* net_init 已为全部 8 个 Socket 分配缓冲;此处仅对 Socket 4~7 兜底重设,
* 使用与 net_init 相同的布局(固定 6 块/Socket,与 MSS 解耦,见 net_init / Trap 21) */
if (sockfd >= 4) {
/* 每 Socket 块数 = 接收 + 发送 */
uint8_t blocks_per_socket = (uint8_t)(CH395F_RECV_BLOCKS + CH395F_SEND_BLOCKS);
/* 本 Socket 接收缓冲起始块 */
uint8_t recv_start = (uint8_t)(sockfd * blocks_per_socket);
/* 发送缓冲起始块(接收块之后) */
uint8_t send_start = (uint8_t)(recv_start + CH395F_RECV_BLOCKS);
/* (重)设置发送缓冲块 */
ch395f_set_send_buf((uint8_t)sockfd, send_start, (uint8_t)CH395F_SEND_BLOCKS);
/* (重)设置接收缓冲块 */
ch395f_set_recv_buf((uint8_t)sockfd, recv_start, (uint8_t)CH395F_RECV_BLOCKS);
}
/* 设置协议类型为 UDP */
ch395f_set_proto_type((uint8_t)sockfd, CH395F_PROTO_TYPE_UDP);
/* 设置本地(源)端口 */
ch395f_set_sour_port((uint8_t)sockfd, p_sock->local_port);
/* DesIP=0xFFFFFFFF:UDP Server 模式(接受任意来源) */
{
/* 任意源地址(全 0xFF) */
uint8_t broadcast[4] = {0xFF, 0xFF, 0xFF, 0xFF};
/* 目的 IP 设为任意源 */
ch395f_set_des_ip((uint8_t)sockfd, broadcast);
}
/* 目的端口 0(服务端不限定对端端口) */
ch395f_set_des_port((uint8_t)sockfd, 0);
/* 打开芯片侧 Socket */
status = ch395f_open_socket((uint8_t)sockfd);
if (status != CH395F_ERR_SUCCESS) {
/* open 失败:留痕并返回 BUSY(芯片忙/复位冲突) */
DBG_ERROR("sock%d open_socket FAIL: status=0x%02X (expected 0x00)", sockfd, status);
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* UDP 打开即可用(无监听态) */
p_sock->state = NET_SOCK_STATE_UDP_OPEN;
} else {
/* TCP 仅记录本地端口,待 listen 打开芯片侧 */
p_sock->state = NET_SOCK_STATE_BOUND;
}
/* 绑定成功 */
return 0;
}
/*
* 函数功能:启动 TCP Server 监听(内部版本,必须在 netTask 上下文调用)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 出口参数:无
* 返回值:0 - 成功,-1 - 失败 int
* 限定条件:sockfd 已绑定且为 TCP;netTask 上下文
* 函数说明:1. 芯片侧 open + TCP_LISTEN(端口=控制块 local_port;open 失败重试 3 次,见 Trap 09)
* 2. 成功置 LISTENING 并启用 auto_relisten(BSD 风格监听常驻)
* 3. 启动监听前清**本 Socket** 中断状态寄存器(读即清),防残留误触发;
* 全局中断寄存器交 net_poll 集中读清(B3,详见函数体内注释)
*/
int net_listen_locked(int sockfd) {
/* 取监听 Socket 控制块(直接下标访问,sockfd 已在上层 net_listen 校验过范围) */
net_sock_t *p_sock = &s_net_socks[sockfd];
/* CH395F 打开 Socket 的返回状态 */
uint8_t status;
/* 合法性检查:Socket 必须已创建且为 TCP 流类型 */
if (!p_sock->in_use || p_sock->type != NET_SOCK_STREAM) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 状态检查:仅允许在 BOUND(已 bind)或 CREATED(未 bind 直接用随机端口)状态下启动监听 */
if (p_sock->state != NET_SOCK_STATE_BOUND &&
p_sock->state != NET_SOCK_STATE_CREATED) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 单连接:监听 Socket 自身即数据通道;收发缓冲已在 net_init 为全部
* 8 个 Socket 显式重分配(6 块/Socket,net_socket.c:339-345),此处不再分配 */
/* 设置协议类型为 TCP */
ch395f_set_proto_type(sockfd, CH395F_PROTO_TYPE_TCP);
/* 本地端口 = 控制块 local_port */
ch395f_set_sour_port(sockfd, p_sock->local_port);
/* CH395F 关闭 Socket 后需延时才能重新打开(Trap 09),故失败重试最多 3 次 */
for (int open_try = 0; open_try < 3; open_try++) {
/* 尝试打开芯片侧 Socket */
status = ch395f_open_socket(sockfd);
if (status == CH395F_ERR_SUCCESS) {
/* 打开成功,退出重试 */
break;
}
/* 打开失败先关闭,再延时后重试 */
ch395f_close_socket(sockfd);
/* 等 500ms 芯片沉降后再试(Trap 09) */
osDelay(500);
}
/* 3 次仍失败,返回错误 */
if (status != CH395F_ERR_SUCCESS) {
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 下发 TCP LISTEN 命令,进入监听状态 */
status = ch395f_tcp_listen(sockfd);
/* LISTEN 失败 → BUSY */
if (status != CH395F_ERR_SUCCESS) {
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 监听 Socket 进入 LISTENING 态 */
p_sock->state = NET_SOCK_STATE_LISTENING;
/* 单连接模式:连接断开后由 net 层自动回到 LISTEN(模拟 BSD 监听常驻) */
p_sock->auto_relisten = 1;
/* N-30(Trap 25):新监听会话,丢中断去抖计数清零 */
p_sock->lost_conn_seen = 0U;
/* 读即清:仅本 Socket 的中断状态——丢弃上一条会话残留的 DISCONNECT/
* TIMEOUT,避免监听刚开始就误触发事件。
* ⚠ B3(2026-10-12 审计):此处原为"遍历 8 个 Socket 全清 + 再读清全局",
* git 考古证明是多连接时代的调试遗留(e862009bc 无注释引入 → 319d00e3
* 原样搬进单连接模型 → db64e6acf 又复制进 net_relisten_locked),不属于
* 任何已登记 Trap 的修复。其危害:§5.15 明文全局中断状态"整体读即清"
* (芯片无按位清除手段),而 net_poll 只在**全局位置位时**才去读该
* Socket 的 SINT(见 :469 门控)→ 全量清等于把别的在用 Socket 的门一并
* 焊死,其 CONNECT/DISCONNECT 等边沿事件永久丢失(RECV 属条件位,下轮
* 可补回,见 Trap 08)。
* 全局寄存器交 net_poll 集中读清(AGENTS 模块约定:所有 CH395F 中断经
* net_poll 集中读取并清除);本 Socket 残留的全局位被 net_poll 读到时
* SINT 已为 0,无害了结。Trap 14/17/23 指定的两级预清落在
* net_connect_locked / net_close_locked / relisten 隔离段,均为单 Socket,
* 本次未动。 */
(void)ch395f_get_sock_int_status((uint8_t)sockfd);
/* 监听建立完成 */
return 0;
}
/*
* 函数功能:启动 TCP Server 监听(线程安全版本,通过消息队列委托给 netTask)
* 入口参数:sockfd - Socket 描述符 int
* 出口参数:无
* 返回值:0 - 成功,-1 - 失败 int
* 限定条件:net_init() + MX_FREERTOS_Init() 已执行;必须在任务上下文中调用
* 函数说明:通过消息队列委托给 netTask,确保 SPI 串行化
*/
int net_listen(int sockfd) {
/* 消息体(按值拷入队列,netTask 改的是其副本) */
net_msg_t msg;
/* 描述符越界 → BADF */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 消息队列未创建(网络层未初始化)→ 通用错误 */
if (netMsgQueueHandle == NULL) {
return NET_ERR_RETURN_1(NET_ERR);
}
/* 消息类型:启动 TCP 监听 */
msg.type = NET_MSG_LISTEN;
/* 目标 Socket */
msg.sockfd = sockfd;
/* 无超时(监听立即完成) */
msg.timeout_ms = 0U;
/* 调用方任务句柄(netTask 凭它回传回执) */
msg.caller = xTaskGetCurrentTaskHandle();
/* 结果占位(netTask 侧覆写实际结果) */
msg.result = -1;
/* 入队:队列满时阻塞调用方直至有空位 */
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
/* 入队失败 → BUSY */
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 阻塞等待 netTask 回执(两段式拾取,防丢失通知) */
msg.result = net_wait_result("lis", sockfd);
if (msg.result < 0) {
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
* errno;对外仍保持 BSD 语义——失败一律返回 -1 */
return NET_ERR_RETURN_1(msg.result);
}
/* 返回结果(成功 = 描述符/字节数) */
return msg.result;
}
/*
* 函数功能:TCP Server 接受新连接(内部版本,必须在 netTask 上下文调用)
* 入口参数:sockfd - 监听 Socket 描述符 int
* 出口参数:addr - 输出:客户端地址结构体指针 struct net_sockaddr *(可为 NULL)
* addrlen - 输入输出:地址结构体长度 int *(可为 NULL)
* 返回值:新连接的 Socket 描述符,失败返回 -1 int
* 限定条件:Socket 处于 LISTENING 状态
* 函数说明:单连接:监听 Socket 自身在 CONNECT 后已转为 ESTABLISHED
*/
/* ===== N-30 accept 路径"丢 CONNECT 中断"补 accept(2026-10-11 裸机芯片台架实证)=====
* 台架结论(剥离 OS/net/FTP 直接对芯片做受控 close→open→listen→connect 矩阵):
* 芯片关闭连接后的拆链收尾窗口**变长且不可见**(状态寄存器显示 CLOSED 也不代表
* 拆完,实测 >2s 仍可能未拆完);该窗口内重新 listen 后,新连接握手照常完成
* (芯片状态寄存器 → ESTABLISHED)但 **CONNECT 中断被丢**——纯事件驱动的 accept
* 永远等不到 → 数据被吞 / 425。固定沉降延时、Socket 轮换、listen 后 close 重试、
* 缓冲重分配均已被实验证伪;唯一有效 = "中断 + 状态轮询"双信源(修复组 15/15
* 全过,机制触发 5 次全部救回)。→ CH395F_Trap_Records.md Trap 25
* 幽灵态甄别:拆链窗口内芯片也会直接假报 ESTABLISHED(旧连接未拆净),此时回读
* 对端端口 = 上一条连接端口;新连接临时端口几乎必然不同 → 端口相同不 accept,
* 等旧连接拆净 / 新 SYN 到达(新连接若也丢中断,后续轮询以新端口命中)。 */
#define NET_LOST_CONN_STABLE_N 2U /* 连续确认轮次(accept 轮询 100ms/轮,≈200ms 去抖) */
/*
* 函数功能:监听等待期的芯片状态轮询探针——芯片已 ESTABLISHED 而 CONNECT 中断
* 未到(丢中断)时,去抖满后补做 accept(复用 CONNECT 事件簿记路径)。
* 入口参数:p_sock - 处于 LISTENING 的监听控制块 net_sock_t *
* 出口参数:无(命中时 p_sock->state 经 handle_connect_event 转 ESTABLISHED)
* 返回值:无
* 限定条件:netTask 上下文(net_accept_locked 内调用,SPI 访问安全)
* 函数说明:1. 每轮 accept 轮询(100ms)读一次芯片状态,兼作状态第二信源
* 2. 幽灵态(对端端口 = last_peer_port)不动手,去抖清零
* 3. 连续 NET_LOST_CONN_STABLE_N 轮确认后才补 accept(防"刚 listen
* 尚未生效"的瞬态误判——2026-09-23 实测教训)
*/
static void net_lost_connect_probe(net_sock_t *p_sock) {
/* 芯片侧状态两字节:[0]=socket 状态,[1]=TCP 状态 */
uint8_t st[2] = {0, 0};
/* 对端 IP+端口回读缓冲(4B IP + 2B 端口小端) */
uint8_t remote[6] = {0, 0, 0, 0, 0, 0};
/* 读芯片状态 */
ch395f_get_socket_status(p_sock->ch395_sock, st);
/* 芯片尚未完成握手:去抖计数清零,本轮无动作 */
if (st[1] != CH395F_TCP_ESTABLISHED) {
p_sock->lost_conn_seen = 0U;
return;
}
/* 已 ESTABLISHED:回读对端端口做幽灵甄别 */
ch395f_get_remot_ipp(p_sock->ch395_sock, remote);
/* 对端端口(小端 2 字节) */
uint16_t peer = (uint16_t)(remote[4] | (remote[5] << 8));
/* 幽灵甄别:仅"端口非零且 = 上一条连接端口"才判幽灵拦截;
* peer==0(芯片对端端口提交滞后,Trap 15 同类)不拦截——宁可补 accept
* 也不退回丢中断死等(2026-10-11 验证轮实测:peer==0 拦截把首连接
* 丢中断的原有对账救援也挡死,LIST#1 卡满 20s,比修复前更糟) */
if ((peer != 0U) && (peer == p_sock->last_peer_port)) {
p_sock->lost_conn_seen = 0U;
return;
}
/* 去抖:连续确认满门槛才补 accept(封顶防溢出) */
if (p_sock->lost_conn_seen < NET_LOST_CONN_STABLE_N) {
p_sock->lost_conn_seen++;
}
if (p_sock->lost_conn_seen < NET_LOST_CONN_STABLE_N) {
return;
}
/* 动手前清去抖计数 */
p_sock->lost_conn_seen = 0U;
/* 留痕:丢中断自愈命中 */
DBG_INFO("sock%d lost-CONNECT self-accept (peer=%u)",
(int)p_sock->ch395_sock, peer);
/* 复用 CONNECT 事件的簿记路径(转 ESTABLISHED + 记对端 + 事件回调) */
handle_connect_event(p_sock);
}
/*
* 函数功能:TCP Server 接受新连接(内部版本,必须在 netTask 上下文调用)
* 入口参数:sockfd - 监听 Socket 描述符 int
* 出口参数:addr - 输出:客户端地址结构体指针 struct net_sockaddr *(可为 NULL)
* addrlen - 输入输出:地址结构体长度 int *(可为 NULL)
* 返回值:新连接的 Socket 描述符,失败返回 -1 int
* 限定条件:Socket 处于 LISTENING 状态
* 函数说明:1. 单连接:监听 Socket 自身在 CONNECT 后已转为 ESTABLISHED
* 2. 等待期叠加芯片状态轮询探针(丢 CONNECT 中断自愈,Trap 25)
* 3. CLOSED + 芯片 RX 缓冲有残留数据:接受"已关但可读"连接排空
* (FUN_PARA §5.52 ② 保留现场契约,Trap 25 三次定案)
*/
static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen) {
/* 监听 Socket 控制块 */
net_sock_t *p_listen_sock;
/* 取控制块(sockfd 已在上层校验范围) */
p_listen_sock = &s_net_socks[sockfd];
/* 控制块未在用 → INVAL */
if (!p_listen_sock->in_use) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 先轮询一次:把状态机刷新到芯片侧最新状态 */
net_poll();
/* N-30(Trap 25):监听等待期兼做芯片状态第二信源——丢 CONNECT 中断时
* 去抖补 accept(裸机台架实证:拆链窗口丢中断是数据被吞/425 的主根因,
* 纯事件等待无自愈可能;详见 net_lost_connect_probe 区块注释) */
if (p_listen_sock->state == NET_SOCK_STATE_LISTENING) {
net_lost_connect_probe(p_listen_sock);
}
/* 单连接:本 Socket 自身在 CONNECT 后处于 ESTABLISHED 才表示有可接受的连接
* (含上一步探针补 accept 的命中);其余失同步由 net_reconcile_tick()
* 周期对账(带去抖)兜底 */
if (p_listen_sock->state != NET_SOCK_STATE_ESTABLISHED) {
/* N-30(2026-10-11 手册 §5.23⑤ + 生产时间线定案):
* "closed-but-readable" 接受——STOR 类传输客户端**发完数据立即关数据连接**
* (正常 FTP 行为,Python ftplib sendall 后即 close),芯片按
* FUN_PARA **bit3 SOCKET_CLOSE=1**(`0x08`,§5.52 ②)规格保留 Socket 与 RX 缓冲供 MCU 排空
* (实测 DISCONNECT 后 st=0x05/0x00、recv_len=1027=完整载荷)。
* 若 accept 轮询晚于对端 FIN,盲目 relisten(close+open 清缓冲)会把
* 已到达的数据销毁 → 425(此前 N-30"425/被吞"的主根因,并非芯片杀连接)。
* 处置:芯片 RX 缓冲有数据 → 接受这条"已关但可读"连接,应用经
* net_recv_locked 快路径(无状态门槛)排空,排空后 EOF 自然触发 relisten。 */
int drain_pending = 0;
if (p_listen_sock->state == NET_SOCK_STATE_CLOSED && p_listen_sock->auto_relisten) {
if (ch395f_get_recv_len((uint8_t)sockfd) > 0) {
drain_pending = 1;
DBG_INFO("sock%d closed-with-data accept (drain pending)", sockfd);
} else {
/* 连接已断开(CLOSED)且无残留数据:先把 Socket 重新置回 LISTEN,
* 下一次 accept 即可等待新连接(BSD 风格监听常驻)。
* N-32 P2 修正:**保留本处即时重建** —— 它由消费者调 accept 触发
* (消费者正在等新连接),不是周期性抢先;删掉会把 CLOSED 窗口
* 拉长、导致调用方高频空转 accept(实测 [WD] task stall)。
* 非阻塞:失败由 D10 重试轮询接管 */
(void)net_listener_try(sockfd);
}
}
if (drain_pending == 0) {
/* 当前无连接 → WOULDBLOCK */
return NET_ERR_RETURN_1(NET_ERR_WOULDBLOCK);
}
/* drain_pending:落入下方 accept 返回(对端簿记仍在控制块,排空后 EOF 了结) */
}
/* 填充对端地址(若调用方需要) */
if (addr != NULL && addrlen != NULL) {
/* 强转为 IPv4 地址结构体 */
struct net_sockaddr_in *p_addr_in = (struct net_sockaddr_in *)addr;
/* 地址族 = IPv4 */
p_addr_in->sin_family = NET_AF_INET;
/* 对端端口(主机序 → 网络序) */
p_addr_in->sin_port = net_htons(p_listen_sock->remote_port);
/* 对端 IP(控制块按网络序保存) */
p_addr_in->sin_addr.s_addr = p_listen_sock->remote_ip;
/* 回填结构体长度 */
*addrlen = sizeof(struct net_sockaddr_in);
}
/* 单连接:返回的就是监听 Socket 自身(已转为数据通道) */
return sockfd;
}
/*
* 函数功能:TCP Server 接受新连接(线程安全版本,通过消息队列委托给 netTask)
* 入口参数:sockfd - 监听 Socket 描述符 int
* 出口参数:addr - 输出:客户端地址结构体指针 struct net_sockaddr *(可为 NULL)
* addrlen - 输入输出:地址结构体长度 int *(可为 NULL)
* 返回值:新连接的 Socket 描述符,失败返回 -1 int
* 限定条件:Socket 处于 LISTENING 状态;net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文
* 函数说明:将接受请求放入消息队列,阻塞等待 netTask 处理完毕
*/
int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
/* 消息体(按值拷入队列,netTask 改的是其副本) */
net_msg_t msg;
/* 描述符越界 → BADF */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 消息队列未创建(网络层未初始化)→ 通用错误 */
if (netMsgQueueHandle == NULL) {
return NET_ERR_RETURN_1(NET_ERR);
}
/* 消息类型:接受新连接 */
msg.type = NET_MSG_ACCEPT;
/* 监听 Socket */
msg.sockfd = sockfd;
/* 对端地址指针经 buf 字段携带 */
msg.buf = (void *)addr;
/* addrlen 经 len 字段携带(不回填时记 0) */
msg.len = (addrlen != NULL) ? *addrlen : 0;
/* 无超时(accept 立即完成) */
msg.timeout_ms = 0U;
/* 调用方任务句柄(netTask 凭它回传回执) */
msg.caller = xTaskGetCurrentTaskHandle();
/* 结果占位(netTask 侧覆写实际结果) */
msg.result = -1;
/* 入队:队列满时阻塞调用方直至有空位 */
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
/* 入队失败 → BUSY */
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 阻塞等待 netTask 回执(两段式拾取,防丢失通知) */
msg.result = net_wait_result("acc", sockfd);
/* 成功时回填地址结构体长度供调用方读取 */
if (addrlen != NULL &&addr != NULL) {
*addrlen = sizeof(struct net_sockaddr_in);
}
if (msg.result < 0) {
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
* errno;对外仍保持 BSD 语义——失败一律返回 -1 */
return NET_ERR_RETURN_1(msg.result);
}
/* 返回结果(成功 = 新连接描述符) */
return msg.result;
}
/*
* 函数功能:查询监听 Socket 是否有可接受的连接(纯 RAM 读,不发起 RPC)
* 入口参数:sockfd - 监听 Socket 描述符 int
* 出口参数:无
* 返回值:1 - 有连接可接受;0 - 无(含描述符无效) int
* 限定条件:任意任务上下文;net_init() 已调用
* 函数说明:1. 判据与 net_accept_locked() 一致(单连接模型:监听 Socket 自身在
* CONNECT 后转 ESTABLISHED),故"门控放行"与"accept 会成功"严格等价
* 2. 只读控制块,不入队、不等通知 → 调用方(uiTask 等)不被 netTask 拖住
* 3. 丢 CONNECT 中断由 net_reconcile_tick() 对账自愈,本函数不做自愈
*/
int net_can_accept(int sockfd) {
/* Socket 控制块(纯 RAM 读,可能为 NULL = 描述符越界或未分配) */
const net_sock_t *p_sock = net_get_sock(sockfd);
if (p_sock == NULL) {
return 0;
}
return (p_sock->state == NET_SOCK_STATE_ESTABLISHED) ? 1 : 0;
}
/*
* 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(纯 RAM 读,不发起 RPC)
* 入口参数:sockfd - 监听 Socket 描述符 int
* 出口参数:无
* 返回值:1 - 就绪(LISTENING,或 CLOSED + auto_relisten = 断连待重建态);
* 0 - 未就绪(fd 未分配 / 非监听型 / 正处隔离期以外的不就绪态) int
* 限定条件:任意任务上下文;net_init() 已调用
* 函数说明:1. N-32(P1 不变量 II):取代 net_can_accept()(要求 ESTABLISHED)作为
* ui_net_sink 的 accept 门控。原门控下 :7003 永不进 net_accept →
* net_lost_connect_probe(丢 CONNECT 中断补 accept)对该端口从不运行
* (设计文档 §2.3 末"探针失效")
* 2. 为什么放行 LISTENING:探针与"closed-but-readable 接受"(:1338)
* 只在 net_accept_locked 内部发生,门控挡在外面就永远到不了;
* LISTENING 态 accept 由探测补 accept 或返 WOULDBLOCK,均安全
* 3. 为什么放行 CLOSED + auto_relisten:单连接模型断连后 fd 停在
* CLOSED 等 relisten(D10 非阻塞重试 / 对账④),此窗口内新 SYN
* 可能已被芯片接住(对账②)→ 放行让 net_accept_locked 的状态门
* 自行裁决(含 RX 有数据的 drain_pending 接受)
* 4. 只读控制块,不入队、不等通知 → uiTask 不被 netTask 拖住
* 5. 与 net_can_accept 语义差异是**有意的**:本函数表达"这个监听器
* 还在服役",而非"这一轮一定有连接"
*/
int net_listener_armed(int sockfd) {
/* Socket 控制块(纯 RAM 读,可能为 NULL = 描述符越界或未分配) */
const net_sock_t *p_sock = net_get_sock(sockfd);
if (p_sock == NULL) {
return 0;
}
/* 正常监听态:放行。net_lost_connect_probe(丢 CONNECT 补 accept)与常规
* accept 都发生在 net_accept_locked 内部,门控挡在外面就永远到不了 */
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
return 1;
}
/* 断连待重建态:**仅当芯片 RX 可能仍有残留数据**时放行,让
* net_accept_locked 的 "closed-but-readable" 接受(N-30,对端"发完即 FIN")
* 仍能拿到该连接。
* ⚠ 无残留时必须返回 0 —— 否则调用方会在整个 CLOSED 窗口内每轮都发起
* net_accept(RPC:入队 + xTaskNotifyWait 回执**无上界**)→ 队列积压 →
* 调用方任务喂不上狗。2026-10-12 P2a 台架实测:删掉即时 relisten 后 CLOSED
* 窗口被拉长到 pump 周期 + 隔离期(~100ms),:21 0ms×50 期间出现 48 条
* `[WD] task stall`(mask=0x67/req=0x77,缺 bit4=UI)。
* 重建本身由 net_listener_pump 负责,调用方**无需**在此催促。
* readable 由 net_poll 在 RECV_OK 置位,"对端发完即 FIN"场景下 CLOSED 之后
* 仍为 1(无人清除),可作"可能仍有残留"的纯 RAM 判据(不发起 SPI)。 */
if ((p_sock->state == NET_SOCK_STATE_CLOSED) &&
(p_sock->auto_relisten != 0U) &&
(p_sock->local_port != 0U) &&
(p_sock->readable != 0U)) {
return 1;
}
return 0;
}
/*
* 函数功能:查询 Socket 是否"值得发一次 recv"(纯 RAM 读,不发起 RPC)
* 入口参数:sockfd - Socket 描述符 int
* 出口参数:无
* 返回值:1 - 应发 recv;0 - 当前无数据可读,可跳过本轮 int
* 限定条件:任意任务上下文;net_init() 已调用
* 函数说明:1. ESTABLISHED → 看 readable(net_poll 在 RECV_OK 置位、读空清零)
* 2. 非 ESTABLISHED / 描述符失效一律返回 1:必须让上层真的 recv 一次
* 拿到 EOF/错误并复位自身状态,门掉它会把"连接已不存在"永久藏住
* 3. ⚠ readable 仅由 RECV_OK 中断置位,Trap 22 对账不含"RX 非空但
* readable=0" → 中断丢失时本函数恒返 0,调用方须保留低频兜底 recv
*/
int net_can_read(int sockfd) {
/* Socket 控制块(纯 RAM 读,可能为 NULL = 描述符越界或未分配) */
const net_sock_t *p_sock = net_get_sock(sockfd);
/* 描述符已失效:让 recv 去报 BADF,走调用方既有错误分支重建,勿静默卡死 */
if (p_sock == NULL) {
return 1;
}
/* 连接已非 ESTABLISHED(CLOSED/LISTENING/…):需要一次 recv 了结 EOF/错误 */
if (p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
return 1;
}
return (p_sock->readable != 0U) ? 1 : 0;
}
/*
* 函数功能:TCP Client 发起与服务器的连接(线程安全版本,通过消息队列委托给 netTask)
* 入口参数:sockfd - Socket 描述符 (0~7),必须为已创建的 TCP Socket int
* addr - 服务器地址结构体指针 const struct net_sockaddr *
* addrlen - 地址结构体长度 int >= sizeof(struct net_sockaddr_in)
* 出口参数:无
* 返回值:0 - 连接请求发送成功(异步,需等待 ESTABLISHED) int
* -1 - 连接失败(参数无效、状态错误等) int
* 限定条件:Socket 已通过 net_socket() 创建且 type == NET_SOCK_STREAM;尚未连接
* 函数说明:1. 将连接请求放入消息队列,阻塞等待 netTask 处理完毕
* 2. 实际的 SPI 操作在 netTask 上下文中执行,避免与 net_poll() 的 SPI 访问竞争(否则会破坏 CH395F 寄存器)
* 3. 连接建立需等待 net_poll() 检测到 CONNECT 事件并将状态更新为 ESTABLISHED
*/
int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) {
/* 消息体(按值拷入队列,netTask 改的是其副本) */
net_msg_t msg;
/* 描述符越界 → BADF */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 消息队列未创建(网络层未初始化)→ 通用错误 */
if (netMsgQueueHandle == NULL) {
return NET_ERR_RETURN_1(NET_ERR);
}
/* 服务器地址无效 → INVAL */
if (addr == NULL || addrlen < (int)sizeof(struct net_sockaddr_in)) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 消息类型:发起 TCP 连接 */
msg.type = NET_MSG_CONNECT;
/* 目标 Socket(须为已创建的 TCP Socket) */
msg.sockfd = sockfd;
/* buf/len/flags 在 connect 中不使用(显式置空) */
msg.buf = NULL;
msg.len = 0;
msg.flags = 0;
/* 服务器地址拷入消息(队列按值拷贝,指针传不过去) */
memcpy(&msg.addr, addr, sizeof(struct net_sockaddr_in));
/* 地址长度 */
msg.addrlen = addrlen;
/* 无超时(connect 为异步命令,成败由中断裁定) */
msg.timeout_ms = 0U;
/* 调用方任务句柄(netTask 凭它回传回执) */
msg.caller = xTaskGetCurrentTaskHandle();
/* 结果占位(netTask 侧覆写实际结果) */
msg.result = -1;
/* 入队:队列满时阻塞调用方直至有空位 */
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
/* 入队失败 → BUSY */
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 阻塞等待 netTask 回执(两段式拾取,防丢失通知) */
msg.result = net_wait_result("con", sockfd);
if (msg.result < 0) {
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
* errno;对外仍保持 BSD 语义——失败一律返回 -1 */
return NET_ERR_RETURN_1(msg.result);
}
/* 返回结果(0 = 连接命令已下发,ESTABLISHED 须以状态轮询等待) */
return msg.result;
}
/*
* 函数功能:TCP Client 发起连接(netTask 内部锁定版本,由消息队列委托执行)
* 入口参数:sockfd - Socket 描述符 (0~7),必须为已创建的 TCP Socket int
* addr - 服务器地址结构体指针 const struct net_sockaddr *
* addrlen - 地址结构体长度 int >= sizeof(struct net_sockaddr_in)
* 出口参数:无
* 返回值:0 - 连接请求已发出(异步,成功以 CONNECT 事件为准);-1 - 失败 int
* 限定条件:net_init() 已调用;必须在 netTask 上下文中调用
* 函数说明:1. 校验控制块(in_use、TCP 类型、地址参数)并保存对端地址
* 2. 芯片侧 OPEN(失败重试 3 次、间隔 500ms,见 Trap 09)+ 配置源/目的端口与目的 IP
* 3. 清本 Socket 残留中断状态(丢弃上一条连接遗留的 DISCONNECT)
* 4. 发起 TCP_CONNECT(驱动内已轮询 CMD_STATUS)并置 CONNECTING;
* 命令级被拒立即关 Socket 返 -1,TCP 层成败仍由 CONNECT/TIMEOUT 裁定
*/
static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int addrlen) {
/* Socket 控制块指针 */
net_sock_t *p_sock;
/* 服务器地址(IPv4)强转 */
const struct net_sockaddr_in *p_addr_in;
/* 芯片命令返回状态 */
uint8_t status;
/* 参数检查:描述符范围 */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
/* 越界 → BADF */
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 取控制块 */
p_sock = &s_net_socks[sockfd];
/* 未在用或非 TCP 流类型 → INVAL */
if (!p_sock->in_use || p_sock->type != NET_SOCK_STREAM) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 服务器地址无效 → INVAL */
if (addr == NULL || addrlen < (int)sizeof(struct net_sockaddr_in)) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 地址强转为 IPv4 结构体 */
p_addr_in = (const struct net_sockaddr_in *)addr;
/* 保存远端地址(IP 按网络序、端口转主机序) */
p_sock->remote_ip = p_addr_in->sin_addr.s_addr;
p_sock->remote_port = net_ntohs(p_addr_in->sin_port);
/* 转换 IP 为数组格式(大端/网络字节序,与 ch395f set_des_ip 期望一致,
* 同 Phase2 phase2_tc201 的 test_parse_ip 结果 [192,168,1,2]) */
p_sock->remote_ip_arr[0] = (uint8_t)(p_sock->remote_ip >> 24);
/* 最高字节 → [0](大端序) */
p_sock->remote_ip_arr[1] = (uint8_t)(p_sock->remote_ip >> 16);
p_sock->remote_ip_arr[2] = (uint8_t)(p_sock->remote_ip >> 8);
/* 最低字节 → [3] */
p_sock->remote_ip_arr[3] = (uint8_t)(p_sock->remote_ip);
/* 配置 CH395F Socket:先 OPEN 再写源/目的端口(避免 open 复位寄存器) */
/* 设置协议类型为 TCP */
ch395f_set_proto_type(sockfd, CH395F_PROTO_TYPE_TCP);
/* CH395F 关闭 Socket 后需延时才能重新打开(Trap 09),故失败重试最多 3 次 */
for (int open_try = 0; open_try < 3; open_try++) {
/* 尝试打开芯片侧 Socket */
status = ch395f_open_socket(sockfd);
if (status == CH395F_ERR_SUCCESS) {
/* 打开成功,退出重试 */
break;
}
/* 打开失败先关闭,再延时后重试 */
ch395f_close_socket(sockfd);
/* 等 500ms 芯片沉降后再试(Trap 09) */
osDelay(500);
}
/* 3 次仍失败 → BUSY */
if (status != CH395F_ERR_SUCCESS) {
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 清掉本 Socket 可能残留的中断状态,丢弃上一条连接遗留的 DISCONNECT */
/* 读即清:本 Socket 中断状态寄存器 */
(void)ch395f_get_sock_int_status((uint8_t)sockfd);
/* 读即清:全局中断状态寄存器 */
(void)ch395f_get_glob_int_status_all();
/* 设置本地(源)端口 */
ch395f_set_sour_port(sockfd, p_sock->local_port);
/* 设置目的 IP(大端数组) */
ch395f_set_des_ip(sockfd, p_sock->remote_ip_arr);
/* 设置目的端口 */
ch395f_set_des_port(sockfd, p_sock->remote_port);
/* 诊断:回读目的 IP/端口与 Socket 状态(GET_REMOT_IPP 为小端,故需按小端解析)
* 注意 ch395f_get_socket_status 输出**2 字节**:[0]=Socket 开关状态、[1]=TCP 状态,
* 必须给 2 字节缓冲(曾只给 1 字节 → 栈越界 1 字节) */
{
/* 回读的目的 IP/端口(6 字节:4B IP + 2B 端口,小端) */
uint8_t rip[6];
/* 两字节 Socket 状态(须给 2 字节缓冲) */
uint8_t st[2] = {0, 0};
/* 目的 IP 解析成的 32 位值(小端 → 主机序) */
uint32_t dip;
/* 目的端口 */
uint16_t dp;
/* 回读目的 IP + 端口 */
ch395f_get_remot_ipp((uint8_t)sockfd, rip);
/* 回读 Socket 状态 */
ch395f_get_socket_status((uint8_t)sockfd, st);
/* 小端 IP 组装为主机序 */
dip = ((uint32_t)rip[3] << 24) | ((uint32_t)rip[2] << 16) |
((uint32_t)rip[1] << 8) | rip[0];
/* 小端端口组装 */
dp = ((uint16_t)rip[5] << 8) | rip[4];
/* 留痕:目的地址 + Socket 状态(现场排障) */
DBG_INFO("[CONN] sock%d openOK dest=%d.%d.%d.%d:%d status=0x%02X tcp=0x%02X",
sockfd,
(dip >> 24) & 0xFF, (dip >> 16) & 0xFF,
(dip >> 8) & 0xFF, dip & 0xFF, dp, st[0], st[1]);
}
/* 发起 TCP 连接:驱动内部已按 §9.2.2 ⑧ 轮询 GET_CMD_STATUS(4 s 上限),
* 并非"立即返回"。⚠ §9.2.2 ⑧ 明文:返回 SUCCESS 仅表示**命令**执行成功,
* 不代表 TCP 连接成功;连接结果仍由 CONNECT/TIM_OUT 中断裁定。 */
status = ch395f_tcp_connect(sockfd);
/* BUSY 是 poll 超时的返回值 = 芯片仍在执行,命令并未被拒绝 → 维持原行为,
* 交给 CONNECT/TIM_OUT 裁定,不得当成失败(否则慢芯片会被误杀)。
* 其余非 SUCCESS 均为明确的命令级失败(VAL/ARG/USE/OPEN/IF/MEM/BUF/RTE…):
* 此前返回值被直接丢弃 → 本层停在 CONNECTING 干等 recv 30 s 超时才暴露
* (2026-10-12 审计 B2)。按 §9.2.2 ⑧"再次连接需从 ⑤ 重开"回滚。 */
if ((status != CH395F_ERR_SUCCESS) && (status != CH395F_ERR_BUSY)) {
DBG_ERROR("[CONN] sock%d TCP_CONNECT rejected by chip: 0x%02X", sockfd, status);
(void)ch395f_close_socket((uint8_t)sockfd);
/* N-32(P1 不变量 I):命令级失败同样是"本会话已结束"——此前这里只置
* CLOSED,而 CONNECTING 态从未置 session_dead,挂起的 recv 只能干等
* deadline(B2 审计发现的"停在 CONNECTING 干等 30s 超时才暴露"同源) */
p_sock->session_dead = 1U;
p_sock->state = NET_SOCK_STATE_CLOSED;
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
if (status == CH395F_ERR_BUSY) {
DBG_INFO("[CONN] sock%d TCP_CONNECT still BUSY after poll, await irq", sockfd);
}
/* 状态 → CONNECTING(等待 CONNECT/TIMEOUT 中断裁定) */
p_sock->state = NET_SOCK_STATE_CONNECTING;
/* 连接请求已发出(0) */
return 0;
}
/*
* SPI DMA 单次事务最大有效载荷(取 CH395F 驱动公开常量)
*/
#define NET_DMA_MAX_PAYLOAD ((int)CH395F_SPI_DMA_MAX_PAYLOAD)
/* 单次写入 CH395F 发送 FIFO 的最大字节数:受硬件发送缓冲(CH395F_SEND_BUF_SIZE_BYTES=1KB)
* 与 SPI DMA 载荷(NET_DMA_MAX_PAYLOAD=2KB) 双重限制,以硬件发送缓冲为硬上限(见 Trap 20),
* 否则单次写 2KB/4KB 会写穿 1KB 发送 FIFO。 */
#define NET_SEND_CHUNK_MAX ((int)CH395F_SEND_BUF_SIZE_BYTES < NET_DMA_MAX_PAYLOAD ? \
(int)CH395F_SEND_BUF_SIZE_BYTES : NET_DMA_MAX_PAYLOAD)
/*
* 函数功能:发送数据(内部锁定版本,必须在 netTask 上下文调用)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* buf - 待发送数据缓冲区指针 const void *
* len - 数据长度 int > 0
* 出口参数:无
* 返回值:实际发送字节数(>= 0);-1 表示参数无效或 Socket 未连接 int
* 限定条件:p_sock 非空;Socket 处于 ESTABLISHED 状态;netTask 上下文
* 函数说明:1. 按 NET_SEND_CHUNK_MAX(硬件发送缓冲上限,见 Trap 20)分块写入 CH395F 发送 FIFO
* 2. 发送前等待 send_ready 置位,超时返回已发送字节数
* 3. 每写完一块等待 SENBUF_FREE 中断(send_ready 清零后重新置位,§5.32/§5.23①)
* 4. 发送中途断连(状态非 ESTABLISHED)→ 立即失败并返回已发字节
* (errno=CONNRESET,不再空等超时)
*/
static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
/* 累计实际发送字节数 */
int sent = 0;
/* 当前等待阶段起始时刻(tick) */
uint32_t tick_start;
/* 由控制块指针反推描述符(数组下标) */
int sockfd = (int)(p_sock - s_net_socks);
/* 参数检查:缓冲区无效 → INVAL */
if (buf == NULL || len <= 0) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 未建立连接 → NOTCONN */
if (p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
return NET_ERR_RETURN_1(NET_ERR_NOTCONN);
}
/* 记录起始时刻(用于停滞超时判定) */
tick_start = HAL_GetTick();
/* 分块发送循环:直到全部写入或失败退出 */
while (sent < len) {
/* 剩余待发字节 */
int chunk = len - sent;
/* 单块以硬件发送缓冲为上限(Trap 20:防写穿 1KB FIFO) */
if (chunk > NET_SEND_CHUNK_MAX) {
chunk = NET_SEND_CHUNK_MAX;
}
/* 发送缓冲未空:等待腾出后再写下一块 */
if (!p_sock->send_ready) {
/* 发送中途断连(DISCONNECT/TIMEOUT 已由 net_poll 处理并置状态非 ESTABLISHED):
* 立即失败,不再空等满超时(旧行为:白等 1s 后报 TIMEDOUT) */
if (p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
(void)net_set_errno(NET_ERR_CONNRESET);
break;
}
/* 停滞超时:NET_SEND_TIMEOUT_MS 后 send_ready 仍未置位 */
if (NET_SEND_TIMEOUT_MS > 0 &&
(HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) {
DBG_ERROR("sock%d send stall: send_ready not set (%d/%d)", sockfd, sent, len);
(void)net_set_errno(NET_ERR_TIMEDOUT);
break;
}
/* 轮询一次(更新 send_ready/状态),下一轮再试 */
net_poll();
continue;
}
/* 本块写入芯片发送 FIFO */
ch395f_write_send_buf(sockfd, (uint8_t *)buf + sent, (uint16_t)chunk);
/* 标记发送缓冲占用中 */
p_sock->send_ready = 0;
/* 累计发送偏移 */
sent += chunk;
/* 累计发送字节统计 */
p_sock->send_bytes += (uint32_t)chunk;
#if defined(TEST_LATENCY_PROBE)
/* L3 探针起点:发送缓冲写入刚完成
* (样本 = 写缓冲 → SENBUF_FREE 等待,见 链路延迟测试说明 §3 L3) */
uint32_t c_l3 = lat_cycles_now();
#endif
/* 重置本等待阶段计时 */
tick_start = HAL_GetTick();
/* 等待本块发完(SENBUF_FREE 中断重新置 send_ready) */
while (!p_sock->send_ready) {
/* 发送中途断连:立即失败(同外层,避免白等超时) */
if (p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
(void)net_set_errno(NET_ERR_CONNRESET);
break;
}
/* 轮询:处理 SEND_OK/SENBUF_FREE 中断 */
net_poll();
/* 等待超时:本块放弃(外层统一处理) */
if (NET_SEND_TIMEOUT_MS > 0 &&
(HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) {
break;
}
}
#if defined(TEST_LATENCY_PROBE)
/* 探针已武装:累计本段等待样本 */
if (lat_probe_armed() != 0U) {
lat_probe_add_seg("chunk_wait", lat_cycles_diff_us(c_l3));
}
#endif
}
/* 发送未完成 = 短写 */
if (sent < len) {
/* 短写告警:调用方必须检查返回值。静默截断会让对端永久等待剩余字节
* (2026-09-21 实证:UI 推流应答被截断 → 客户端反复判定"全程无字节"失联)。 */
DBG_ERROR("sock%d short send %d/%d (errno=%d)", sockfd, sent, len,
s_net_errno_slots[net_errno_slot()].err);
}
/* 返回实际发送字节数(调用方必须检查,勿只看非负) */
return sent;
}
/*
* 函数功能:接收数据(内部锁定版本,必须在 netTask 上下文调用)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位,NET_MSG_DONTWAIT 表示非阻塞 int
* timeout_ms - 本次等待预算(0 = 默认 NET_RECV_TIMEOUT_MS) uint32_t
* caller - 调用方任务句柄(挂起请求的完成通知目标) TaskHandle_t
* 出口参数:buf - 填充接收到的数据
* 返回值:实际接收字节数(> 0);0 表示对端关闭;-1 表示出错/非阻塞无数据 int
* NET_PENDING_REGISTERED 表示已登记挂起请求(分派据此跳过即时通知)
* 限定条件:p_sock 非空;netTask 上下文
* 函数说明:1. 数据已在(快速路径)→ 立即读出返回,行为与旧实现一致
* 2. 非阻塞模式无数据立即返回 -1(NET_ERR_WOULDBLOCK)
* 3. 无数据 + 阻塞等待 → 登记挂起请求(L3 pending 化,E-02 结构演进)
* 4. netTask 零占用立即返回;完成条件(数据/到点/断连)由 net_pending_tick 按主循环节拍检查
* 5. 并通知 caller;关闭/网络重建/陈旧重同步由对应路径显式了结(取消必通知)
* 6. CLOSED 状态语义(排空 + EOF + 自动重监听)逐项保留在 tick 内
*/
static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
uint32_t timeout_ms, TaskHandle_t caller) {
/* 由控制块指针反推描述符(数组下标) */
int sockfd = (int)(p_sock - s_net_socks);
/* 参数检查:缓冲区无效 → INVAL */
if (buf == NULL || len <= 0) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 超时来源:调用方指定(单次预算)优先,否则用默认值(均作为挂起截止) */
uint32_t to_ms = (timeout_ms != 0U) ? timeout_ms : (uint32_t)NET_RECV_TIMEOUT_MS;
/* 先轮询一次:刷新状态机与可读标志 */
net_poll();
/* 查询芯片侧接收缓冲可用字节数 */
int recv_len = ch395f_get_recv_len(sockfd);
/* 快速路径:数据已在 */
if (recv_len > 0) {
/* 本次读取量 = 缓冲容量与芯片可用量的较小值 */
int read_len = (recv_len > len) ? len : recv_len;
/* 读出到调用方缓冲区 */
ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len);
/* 累计接收字节统计 */
p_sock->recv_bytes += (uint32_t)read_len;
/* 已消费数据(注:部分读受 Trap 13 限制,下一次 RECV_OK 会重新置位) */
p_sock->readable = 0;
/* 返回本次读出字节数 */
return read_len;
}
/* 非阻塞模式(DONTWAIT) */
if (flags & NET_MSG_DONTWAIT) {
/* 非阻塞且当前无数据:若连接已断开则视为 EOF(0) 并自动重监听,
* 否则返回 WOULDBLOCK(-1) 表示暂时无数据 */
/* 连接已断开(CLOSED)**或**本会话已结束(session_dead):按 EOF 处理。
* 判据从纯 `state == CLOSED` 放宽为 `|| session_dead`(N-32 P1 不变量 I):
* 抢先 relisten 后 state 已是 LISTENING 而会话确实已结束,旧判据在此
* 恒返 WOULDBLOCK → EOF 永久失踪(新连接被旧会话静默吸收)。
* 有数据时走上方快路径(recv_len>0 优先),本判据只在"无数据"时生效,
* 故不会把新连接的起始数据误判为 EOF。 */
if ((p_sock->state == NET_SOCK_STATE_CLOSED) || (p_sock->session_dead != 0U)) {
/* 启用自动重监听:先把监听器重新置回 LISTEN。
* ⚠ 内层 `state == CLOSED` 门是**必需的**,不是冗余:
* `net_listener_try` 无状态门,对已 LISTENING 的 fd 再 open+listen
* 会让 ch395f_open_socket 返 USE,其失败分支会 ch395f_close_socket
* —— 把正听着新 SYN 的 Socket 当场关掉(U12 同源事故)。
* session_dead + 已回 LISTENING = 重建已由别处完成,这里只报 EOF。
* N-32 P2 修正(2026-10-12 台架实证):**保留本处即时重建**。
* 它是"消费者动作触发"(消费者正在等这条连接的结局),不是周期性
* "抢先";删掉它会把 CLOSED 窗口从 ~0 拉长到 pump 周期+隔离期
* (~100ms),期间调用方高频发起 net_accept(RPC 无上界)→
* `[WD] task stall`(实测 uis 往返 1063ms)。周期性抢先由 pump 收敛即可。 */
if ((p_sock->state == NET_SOCK_STATE_CLOSED) && (p_sock->auto_relisten != 0U)) {
/* 非阻塞:失败由 D10 重试轮询接管 */
(void)net_listener_try(sockfd);
}
/* EOF:返回 0(非错误) */
return 0;
}
/* 暂时无数据 → WOULDBLOCK */
return NET_ERR_RETURN_1(NET_ERR_WOULDBLOCK);
}
/* 无数据 + 阻塞等待:登记挂起请求,立即返回(netTask 任何时刻都不被占用)。
* 调用方在自己上下文睡任务通知(LwIP netconn 模型);等待期间 netTask
* 继续主循环(事件泵 + 队列),其他消费者零头阻塞。 */
/* 登记挂起请求(L3):返回值即登记结果 */
return net_pending_register(sockfd, buf, len, to_ms, caller);
}
/*
* 函数功能:关闭 Socket(内部锁定版本,必须在 netTask 上下文调用)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:0 - 关闭成功 int
* 限定条件:p_sock 非空;netTask 上下文
* 函数说明:1. 调用 ch395f_close_socket 关闭 CH395F 硬件 Socket
* 2. 清空整个控制块(memset 0),释放该 Socket 资源
*/
/* ===== L3 挂起 recv 请求的实现(一期,2026-09-22) =====
* 完成条件按 ①数据 ②到点 ③断连 顺序检查(逐项对齐旧自旋循环语义)
* ① 数据通道(ESTABLISHED/UDP_OPEN)内 readable 门控 SPI 读——常见无数据路径零额外 SPI 事务
* (INT# 电平触发链保证缓冲有数据时每轮重读 SINT 重位置位)
* ② 到点(与状态无关,旧自旋亦在任何状态下检查超时)→ NET_ERR_TIMEDOUT
* ③ CLOSED:先排空(Trap 13:FIN 可能先于数据)→ 有数据按数据完成,否则 EOF(0) + 自动重监听
* 其他状态(CONNECTING/LISTENING/…)保持等待状态迁移(CONNECT→ESTABLISHED 是合法路径;
* 陈旧 resync 由显式取消处理)——不做状态推断取消,避免误杀正在建链的等待
* 看门狗兜底:到点后仍未了结 = 完成逻辑缺位 → 强制了结留痕
*/
/*
* 函数功能:通知调用方并清除本 fd 的挂起请求(所有完成/取消路径的公共出口)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* result - 32 位结果原值(>0 字节数 / 0 EOF / 负错误码) int
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文;sockfd 合法且挂起在位(否则静默返回,防重复通知)
* 函数说明:通知值 = 结果本身(与消息分派同一回传通道,调用方 net_recv/net_recv_to
* 的映射逻辑不变);失败结果同步 errno 保持 netTask 上下文一致性
*/
static void net_pending_complete(int sockfd, int result) {
/* 本 fd 挂起请求指针 */
net_pending_t *p;
/* 描述符越界:静默返回(防重复通知) */
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return;
}
/* 取挂起请求 */
p = &s_net_pending[sockfd];
/* 无在位挂起(已了结/已取消):静默返回 */
if (p->active == 0U) {
return;
}
/* 失败结果:记入 netTask 自己的错误码槽位(E-03;不再写入跨任务共享全局) */
if (result < 0) {
(void)net_set_errno(result);
}
/* 覆盖式写入通知值 = 结果本身(32 位原值) */
xTaskNotify(p->caller, (uint32_t)result, eSetValueWithOverwrite);
/* 清除挂起状态 */
p->active = 0U;
}
/*
* 函数功能:登记一个挂起 recv 请求(net_recv_locked 的阻塞无数据路径)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* buf - 调用方接收缓冲(须保持有效至完成) void *
* len - 缓冲容量 int
* to_ms - 本次等待预算(截止 = 登记时刻 + to_ms) uint32_t
* caller - 调用方任务句柄(完成通知目标) TaskHandle_t
* 出口参数:无
* 返回值:NET_PENDING_REGISTERED - 登记成功(分派跳过即时通知)
* -1 - 拒绝(同 fd 已有挂起 / 执行任务自身发起)
* 限定条件:netTask 上下文
* 函数说明:每 fd 至多一个挂起(禁止覆盖——覆盖丢失前调用方通知 = 该任务永久挂死);
* 执行任务自身发起的阻塞等待无他人可完成(tick 跑在执行任务内)→ 自等自
* 通知 = 死锁,必须拒绝(netTask 上下文调用方须用 DONTWAIT)
*/
static int net_pending_register(int sockfd, void *buf, int len,
uint32_t to_ms, TaskHandle_t caller) {
/* 挂起请求指针 */
net_pending_t *p;
/* 登记时刻(tick) */
uint32_t now;
/* 描述符越界 → BADF */
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 取挂起请求 */
p = &s_net_pending[sockfd];
/* 同 fd 已有挂起:拒绝(覆盖将丢失前通知 = 该任务永久挂死) */
if (p->active != 0U) {
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* netTask 自身发起:无他人可完成(tick 跑在 netTask 内)→ 拒绝(自等死锁) */
if ((caller == NULL) || (caller == s_net_task_handle)) {
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 当前 tick(截止/看门狗基准) */
now = HAL_GetTick();
/* 标记挂起在位 */
p->active = 1U;
/* 调用方接收缓冲(须保持有效至完成) */
p->buf = buf;
/* 缓冲容量 */
p->len = len;
/* 截止时刻 = 登记时刻 + 预算 */
p->deadline = now + to_ms;
/* 登记时刻(看门狗基准) */
p->registered = now;
/* 完成通知目标 */
p->caller = caller;
/* 登记成功:分派跳过即时通知 */
return NET_PENDING_REGISTERED;
}
/*
* 函数功能:挂起 recv 完成检查(netTask 每轮 net_process_messages 调用一次,非阻塞)
* 入口参数:无
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文
* 函数说明:1. 逐 fd 检查完成条件:数据到达 / 到点(deadline)/ 断连
* 2. 完成即经 net_pending_complete 通知并清除;看门狗(NET_PENDING_WATCHDOG_MS)超时强制了结
*/
static void net_pending_tick(void) {
/* 当前 tick */
uint32_t now = HAL_GetTick();
/* Socket 循环索引 */
int i;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
/* 本 fd 挂起请求 */
net_pending_t *p = &s_net_pending[i];
/* 本 fd 控制块 */
net_sock_t *p_sock = &s_net_socks[i];
/* 无挂起请求:跳过 */
if (p->active == 0U) {
continue;
}
/* fd 已关闭 */
if (p_sock->in_use == 0U) {
/* fd 已关闭(正常路径在 close 内先了结;此处兜底,防任何路径漏结) */
net_pending_complete(i, NET_ERR_CONNRESET);
continue;
}
/* ① 数据到达(数据优先,与旧自旋一致) */
if ((p_sock->state == NET_SOCK_STATE_ESTABLISHED) ||
(p_sock->state == NET_SOCK_STATE_UDP_OPEN)) {
/* 可读标志置位 */
if (p_sock->readable != 0U) {
/* 查询芯片侧可用字节数 */
int avail = ch395f_get_recv_len((uint8_t)i);
/* 有数据 */
if (avail > 0) {
/* 本次读取量 = min(可用, 缓冲容量) */
int rd = (avail > p->len) ? p->len : avail;
/* 读入调用方缓冲区 */
ch395f_read_recv_buf((uint8_t)i, (uint8_t *)p->buf, (uint16_t)rd);
/* 累计接收字节统计 */
p_sock->recv_bytes += (uint32_t)rd;
/* 数据已消费,清可读标志 */
p_sock->readable = 0U;
/* 了结:以字节数通知 */
net_pending_complete(i, rd);
continue;
}
/* 虚假标志:缓冲无数据,继续等待 */
p_sock->readable = 0U;
}
}
/* ② 到点(tick 差值按有符号比较,抗回绕) */
if ((int32_t)(now - p->deadline) >= 0) {
/* 了结:超时(错误码由 net_pending_complete 记入本任务槽位) */
net_pending_complete(i, NET_ERR_TIMEDOUT);
continue;
}
/* ③ 断连:排空 → 数据或 EOF(0) + 自动重监听。
* 判据同 net_recv_locked(N-32 P1 不变量 I):`state == CLOSED ||
* session_dead`。只判 CLOSED 时,抢先 relisten 后 ①(要求 ESTABLISHED)
* 与 ③(要求 CLOSED) 两头落空 → 挂起的 recv 只能干等 ② deadline 超时
* (lftpd 阻塞 recv 的"消费者卡死"原型)。 */
if ((p_sock->state == NET_SOCK_STATE_CLOSED) || (p_sock->session_dead != 0U)) {
/* 查询残留字节数 */
int rem = ch395f_get_recv_len((uint8_t)i);
/* 有残留数据:按数据完成 */
if (rem > 0) {
/* 本次读取量 = min(残留, 缓冲容量) */
int rd = (rem > p->len) ? p->len : rem;
/* 读入调用方缓冲区 */
ch395f_read_recv_buf((uint8_t)i, (uint8_t *)p->buf, (uint16_t)rd);
/* 累计接收字节统计 */
p_sock->recv_bytes += (uint32_t)rd;
/* 数据已消费,清可读标志 */
p_sock->readable = 0U;
/* 了结:以字节数通知 */
net_pending_complete(i, rd);
} else {
/* 启用自动重监听:先把监听器重新置回 LISTEN。
* ⚠ 内层 `state == CLOSED` 门同 net_recv_locked:session_dead
* 但已回 LISTENING 时重建已由别处完成,再 open+listen 会因
* USE 错误走进关闭分支、关掉正听着的 Socket(U12 同源)。
* N-32 P2 修正:**保留本处即时重建** —— 它只在有**挂起 recv**
* 时到达(`p->active != 0`),属"消费者在等这条连接结局"的
* 消费者驱动路径,不是周期性抢先(同 net_recv_locked 的理由)。 */
if ((p_sock->state == NET_SOCK_STATE_CLOSED) &&
(p_sock->auto_relisten != 0U)) {
/* 非阻塞:失败由 D10 重试轮询接管(N-32 §10:入口收敛到 net_listener_try) */
(void)net_listener_try(i);
}
/* Trap 23 待办 #3:异常释放(TIMEOUT/RST)不是正常 EOF(对端未发正常 FIN)
* → 以连接重置了结:应用(lftpd STOR)对异常结束回 5xx 而非误报 226;
* 正常 FIN(DISCONNECT)仍回 0(在途 FIN 与正常完成不可区分——协议边界,见 Trap 23 记录) */
/* 异常释放:以连接重置了结(回 5xx,不误报 226) */
if (p_sock->released_abnormal != 0U) {
/* 异常释放(错误码由 net_pending_complete 记入本任务槽位) */
net_pending_complete(i, NET_ERR_CONNRESET);
} else {
/* 正常 EOF:以 0 了结 */
net_pending_complete(i, 0);
}
}
continue;
}
/* 其他状态:保持等待(见本区块头注释) */
/* ④ 看门狗:到点 + 裕量仍未了结 = 完成逻辑缺位 → 强制了结并留痕 */
if ((int32_t)(now - p->deadline) >= (int32_t)NET_PENDING_WATCHDOG_MS) {
/* 强制了结留痕(超截止时刻的毫秒数) */
DBG_ERROR("sock%d pending recv stuck %ums past deadline (force settle)",
i, (unsigned)((int32_t)now - (int32_t)p->deadline));
/* 强制了结:超时(错误码由 net_pending_complete 记入本任务槽位) */
net_pending_complete(i, NET_ERR_TIMEDOUT);
}
}
}
/*
* 函数功能:查询本 fd 是否有在途挂起 recv(L3 pending)—— 宿主能力,供 net_listener 用
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 出口参数:无
* 返回值:1 - 有挂起;0 - 无(含描述符越界) int
* 限定条件:任意上下文(纯 RAM 读,不发起 RPC)
* 函数说明:1. N-32 §10 模块化:net_listener_retry_tick 用它跳过"重开 Socket
* 会摧毁在途连接"的 fd(L3 保险,原为直接读 s_net_pending[i].active)
* 2. 声明在 net_listener.h(本模块提供、监听器模块消费)
*/
int net_pending_active(int sockfd) {
/* 描述符越界:视为无挂起 */
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return 0;
}
return (s_net_pending[sockfd].active != 0U) ? 1 : 0;
}
/*
* 函数功能:清除本 fd 的挂起请求(不通知;用于"新 fd 不得继承"与 close 后的幂等清理)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 出口参数:无
* 返回值:无
* 限定条件:无
* 函数说明:1. 静默清理不发通知:close 路径先经 settle 通知,本调用仅保证状态归零(幂等)
* 2. 新建 fd 时调用,防复用槽位继承上一条连接的挂起请求
*/
static void net_pending_clear(int sockfd) {
/* 描述符越界:直接返回 */
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return;
}
/* 挂起状态清零(静默,不发通知) */
s_net_pending[sockfd].active = 0U;
}
/*
* 函数功能:取消本 fd 的挂起请求并通知调用方(显式路径:陈旧 resync 等)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* err - 通知的错误码(如 NET_ERR_CONNRESET) int
* 出口参数:无
* 返回值:无(无在位挂起时静默返回)
* 限定条件:netTask 上下文
* 函数说明:经 net_pending_complete 以指定错误码通知调用方(取消必通知,防挂死)
*/
static void net_pending_cancel(int sockfd, int err) {
/* complete 内部校验范围与在位(未在位则静默返回) */
net_pending_complete(sockfd, err);
}
/*
* 函数功能:取消全部挂起请求并逐一通知(网络重建路径)
* 入口参数:err - 通知的错误码 int
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文;须在控制块清零与芯片复位之前调用
* 函数说明:网络重建(net_stack_bring_up)前调用,保证所有在位调用方均被唤醒
*/
static void net_pending_cancel_all(int err) {
/* 逐 fd 遍历 */
for (int i = 0; i < NET_MAX_SOCKETS; i++) {
/* 取消并通知(未在位则静默跳过) */
net_pending_complete(i, err);
}
}
/*
* 函数功能:关闭前了结本 fd 的挂起 recv(per-fd 串行:先完成、再关闭)
* 入口参数:p_sock - 将被关闭的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无(无在位挂起时静默返回)
* 限定条件:netTask 上下文;须在 ch395f_close_socket 之前调用
* 函数说明:先排空尝试——关闭不丢已到达数据(芯片缓冲在 close 前可读);
* 有数据按字节数通知,否则取消(NET_ERR_CONNRESET)
*/
static void net_pending_settle_close(net_sock_t *p_sock) {
/* 由控制块指针反推描述符 */
int sockfd = (int)(p_sock - s_net_socks);
/* 通知用结果值(默认为取消) */
int val;
/* 芯片 RX 可用字节数 */
int avail;
/* 无在位挂起:静默返回 */
if (s_net_pending[sockfd].active == 0U) {
return;
}
/* 默认结果 = 取消(CONNRESET) */
val = NET_ERR_CONNRESET;
/* 查询芯片 RX 可用字节(缓冲内数据在 close 前可读) */
avail = ch395f_get_recv_len((uint8_t)sockfd);
/* 数据已到达:排空,关闭不丢数据 */
if (avail > 0) {
/* 本次读取量 = min(可用, 缓冲容量) */
int rd = (avail > s_net_pending[sockfd].len) ? s_net_pending[sockfd].len : avail;
/* 读入调用方缓冲区 */
ch395f_read_recv_buf((uint8_t)sockfd, (uint8_t *)s_net_pending[sockfd].buf, (uint16_t)rd);
/* 累计接收字节统计 */
p_sock->recv_bytes += (uint32_t)rd;
/* 数据已消费,清可读标志 */
p_sock->readable = 0U;
/* 有数据:以字节数通知 */
val = rd;
}
/* 了结(必通知) */
net_pending_complete(sockfd, val);
}
/*
* 函数功能:关闭 Socket(内部版本,不经过消息队列)
* 入口参数:p_sock - 要关闭的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:0 - 关闭成功 int
* 限定条件:必须在 netTask 上下文中调用;p_sock 有效
* 函数说明:1. 关闭 CH395F Socket,排空上一条连接残留的 DISCONNECT 中断
* 2. 等待芯片回到 CLOSED 状态(超时 500ms),再清零控制块
*/
static int net_close_locked(net_sock_t *p_sock) {
/* 由控制块指针反推描述符 */
int sockfd = (int)(p_sock - s_net_socks);
/* 起始时刻(等芯片 CLOSED 的 500ms 上限基准) */
uint32_t tick = HAL_GetTick();
/* L3:先了结本 fd 的挂起 recv(排空/取消 + 必通知),再关芯片 Socket
* ——关闭不得让等待中的调用方永久悬挂 */
net_pending_settle_close(p_sock);
/* 关闭芯片侧 Socket */
ch395f_close_socket(sockfd);
/* 排空上一条连接残留的 DISCONNECT 中断(单连接模式仅 Socket 0;复用同一 Socket 时
* 旧 DISCONNECT 在 FIN/ACK 完成后才上报,不清除会被下一连接误判为"刚建立就断开")
* 等待芯片回到 CLOSED 并清掉 Socket/全局中断状态,避免事件串扰 */
do {
/* [0]=Socket 状态 [1]=TCP 状态(该 API 输出 2 字节) */
uint8_t st[2] = {0, 0};
/* 读芯片状态(两字节) */
ch395f_get_socket_status((uint8_t)sockfd, st);
/* 读即清:本 Socket 中断状态 */
(void)ch395f_get_sock_int_status((uint8_t)sockfd);
/* 读即清:全局中断状态 */
(void)ch395f_get_glob_int_status_all();
/* 芯片已回 CLOSED:结束 */
if (st[0] == CH395F_SOCKET_CLOSED) {
break;
}
/* 等待超时(500ms):停止等待继续 */
if (HAL_GetTick() - tick > 500) {
break;
}
/* 等 5ms 再查(循环受 500ms 上限约束) */
osDelay(5);
} while (1);
/* 清零控制块(memset 0 = 释放本 fd 全部状态) */
/* N-30(Trap 25):幽灵甄别基准跨 fd 释放保留(幽灵属芯片 Socket 级状态,
* 与 fd 复用无关——close 后新 listen 落在同一芯片 Socket 上仍可能撞幽灵) */
uint16_t keep_peer = p_sock->remote_port;
memset(p_sock, 0, sizeof(net_sock_t));
p_sock->last_peer_port = keep_peer;
/* 关闭即取消该 fd 的待重试(D10 重试队列) */
net_listener_clear(sockfd);
/* 关闭即清除该 fd 的挂起状态(L3;幂等) */
net_pending_clear(sockfd);
/* 关闭成功 */
return 0;
}
/*
* 函数功能:发送数据(线程安全版本,通过消息队列委托给 netTask)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 待发送数据缓冲区指针(调用返回前须保持有效,本函数不拷贝数据) const void *
* len - 数据长度(字节),> 0 int
* flags - 标志位,当前实现忽略(保留 BSD send 接口兼容) int
* 出口参数:无
* 返回值:实际发送字节数 int >= 0 || -1
* -1 - 发送失败(参数非法 / 消息队列入队失败 / netTask 侧发送错误) int
* 限定条件:1. 必须在【非 netTask】上下文中调用(应用/测试任务);若在 netTask 内调用会自己等自己 → 死锁
* (netTask 阻塞在 xTaskNotifyWait 而无人处理消息);netTask 内请直接用 net_send_locked()
* 2. Socket 须处于 ESTABLISHED 状态
* 3. net_init() + MX_FREERTOS_Init() 已执行,netMsgQueueHandle 已由 net_init 创建
* 函数说明:1. 所有 CH395F SPI 访问只允许在 netTask 内串行执行(见 Trap 16)
* 2. 本函数不直接触碰 SPI,而是把发送请求打包成 net_msg_t 投递到 netMsgQueueHandle
* 3. netTask 的 net_process_messages() 取出并在 netTask 上下文调用 net_send_locked() 真正写 CH395F
* 4. 调用方经 FreeRTOS TaskNotify 阻塞等待处理结果,对外呈现"同步发送"语义
* 5. 函数返回 = 本次发送已被 netTask 处理完毕(成功或失败)
*/
int net_send(int sockfd, const void *buf, int len, int flags) {
/* flags 当前未使用,显式 void 以消除未用参数告警 */
(void)flags;
/* 一次性消息结构体;入队后由 netTask 取出处理(注意只传指针不拷贝数据) */
net_msg_t msg;
/* ---- 1. 参数合法性检查 ---- */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
/* 描述符越界 */
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* ---- 2. 运行环境检查:消息队列须已创建(网络层已初始化) ---- */
if (netMsgQueueHandle == NULL) {
/* 网络层未初始化 */
return NET_ERR_RETURN_1(NET_ERR);
}
/* ---- 3. 组装消息:把本次 send 的全部上下文填入 msg ---- */
/* 消息类型,netTask 据此分派到 net_send_locked */
msg.type = NET_MSG_SEND;
/* 目标 Socket */
msg.sockfd = sockfd;
/* 数据缓冲(队列仅拷贝指针,buf 须有效至函数返回) */
msg.buf = (void *)buf;
/* 待发送字节数 */
msg.len = len;
/* 实际标志位(忽略入参 flags) */
msg.flags = 0;
/* 发送不使用超时字段 */
msg.timeout_ms = 0U;
/* 记录调用任务句柄,供 netTask 回执时精准通知 */
msg.caller = xTaskGetCurrentTaskHandle();
/* 预置失败;netTask 处理成功后会改写为实际发送字节数 */
msg.result = -1;
/* ---- 4. 入队:将消息投递给 netTask,osWaitForever 表示队列满时阻塞调用方 ---- */
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
/* 入队失败(队列异常等极端情况) */
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* ---- 5. 阻塞等待 netTask 处理完毕:netTask 调 net_send_locked 后通过
* xTaskNotify() 唤醒本任务,并把结果(实际发送字节数或 -1)作为通知值传回 ---- */
/* 两段式等待(防丢失通知,见 net_wait_result) */
msg.result = net_wait_result("snd", sockfd);
/* ---- 6. 结果回传:负数视为错误并设置线程级错误码;否则返回实际发送字节数 ---- */
if (msg.result < 0) {
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
* errno;对外仍保持 BSD 语义——失败一律返回 -1 */
return NET_ERR_RETURN_1(msg.result);
}
/* 返回实际发送字节数 */
return msg.result;
}
/*
* 函数功能:接收数据(线程安全版本,通过消息队列委托给 netTask)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位,0=阻塞,NET_MSG_DONTWAIT=非阻塞 int
* 出口参数:无
* 返回值:实际接收字节数 int >= 0 || 0 || -1
* 0 - 对端关闭连接 int
* -1 - 接收失败(参数无效、队列满等) int
* 限定条件:Socket 处于 ESTABLISHED 状态;net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文
* 函数说明:将接收请求放入消息队列,阻塞等待 netTask 处理完毕并返回结果。netTask 内请使用 net_recv_sock()
*/
int net_recv(int sockfd, void *buf, int len, int flags) {
/* 一次性消息结构体(队列按值存结构副本) */
net_msg_t msg;
/* 描述符越界 → BADF */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 消息队列未创建(网络层未初始化)→ 通用错误 */
if (netMsgQueueHandle == NULL) {
return NET_ERR_RETURN_1(NET_ERR);
}
/* 消息类型:接收数据 */
msg.type = NET_MSG_RECV;
/* 目标 Socket */
msg.sockfd = sockfd;
/* 接收缓冲(队列仅拷贝指针,缓冲须有效至调用返回) */
msg.buf = buf;
/* 缓冲容量 */
msg.len = len;
/* 标志:0=阻塞,NET_MSG_DONTWAIT=非阻塞 */
msg.flags = flags;
/* 0 = 默认超时预算(netTask 侧用 NET_RECV_TIMEOUT_MS) */
msg.timeout_ms = 0U;
/* 调用方任务句柄(netTask 凭它回传回执) */
msg.caller = xTaskGetCurrentTaskHandle();
/* 结果占位(netTask 侧覆写实际结果) */
msg.result = -1;
/* 入队:队列满时阻塞调用方直至有空位 */
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
/* 入队失败 → BUSY */
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 阻塞等待 netTask 回执(两段式拾取,防丢失通知) */
msg.result = net_wait_result("rcv", sockfd);
if (msg.result < 0) {
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
* errno;对外仍保持 BSD 语义——失败一律返回 -1 */
return NET_ERR_RETURN_1(msg.result);
}
/* 返回结果(接收字节数;0 = EOF) */
return msg.result;
}
/*
* 函数功能:接收数据(带调用方指定超时的阻塞版,用于分片有界读)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf / len - 接收缓冲与容量 void * / int > 0
* timeout_ms - 单次占用 netTask 的最长等待(毫秒,>0) uint32_t
* 出口参数:无
* 返回值:>0 字节数;0 = 对端关闭(EOF);-1 = 真错误(errno 可查);**-2 = 超时无数据**
* 限定条件:非 netTask 上下文;net 层已初始化
* 函数说明:1. E-02 修复配套接口:lftpd 等"等对端下一行/下一块数据"的调用方
* 若用默认 30s 阻塞 recv,会把 netTask(全部 socket 操作的唯一执行者)长期占住
* 2. 导致其余任务的网络操作整体冻结(M3 实测一次空闲 FTP 登录冻结远端显示 17.7s)
* 3. 用本接口把等待切成 ≤timeout_ms 的片,调用方自行累计总预算:总超时语义不变、单次占用有界
* 4. 超时用独立返回码 -2(而非靠 errno 区分)→ 无跨任务 errno 竞态
*/
int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms) {
/* 一次性消息结构体:队列按值存结构副本(仅指针、不拷数据),入队后由 netTask 取出处理 */
net_msg_t msg;
/* ---- 1. 参数合法性检查 ---- */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
/* 描述符越界 */
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* ---- 2. 运行环境检查:消息队列须已创建(网络层已初始化) ---- */
if (netMsgQueueHandle == NULL) {
/* 网络层未初始化 */
return NET_ERR_RETURN_1(NET_ERR);
}
/* ---- 3. 分片长度检查:timeout_ms = 0 在 netTask 侧意味着"改用默认
* NET_RECV_TIMEOUT_MS(30 秒)",恰是本接口要规避的长阻塞,直接拒绝 ---- */
if (timeout_ms == 0U) {
/* 分片长度必须 > 0 */
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* ---- 4. 组装消息:把本次分片读的全部上下文填入 msg ---- */
/* 消息类型,netTask 据此分派到 net_recv_locked */
msg.type = NET_MSG_RECV;
/* 目标 Socket */
msg.sockfd = sockfd;
/* 接收缓冲(队列仅拷贝指针,buf 须有效至本函数返回) */
msg.buf = buf;
/* 缓冲容量:单次读取字节数上限(允许部分读) */
msg.len = len;
/* 阻塞式:netTask 侧轮询至"有数据"或"本片超时"(勿置 NET_MSG_DONTWAIT) */
msg.flags = 0;
/* 本片预算:netTask 最多等待 timeout_ms,保证对 netTask 的占用有界(E-02) */
msg.timeout_ms = timeout_ms;
/* 记录调用任务句柄,netTask 处理后经 xTaskNotify() 精准回执 */
msg.caller = xTaskGetCurrentTaskHandle();
/* 预置失败(仅防御值;真实结果经任务通知值回传,见步骤 6) */
msg.result = -1;
/* ---- 5. 入队:将消息投递给 netTask,osWaitForever 表示队列满时阻塞调用方 ---- */
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
/* 入队失败(队列异常等极端情况) */
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* ---- 6. 阻塞等待 netTask 处理完毕:netTask 执行 net_recv_locked 后经 xTaskNotify()
* 把结果(字节数 / 0 / 负错误码)以 32 位通知值回传本任务
* net_msg_t 按值拷入队列,result 字段不会回来——通知值是唯一的真实回传通道(勿加"回传用"字段) ---- */
/* 两段式等待:零等待拾取 + 阻塞,防丢失通知(见 net_wait_result) */
msg.result = net_wait_result("rcv", sockfd);
/* ---- 7. 回传结果(三分支:分片超时 / 真错误 / 数据或 EOF) ---- */
if (msg.result == NET_ERR_TIMEDOUT) {
/* 本片超时:timeout_ms 内未等到数据,非致命 → 返回 -2 */
/* 超时只凭返回码 -2 判定、不得依赖 errno:返回码是 netTask 经任务通知值
* 专为本任务回传的,天然无竞态(本分支也不改写错误码) */
/* 调用方见到 -2 后累计自身总超时预算,决定是否发起下一片 */
/* 本判断必须先于下方通用 <0 分支:NET_ERR_TIMEDOUT = -9 同样满足 <0 */
return -2;
}
if (msg.result < 0) {
/* 真错误:netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值回传,
* 此处恢复为线程级错误码(可经 net_get_errno() 读取);
* 对外保持 BSD 语义——失败一律返回 -1 */
return NET_ERR_RETURN_1(msg.result);
}
/* >= 0:0 = 对端关闭(EOF,net 层尽力自动重监听,失败由重试轮询接管);
* > 0 = 实际接收字节数(可能小于 len,属正常部分读) */
/* 返回结果(0=EOF / >0=字节数) */
return msg.result;
}
/*
* 函数功能:关闭 Socket(线程安全版本,通过消息队列委托给 netTask)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* 出口参数:无
* 返回值:0 - 关闭成功 int
* -1 - 关闭失败 int
* 限定条件:Socket 已创建且处于非 CLOSED 状态;net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文
* 函数说明:将关闭请求放入消息队列,阻塞等待 netTask 处理完毕。TCP Socket 先断开连接再释放控制块。
* netTask 内请使用 net_close_sock()
*/
int net_close(int sockfd) {
/* 消息体(按值拷入队列,netTask 改的是其副本) */
net_msg_t msg;
/* 描述符越界 → BADF */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 消息队列未创建(网络层未初始化)→ 通用错误 */
if (netMsgQueueHandle == NULL) {
return NET_ERR_RETURN_1(NET_ERR);
}
/* 消息类型:关闭 Socket */
msg.type = NET_MSG_CLOSE;
/* 目标 Socket */
msg.sockfd = sockfd;
/* 无超时(关闭立即完成) */
msg.timeout_ms = 0U;
/* 调用方任务句柄(netTask 凭它回传回执) */
msg.caller = xTaskGetCurrentTaskHandle();
/* 结果占位(netTask 侧覆写实际结果) */
msg.result = -1;
/* 入队:队列满时阻塞调用方直至有空位 */
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
/* 入队失败 → BUSY */
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 阻塞等待 netTask 回执(两段式拾取,防丢失通知) */
msg.result = net_wait_result("clo", sockfd);
if (msg.result < 0) {
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
* errno;对外仍保持 BSD 语义——失败一律返回 -1 */
return NET_ERR_RETURN_1(msg.result);
}
/* 关闭成功(0) */
return msg.result;
}
/*
* 函数功能:发送 UDP 数据报(含目标地址)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 待发送数据缓冲区指针 const void *
* len - 数据长度 int > 0
* flags - 标志位,通常为 0 int
* dest_addr - 目标地址结构体指针 const struct net_sockaddr *
* addrlen - 目标地址结构体长度 int >= sizeof(struct net_sockaddr_in)
* 出口参数:无
* 返回值:实际发送字节数 int > 0 || -1
* -1 - 发送失败 int
* 限定条件:Socket 已创建且为 UDP 类型(SOCK_DGRAM);net_init() 已调用
* 函数说明:1. UDP 数据报无需连接:目标地址随消息一并投递
* 2. 与 net_send 同一消息通道,SPI 访问串行在 netTask
*/
int net_sendto(int sockfd, const void *buf, int len, int flags,
const struct net_sockaddr *dest_addr, int addrlen) {
/* Socket 控制块指针 */
net_sock_t *p_sock;
/* 目标地址(IPv4)强转 */
const struct net_sockaddr_in *p_addr_in;
/* 累计实际发送字节数 */
int sent = 0;
/* 当前等待阶段起始时刻(tick) */
uint32_t tick_start;
/* 参数检查 */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
/* 描述符越界 → BADF */
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 取控制块 */
p_sock = &s_net_socks[sockfd];
/* 控制块未在用 → BADF */
if (!p_sock->in_use) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 缓冲区无效 → INVAL */
if (buf == NULL || len <= 0) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* UDP 模式:设置目标地址 */
if (dest_addr != NULL && addrlen >= (int)sizeof(struct net_sockaddr_in)) {
/* 强转为 IPv4 地址结构体 */
p_addr_in = (const struct net_sockaddr_in *)dest_addr;
/* 转换 IP 为大端数组(同 TCP 客户端,set_des_ip 期望大端) */
/* 大端 IP 数组 */
uint8_t ip_arr[4];
/* 目的 IP(网络序 32 位) */
uint32_t ip = p_addr_in->sin_addr.s_addr;
/* 最高字节 → [0](大端序) */
ip_arr[0] = (uint8_t)(ip >> 24);
ip_arr[1] = (uint8_t)(ip >> 16);
ip_arr[2] = (uint8_t)(ip >> 8);
/* 最低字节 → [3] */
ip_arr[3] = (uint8_t)(ip);
/* 设置目的 IP */
ch395f_set_des_ip(sockfd, ip_arr);
/* 设置目的端口(网络序 → 主机序) */
ch395f_set_des_port(sockfd, net_ntohs(p_addr_in->sin_port));
}
/* 记录起始时刻(用于停滞超时判定) */
tick_start = HAL_GetTick();
/* 分块发送循环 */
while (sent < len) {
/* 剩余待发字节 */
int chunk = len - sent;
/* 单块以硬件发送缓冲为上限(Trap 20) */
if (chunk > NET_SEND_CHUNK_MAX) {
chunk = NET_SEND_CHUNK_MAX;
}
/* 发送缓冲未空:等待腾出 */
if (!p_sock->send_ready) {
/* 停滞超时 */
if (NET_SEND_TIMEOUT_MS > 0 &&
(HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) {
DBG_ERROR("sock%d send stall: send_ready not set (%d/%d)", sockfd, sent, len);
(void)net_set_errno(NET_ERR_TIMEDOUT);
break;
}
/* 轮询一次,下一轮再试 */
net_poll();
continue;
}
/* 本块写入芯片发送 FIFO */
ch395f_write_send_buf(sockfd, (uint8_t *)buf + sent, (uint16_t)chunk);
/* 标记发送缓冲占用中 */
p_sock->send_ready = 0;
/* 累计发送偏移 */
sent += chunk;
/* 累计发送字节统计 */
p_sock->send_bytes += (uint32_t)chunk;
/* 等待发送完成 */
/* 重置本等待阶段计时 */
tick_start = HAL_GetTick();
/* 等待本块发完(SENBUF_FREE 中断重新置 send_ready) */
while (!p_sock->send_ready) {
/* 轮询:处理 SEND_OK/SENBUF_FREE 中断 */
net_poll();
/* 等待超时:本块放弃(外层统一处理) */
if (NET_SEND_TIMEOUT_MS > 0 &&
(HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) {
break;
}
}
}
/* 发送未完成 = 短写 */
if (sent < len) {
/* 短写告警:调用方必须检查返回值。静默截断会让对端永久等待剩余字节
* (2026-09-21 实证:UI 推流应答被截断 → 客户端反复判定"全程无字节"失联)。 */
DBG_ERROR("sock%d short send %d/%d (errno=%d)", sockfd, sent, len,
s_net_errno_slots[net_errno_slot()].err);
}
/* 返回实际发送字节数(调用方必须检查) */
return sent;
}
/*
* 函数功能:接收 UDP 数据报(含源地址)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位,通常为 0 int
* 出口参数:src_addr - 输出:发送方地址结构体指针 struct net_sockaddr *(可为 NULL)
* addrlen - 输入输出:源地址长度 int *(可为 NULL)
* 返回值:实际接收字节数 int >= 0 || -1
* -1 - 接收失败 int
* 限定条件:Socket 已创建且为 UDP 类型(SOCK_DGRAM);net_init() 已调用;有数据可用
* 函数说明:1. 轮询检查芯片接收缓冲(net_poll + get_recv_len),有数据即返回
* 2. UDP 数据报前 8 字节为报文头(源端口+源 IP),据此填充 src_addr
* 3. 无数据时:DONTWAIT 立即返回 -1(WOULDBLOCK);默认等待 NET_RECV_TIMEOUT_MS 后返回 TIMEDOUT
*/
int net_recvfrom(int sockfd, void *buf, int len, int flags,
struct net_sockaddr *src_addr, int *addrlen) {
/* Socket 控制块指针 */
net_sock_t *p_sock;
/* 起始时刻(超时判定基准,tick) */
uint32_t tick_start;
/* 参数检查 */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
/* 描述符越界 → BADF */
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 取控制块 */
p_sock = &s_net_socks[sockfd];
/* 控制块未在用 → BADF */
if (!p_sock->in_use) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 缓冲区无效 → INVAL */
if (buf == NULL || len <= 0) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 记录起始时刻(超时基准) */
tick_start = HAL_GetTick();
/* 轮询直到:有数据 / 非阻塞 / 超时 */
while (1) {
/* 轮询一次:更新状态机与可读标志 */
net_poll();
/* 查询芯片侧可用字节数 */
int recv_len = ch395f_get_recv_len(sockfd);
/* 有数据 */
if (recv_len > 0) {
/* UDP Server 模式:前 8 字节为固定格式(IP + 端口 + 长度)*/
/* UDP 数据报:解析 8 字节报文头 */
if (p_sock->type == NET_SOCK_DGRAM && recv_len >= 8) {
/* 8 字节报文头 */
uint8_t header[8];
/* 读出报文头(剥头 = 消费芯片缓冲) */
ch395f_read_recv_buf(sockfd, header, 8);
/* 数据长度 = 总长 - 头长 */
recv_len -= 8;
/* 填充来源地址 */
if (src_addr != NULL &&addrlen != NULL &&
*addrlen >= (int)sizeof(struct net_sockaddr_in)) {
/* 强转为 IPv4 地址结构体 */
struct net_sockaddr_in *p_addr_in = (struct net_sockaddr_in *)src_addr;
/* 地址族 = IPv4 */
p_addr_in->sin_family = NET_AF_INET;
/* CH395F UDP 头 [0-1] 数据长度(LE) [2-3] src_port(LE) [4-7] src_ip(§9.2.4 表) */
/* 源端口(小端 2 字节 → 主机序 → 网络序) */
p_addr_in->sin_port = net_htons((uint16_t)(header[2] | (header[3] << 8)));
/* 源 IP(小端 4 字节组装为网络序) */
p_addr_in->sin_addr.s_addr = ((uint32_t)header[4]) |
((uint32_t)header[5] << 8) |
((uint32_t)header[6] << 16) |
((uint32_t)header[7] << 24);
/* 回填结构体长度 */
*addrlen = sizeof(struct net_sockaddr_in);
}
}
/* 读取数据 */
if (recv_len > 0) {
/* 本次读取量 = min(可用, 缓冲容量) */
int read_len = (recv_len > len) ? len : recv_len;
/* 读入调用方缓冲区 */
ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len);
/* 累计接收字节统计 */
p_sock->recv_bytes += (uint32_t)read_len;
/* 返回本次读出字节数 */
return read_len;
}
}
/* 非阻塞模式:无数据立即返回 */
if (flags & NET_MSG_DONTWAIT) {
/* 置 WOULDBLOCK */
return NET_ERR_RETURN_1(NET_ERR_WOULDBLOCK);
}
/* 超时检查 */
if (NET_RECV_TIMEOUT_MS > 0 &&
(HAL_GetTick() - tick_start) >= NET_RECV_TIMEOUT_MS) {
/* 置 TIMEDOUT */
return NET_ERR_RETURN_1(NET_ERR_TIMEDOUT);
}
}
}
/*
* 函数功能:发送数据(内部版本,绕过消息队列,供 netTask 直接使用)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* buf - 待发送数据缓冲区指针 const void *
* len - 数据长度 int > 0
* 出口参数:无
* 返回值:实际发送字节数(>= 0);-1 表示参数无效 int
* 限定条件:p_sock 非空;netTask 上下文(避免向自身发送消息造成死锁)
* 函数说明:直接调用 net_send_locked 完成发送
*/
int net_send_sock(net_sock_t *p_sock, const void *buf, int len) {
/* 控制块指针为空 → BADF */
if (p_sock == NULL) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 直接委托锁定实现(netTask 上下文,无队列往返) */
return net_send_locked(p_sock, buf, len);
}
/*
* 函数功能:接收数据(内部版本,绕过消息队列,供 netTask 直接使用)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位,NET_MSG_DONTWAIT=非阻塞 int
* 出口参数:buf - 填充接收到的数据
* 返回值:实际接收字节数 int >= 0 || 0 || -1
* 0 - 对端关闭连接 int
* -1 - 接收失败 int
* 限定条件:必须在 netTask 上下文中调用;p_sock->in_use == 1
* 函数说明:1. 直接操作 Socket 控制块,不经过消息队列。避免 netTask 向自身发送消息造成死锁
* 2. L3 后:netTask 上下文的阻塞等待会被挂起登记拒绝(自通知死锁)
* 3. 本函数阻塞用法仅对 DONTWAIT 有效
*/
int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags) {
/* 控制块指针为空 → BADF */
if (p_sock == NULL) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 直接委托锁定实现(netTask 上下文,无队列往返);
* 0 = 默认 NET_RECV_TIMEOUT_MS;caller = netTask 自身(自调用) */
return net_recv_locked(p_sock, buf, len, flags, 0U,
xTaskGetCurrentTaskHandle());
}
/*
* 函数功能:关闭 Socket(内部版本,绕过消息队列,供 netTask 直接使用)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:0 - 关闭成功 int
* -1 - 关闭失败 int
* 限定条件:必须在 netTask 上下文中调用;p_sock->in_use == 1
* 函数说明:直接操作 Socket 控制块,不经过消息队列。TCP Socket 先断开连接再释放控制块
*/
int net_close_sock(net_sock_t *p_sock) {
/* 控制块指针为空 → BADF */
if (p_sock == NULL) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 直接委托锁定实现(netTask 上下文,无队列往返) */
return net_close_locked(p_sock);
}
/*
* 函数功能:注册 Socket 事件回调函数
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* cb - 回调函数指针 net_event_cb_t(可为 NULL 取消注册)
* arg - 用户自定义参数指针 void *
* 出口参数:无
* 返回值:0 - 注册成功 int
* -1 - 注册失败 int
* 限定条件:Socket 已创建且 in_use == 1;net_init() 已调用
* 函数说明:1. 回调在 netTask 上下文触发(事件分派路径内),实现须短小且不得阻塞
* 2. cb 传 NULL 可取消注册
*/
int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) {
/* Socket 控制块指针 */
net_sock_t *p_sock;
/* 参数检查 */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
/* 描述符越界 → BADF */
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 取控制块 */
p_sock = &s_net_socks[sockfd];
/* 控制块未在用 → BADF */
if (!p_sock->in_use) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 注册回调函数(NULL = 取消注册) */
p_sock->event_cb = cb;
/* 注册用户自定义参数 */
p_sock->event_arg = arg;
/* 注册成功 */
return 0;
}
/*
* 函数功能:获取 **本任务** 最后一次 net_*() 调用失败时的精确错误码
* 入口参数:无
* 出口参数:无
* 返回值:错误码(NET_OK=0, NET_ERR_xxx=-N);本任务无失败记录时为 NET_OK
* 限定条件:无(任何上下文可调用;中断上下文读 [0] 槽)
* 函数说明:1. E-03 修复:错误码**按任务隔离**——只读本任务自己的槽位,
* 其他任务的 net 调用再也覆盖不了它(旧实现为单一全局量,跨任务竞态)
* 2. 调用成功时**不清零**(保持历史语义,向后兼容既有调用方)
* 3. 超时场景(net_recv_to)请直接用返回码 -2 判定,不要依赖本接口
*/
int net_get_errno(void) {
/* 返回本任务槽位的错误码(只读快照;无槽位时回退 [0] 槽) */
return s_net_errno_slots[net_errno_slot()].err;
}
/*
* 函数功能:查询 Socket 当前状态(只读,无锁)
* 入口参数:sockfd - Socket 描述符 int
* 出口参数:无
* 返回值:状态值(net_sock_state_t);描述符无效/未占用 → -1
* 限定条件:无(单字节读,无撕裂;值可能滞后于 netTask 的最新状态)
* 函数说明:1. 跨任务只读快照;典型用途:检测"芯片静默回收"失步(2026-09-22 台架实证)
* 2. peer 发 FIN 后芯片自行把 Socket 回收回 LISTEN 且不发 DISCONNECT 中断
* 3. MCU 侧状态滞留 ESTABLISHED → 新连接字节被旧会话吞掉
* 4. 上层可据此执行 close+listen 重同步
*/
int net_sock_state(int sockfd) {
/* 描述符越界:返回 -1(不设错误码,只读查询) */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
return -1;
}
/* 控制块未在用:返回 -1 */
if (!s_net_socks[sockfd].in_use) {
return -1;
}
/* 返回状态快照(单字节读,无撕裂;值可能滞后于 netTask 最新状态) */
return (int)s_net_socks[sockfd].state;
}
/*
* 函数功能:断连重同步(仅 MCU 侧状态,见头文件注释的适用场景)
* 入口参数:sockfd - Socket 描述符 int
* 出口参数:无
* 返回值:0 - 已同步/原本即一致;-1 - 失败
* 限定条件:连接已死且芯片已静默回收后调用(应用层判定)
* 函数说明:1. 适用:芯片已静默回收连接(无 DISCONNECT 中断)而本层状态滞留 ESTABLISHED
* 2. 状态收回 LISTENING 并重建监听,后续 accept 恢复正常
*/
int net_resync_listen(int sockfd) {
/* 消息体(按值拷入队列,netTask 改的是其副本) */
net_msg_t msg;
/* 描述符越界 → BADF */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 消息队列未创建(网络层未初始化)→ 通用错误 */
if (netMsgQueueHandle == NULL) {
return NET_ERR_RETURN_1(NET_ERR);
}
/* 消息类型:断连重同步(MCU 侧收回 LISTENING 并重建监听) */
msg.type = NET_MSG_RESYNC_LISTEN;
/* 目标 Socket */
msg.sockfd = sockfd;
/* 无超时(重同步立即完成) */
msg.timeout_ms = 0U;
/* 调用方任务句柄(netTask 凭它回传回执) */
msg.caller = xTaskGetCurrentTaskHandle();
/* 结果占位(netTask 侧覆写实际结果) */
msg.result = -1;
/* 入队:队列满时阻塞调用方直至有空位 */
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
/* 入队失败 → BUSY */
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 阻塞等待 netTask 回执(两段式拾取,防丢失通知) */
msg.result = net_wait_result("rsl", sockfd);
/* 失败:恢复 errno(对外统一 -1) */
if (msg.result < 0) {
return NET_ERR_RETURN_1(msg.result);
}
/* 重同步成功(0) */
return 0;
}
/*
* 函数功能:无条件重新监听(芯片侧 open+listen,含 Trap 09 失败重试)
* 入口参数:sockfd - Socket 描述符 int
* 出口参数:无
* 返回值:0 - 成功;-1 - 失败
* 限定条件:TCP 监听型 Socket;net_init() 已完成
* 函数说明:1. 用于"断连后监听器可能已失位"的自愈场景(2026-09-22 run9 实证)
* 2. 一次性 net_listen 落在 Trap 09 窗口失败且被静默忽略 → 监听器永久丢失
* 3. 后续连接全部拒绝
* 4. 与 auto_relisten 内部路径同一实现(net_relisten_locked:单次尝试 + 非阻塞重试,见 net_listener_retry_tick)
* 5. 芯片侧已在监听时:首次 open 失败 → close 重建 → 监听短暂中断 ~1s
*/
int net_relisten(int sockfd) {
/* 消息体(按值拷入队列,netTask 改的是其副本) */
net_msg_t msg;
/* 描述符越界 → BADF */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 消息队列未创建(网络层未初始化)→ 通用错误 */
if (netMsgQueueHandle == NULL) {
return NET_ERR_RETURN_1(NET_ERR);
}
/* 消息类型:无条件重新监听(含 Trap 09 非阻塞重试) */
msg.type = NET_MSG_RELISTEN;
/* 目标 Socket */
msg.sockfd = sockfd;
/* 无超时(重监听立即尝试) */
msg.timeout_ms = 0U;
/* 调用方任务句柄(netTask 凭它回传回执) */
msg.caller = xTaskGetCurrentTaskHandle();
/* 结果占位(netTask 侧覆写实际结果) */
msg.result = -1;
/* 入队:队列满时阻塞调用方直至有空位 */
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
/* 入队失败 → BUSY */
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 阻塞等待 netTask 回执(两段式拾取,防丢失通知) */
msg.result = net_wait_result("rel", sockfd);
/* 失败:恢复 errno(对外统一 -1) */
if (msg.result < 0) {
return NET_ERR_RETURN_1(msg.result);
}
/* 重监听成功(0) */
return 0;
}
/*
* 函数功能:将点分十进制 IP 地址字符串转换为网络字节序的 32 位地址
* 入口参数:cp - 点分十进制 IP 字符串(如 "192.168.1.100") const char *
* 出口参数:无
* 返回值:网络字节序的 32 位 IP 地址(0 表示参数无效) uint32_t
* 限定条件:无
* 函数说明:1. 逐段解析十进制数与并小端组装为 32 位值
* 2. 转换结果经 NET_HTONL 转为网络字节序
*/
uint32_t net_inet_addr(const char *cp) {
/* 组装出的 32 位地址(主机序,末转网络序) */
uint32_t addr = 0;
/* 段循环索引 */
int i;
/* 参数为空:返回 0(无效) */
if (cp == NULL) {
return 0;
}
/* 逐段解析(4 段) */
for (i = 0; i < 4; i++) {
/* 本段数值 */
uint32_t val = 0;
/* 逐字符解析十进制数字 */
while (*cp >= '0' && *cp <= '9') {
val = val * 10 + (*cp - '0');
cp++;
}
/* 本段放入对应字节位(段 0 = 最低字节,小端组装) */
addr |= (val << (i * 8));
/* 跳过分隔符点(末段可无点) */
if (*cp == '.') {
cp++;
}
}
/* 转网络字节序后返回 */
return NET_HTONL(addr);
}
/*
* 函数功能:将网络字节序的 32 位地址转换为点分十进制字符串
* 入口参数:addr - 网络字节序的 32 位 IP 地址 uint32_t
* buf - 输出缓冲区(至少 16 字节) char *
* 出口参数:buf - 填充点分十进制字符串
* 返回值:成功返回 buf 指针;buf 为 NULL 时返回 NULL char *
* 限定条件:buf 非空且长度足够(>= 16 字节)
* 函数说明:1. 先经 NET_NTOHL 转为主机字节序再格式化输出
*/
char *net_inet_ntoa(uint32_t addr, char *buf) {
/* 网络序 → 主机序 */
uint32_t naddr = NET_NTOHL(addr);
/* 输出缓冲为 NULL:返回 NULL */
if (buf == NULL) {
return NULL;
}
/* 四段格式化为点分十进制(最低段在前,16B 缓冲) */
snprintf(buf, 16, "%lu.%lu.%lu.%lu",
(unsigned long)(naddr & 0xFF),
(unsigned long)((naddr >> 8) & 0xFF),
(unsigned long)((naddr >> 16) & 0xFF),
(unsigned long)((naddr >> 24) & 0xFF));
/* 返回输出缓冲 */
return buf;
}
/*
* 函数功能:端口字节序转换(主机序 -> 网络序)
* 入口参数:hostshort - 主机字节序的 16 位端口 uint16_t
* 出口参数:无
* 返回值:网络字节序的 16 位端口 uint16_t
* 限定条件:无
* 函数说明:直接调用 NET_HTONS 完成转换
*/
uint16_t net_htons(uint16_t hostshort) {
/* 主机序 → 网络序(16 位端口) */
return NET_HTONS(hostshort);
}
/*
* 函数功能:端口字节序转换(网络序 -> 主机序)
* 入口参数:netshort - 网络字节序的 16 位端口 uint16_t
* 出口参数:无
* 返回值:主机字节序的 16 位端口 uint16_t
* 限定条件:无
* 函数说明:直接调用 NET_NTOHS 完成转换
*/
uint16_t net_ntohs(uint16_t netshort) {
/* 网络序 → 主机序(16 位端口) */
return NET_NTOHS(netshort);
}
/*
* 函数功能:IP 地址字节序转换(主机序 -> 网络序)
* 入口参数:hostlong - 主机字节序的 32 位 IP 地址 uint32_t
* 出口参数:无
* 返回值:网络字节序的 32 位 IP 地址 uint32_t
* 限定条件:无
* 函数说明:直接调用 NET_HTONL 完成转换
*/
uint32_t net_htonl(uint32_t hostlong) {
/* 主机序 → 网络序(32 位 IP) */
return NET_HTONL(hostlong);
}
/*
* 函数功能:IP 地址字节序转换(网络序 -> 主机序)
* 入口参数:netlong - 网络字节序的 32 位 IP 地址 uint32_t
* 出口参数:无
* 返回值:主机字节序的 32 位 IP 地址 uint32_t
* 限定条件:无
* 函数说明:直接调用 NET_NTOHL 完成转换
*/
uint32_t net_ntohl(uint32_t netlong) {
/* 网络序 → 主机序(32 位 IP) */
return NET_NTOHL(netlong);
}
/*
* 函数功能:分配一个空闲的 Socket 控制块
* 入口参数:无
* 出口参数:无
* 返回值:空闲 Socket 索引(0~NET_MAX_SOCKETS-1);-1 表示无空闲 Socket int
* 限定条件:s_net_socks 已定义
* 函数说明:1. 线性扫描 s_net_socks,返回首个 in_use == 0 的控制块索引
* 2. 调用方负责初始化该控制块
*/
static int alloc_socket(void) {
/* 循环索引 */
int i;
/* 线性扫描首个空闲控制块 */
for (i = 0; i < NET_MAX_SOCKETS; i++) {
/* in_use == 0:空闲槽位 */
if (!s_net_socks[i].in_use) {
/* 返回空闲索引(初始化由调用方负责) */
return i;
}
}
/* 无空闲控制块:返回 -1 */
return -1;
}
/*
* 函数功能:处理 CH395F CONNECT 中断事件(单连接到达 / Client 连接成功)
* 入口参数:p_sock - 发生连接事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 CONNECT 中断后调用;netTask 上下文
* 函数说明:1. 单连接:自身转 ESTABLISHED 并读取对端地址
* 2. TCP Client(CONNECTING):连接成功,读取对端地址并置 ESTABLISHED
* 3. 连接成功后使能 KeepAlive 并触发 NET_EVENT_CONNECTED 回调
*/
static void handle_connect_event(net_sock_t *p_sock) {
#if defined(NET_BENCH_CMD_LOG)
/* Bench:记 'a'(accept)命令 */
net_bench_cmd_log('a', (int)p_sock->ch395_sock, 0);
#endif
/* 监听态:有连接到达 */
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
/* 单连接:收到 CONNECT 中断 = 自身有连接到达,转为 ESTABLISHED */
DBG_INFO("sock%d standalone accept", p_sock->ch395_sock);
/* 对端 IP/端口(6 字节:4B IP + 2B 端口,小端) */
uint8_t remote[6];
/* 回读对端 IP + 端口 */
ch395f_get_remot_ipp(p_sock->ch395_sock, remote);
/* 对端 IP 数组(前 4 字节,大端) */
memcpy(p_sock->remote_ip_arr, remote, 4);
/* 小端 IP 组装为 32 位值 */
p_sock->remote_ip = ((uint32_t)remote[0]) |
((uint32_t)remote[1] << 8) |
((uint32_t)remote[2] << 16) |
((uint32_t)remote[3] << 24);
/* 对端端口(小端 2 字节) */
p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8));
/* 状态 → ESTABLISHED(监听自身转数据通道) */
p_sock->state = NET_SOCK_STATE_ESTABLISHED;
/* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平。必须在置
* ESTABLISHED 之后立刻做:否则抢先 relisten 后到新连接的窗口内
* recv 仍会误得 EOF(把新客户端踢开) */
p_sock->session_dead = 0U;
/* 新开 Socket 发送缓冲按空闲计 */
p_sock->send_ready = 1;
/* 清除残留的 TIMEOUT 中断标志 */
/* 读即清:本 Socket 中断状态寄存器 */
(void)ch395f_get_sock_int_status(p_sock->ch395_sock);
/* ⚠ 曾在此按 §5.56 补 ch395f_set_keepalive_enable(sock,1)(服务器侧
* KeepAlive),2026-10-12 实测后**撤回**:禁用网卡模拟真·静默死亡,
* 芯片 +2 s 即报 TIM_OUT(≈ §5.17 重传 8×200 ms),本层随即释放重开
* 监听——链路层失效本就被快速覆盖,KeepAlive 的 60 s 空转窗口对
* 该失败模式无增益。依 Trap 25 铁律「不留未证明的复杂度」撤回。
* 未覆盖的残余场景是"链路仍在、对端静默"(NAT/中间节点黑洞),
* 本轮无法安全注入(见 Trap 记录「台架注入陷阱」)。 */
/* 触发连接建立事件回调 */
fire_event(p_sock, NET_EVENT_CONNECTED);
} else if (p_sock->state == NET_SOCK_STATE_CONNECTING) {
/* TCP Client 连接成功 */
/* 对端 IP/端口(6 字节:4B IP + 2B 端口,小端) */
uint8_t remote[6];
/* 回读对端 IP + 端口 */
ch395f_get_remot_ipp(p_sock->ch395_sock, remote);
/* 对端 IP 数组(前 4 字节) */
memcpy(p_sock->remote_ip_arr, remote, 4);
/* 小端 IP 组装为 32 位值 */
p_sock->remote_ip = ((uint32_t)remote[0]) |
((uint32_t)remote[1] << 8) |
((uint32_t)remote[2] << 16) |
((uint32_t)remote[3] << 24);
/* 对端端口(小端 2 字节) */
p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8));
/* 状态 → ESTABLISHED */
p_sock->state = NET_SOCK_STATE_ESTABLISHED;
/* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平(同 LISTENING
* 分支:必须在转 ESTABLISHED 后立刻做,防抢先 relisten 窗口误 EOF) */
p_sock->session_dead = 0U;
/* 使能 KeepAlive(客户端连接保活) */
ch395f_set_keepalive_enable(p_sock->ch395_sock, 1);
/* 触发连接建立事件回调 */
fire_event(p_sock, NET_EVENT_CONNECTED);
}
}
/*
* 函数功能:处理 Socket 断开(DISCONNECT)事件,更新 Socket 状态机
* 入口参数:p_sock - 发生断开事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 DISCONNECT 中断后调用;netTask 上下文
* 函数说明:1. ⚠ FUN_PARA bit3=1(本仓 net_init 置 0x08):DISCONNECT/TIM_OUT 后芯片
* **不**关 Socket、**不清**缓冲(停 0x05/0x00)——残留数据须由本层读出后,再由
* 本层 ch395f_close_socket 关闭;芯片不代劳(§5.52② / §5.23)。旧注释写"CH395F 在
* DISCONNECT 后自动关闭 Socket(FUN_PARA 默认)"与实际 0x08 相反,2026-10-12 修正:
* 它会误导维护者跳过显式 close(= U12 的成因之一)。之后需重新 OPEN + LISTEN
* 2. 单连接:置 CLOSED,交由 net_recv_locked 排空剩余数据后再关
* 3. 触发 NET_EVENT_DISCONNECTED 回调
*/
static void handle_disconnect_event(net_sock_t *p_sock) {
#if defined(NET_BENCH_CMD_LOG)
/* Bench:记 'd'(disconnect)命令 */
net_bench_cmd_log('d', (int)p_sock->ch395_sock, 0);
#endif
/* 由控制块指针反推描述符 */
int sockfd = (int)(p_sock - s_net_socks);
/* 监听态:DISCONNECT 落在监听器上 */
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
/* Trap 22 / N-6:监听态收到 DISCONNECT——bit3=1(0x08)下芯片**不**关该 Socket(停 0x05/0x00,芯片 TCP 侧已 CLOSED);不重开则此后所有 SYN 全被拒 = 监听永久死亡;
* 触发 = 断连后 <~1s 快速复连时 CONNECT 中断未上报(控制块滞留 LISTENING)后该连接断开(2026-09-23 生产固件复现);
* N-32 P2:本分支**只回读留痕、不再 relisten** —— 重建统一交
* net_listener_pump(每 50 ms 回读芯片状态,监听失位即重建)。原处直接
* relisten 是 12 个所有者之一,且位于 DISCONNECT 事件路径,可能"抢先"
* 于消费者 recv(P1 已消除其后果,P2 消除其可能) */
/* 回读缓冲(芯片状态 2 字节) */
uint8_t st[2] = {0, 0};
/* 回读芯片状态(仅为留痕:重建与否由 pump 裁决) */
ch395f_get_socket_status((uint8_t)sockfd, st);
/* 监听已失位(芯片已关闭) */
if ((st[0] == CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_LISTEN)) {
DBG_INFO("sock%d listen lost (chip sock=0x%02X tcp=0x%02X), pump will relisten",
sockfd, st[0], st[1]);
} else {
/* 仍在监听:判陈旧事件丢弃 */
DBG_INFO("sock%d stale listen DISCONNECT ignored (chip tcp=0x%02X)", sockfd, st[1]);
}
} else if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
/* 陈旧 DISCONNECT 甄别(D20 修复,2026-09-22 台架实证):同一 fd 被"关闭 → 重新监听 → 再连接"循环复用,上一条连接的 FIN/ACK
* 可能在 fd 已复用为新连接**之后**才上报(net_close_locked 排空的中断里还没有它)→ 新连接被误判为断开(实测:FTP 同一会话第二个数据连接必失败 425);
* 处置:回读芯片状态,TCP 仍 ESTABLISHED 判陈旧丢弃(已读清不重复上报);真断开时芯片处于 CLOSE_WAIT(0x07)/CLOSED(0x00) 等状态照常处理;超时事件走 handle_timeout_event,不受影响 */
/* 芯片状态回读缓冲(2 字节) */
uint8_t st[2];
/* 回读芯片状态(Socket + TCP) */
ch395f_get_socket_status((uint8_t)sockfd, st);
/* 芯片仍 ESTABLISHED:陈旧事件 */
if (st[1] == CH395F_TCP_ESTABLISHED) {
/* 留痕:陈旧事件丢弃 */
DBG_ERROR("sock%d stale DISCONNECT ignored (chip TCP still ESTABLISHED)", sockfd);
/* 直接返回(中断已读清,不会重复上报) */
return;
}
/* 单连接:先不关 CH395F Socket,
* 留给 net_recv_locked 把剩余数据读出来再关,置 CLOSED 等待应用重新监听 */
DBG_INFO("sock%d DISCONNECTED, releasing (chip st=0x%02X/0x%02X)",
p_sock->ch395_sock, st[0], st[1]);
/* Trap 23 推广(2026-09-24 台架实证):正常 DISCONNECT 释放留下的芯片收尾状态
* 会在复用同一 fd 的下一条连接上以"伪 DISCONNECT"形式投递(实测 accept 后约 10~20ms)→ 该连接被提前释放,应用把已到的 2048B 当整份文件并回报 226;
* 仅**本连接收过数据**时登记沉降(实测触发源 = 收过数据的数据通道被关闭),纯连接即关/只发不收的连接免沉降,保住 Trap 22 的快速复连时序(:21 @0ms ×50 全通过) */
/* 本连接收过数据:登记沉降(同 fd 下次重监听前多等 400/800ms 沉降窗口) */
if (p_sock->recv_bytes != 0U) {
/* 反推描述符(沉降按 fd 登记) */
int qfd = (int)(p_sock - s_net_socks);
/* 描述符范围保护 */
if ((qfd >= 0) && (qfd < NET_MAX_SOCKETS)) {
/* 登记优雅沉降(NET_RELISTEN_SETTLE_GRACEFUL) */
net_listener_settle(qfd, NET_LISTENER_SETTLE_GRACEFUL);
}
}
/* 正常释放(对端 FIN):断连了结回 EOF(0) */
p_sock->released_abnormal = 0U;
/* N-32(P1 不变量 I):置"本会话已结束"电平。须在置 CLOSED **之前**,
* 使 session_dead 覆盖"CLOSED 等待 recv 排空"整个窗口——排空期间
* recv 的 DONTWAIT 快路径先返数据(recv_len>0 优先),数据尽后判据
* 落到本标志 → 仍得 EOF,不因排空慢而漏报 */
p_sock->session_dead = 1U;
/* N-30(Trap 25):登记本连接对端端口(幽灵态甄别基准,须在状态
* 迁移前记录——remote_port 此刻仍有效,relisten 清零) */
p_sock->last_peer_port = p_sock->remote_port;
/* 状态 → CLOSED */
p_sock->state = NET_SOCK_STATE_CLOSED;
/* 触发断连事件回调 */
/* 触发断连事件回调 */
fire_event(p_sock, NET_EVENT_DISCONNECTED);
/* 其他状态:未预期的断开 */
} else if (p_sock->state != NET_SOCK_STATE_CLOSED &&
p_sock->state != NET_SOCK_STATE_BOUND &&
p_sock->state != NET_SOCK_STATE_CREATED) {
/* 未预期的断开:记错误日志 */
DBG_ERROR("sock%d disconnect in state %d", p_sock->ch395_sock, p_sock->state);
/* 触发断连事件回调(统一) */
fire_event(p_sock, NET_EVENT_DISCONNECTED);
}
}
/*
* 函数功能:处理 Socket 数据接收(RECV_OK)事件
* 入口参数:p_sock - 发生接收事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 RECV_OK 中断后调用;netTask 上下文
* 函数说明:1. 仅触发 NET_EVENT_DATA_RECEIVED 回调通知上层
* 2. 不在此读取 recv_len,避免与 net_recv_locked 中的读取冲突(Trap 13)
*/
static void handle_recv_event(net_sock_t *p_sock) {
/* RECV_OK 中断已触发,直接通知上层。不在此读 recv_len,
* 避免和 net_recv_locked 里面的 ch395f_get_recv_len 冲突(Trap 13) */
/* 置可读标志,供 net_select 在无 SPI 访问下判读 */
p_sock->readable = 1;
/* 触发数据到达事件回调 */
fire_event(p_sock, NET_EVENT_DATA_RECEIVED);
}
/*
* 函数功能:处理 Socket 发送完成(SEND_OK)事件
* 入口参数:p_sock - 发生发送完成事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 SEND_OK 中断后调用;netTask 上下文
* 函数说明:触发 NET_EVENT_SEND_COMPLETE 回调通知上层
*/
static void handle_send_event(net_sock_t *p_sock) {
/* 触发发送完成事件回调 */
fire_event(p_sock, NET_EVENT_SEND_COMPLETE);
}
/*
* 函数功能:处理 Socket 超时(SOCK_TIMEOUT)事件,更新 Socket 状态机
* 入口参数:p_sock - 发生超时事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 SOCK_TIMEOUT 中断后调用;netTask 上下文
* 函数说明:1. TCP Client(CONNECTING):连接超时,回到 CREATED 状态
* 2. 已建立连接:回到 CLOSED(单连接)
* 3. 监听态:异常超时,记录错误日志
* 4. 触发 NET_EVENT_TIMEOUT 回调
*/
static void handle_timeout_event(net_sock_t *p_sock) {
#if defined(NET_BENCH_CMD_LOG)
/* Bench:记 'x'(timeout)命令 */
net_bench_cmd_log('x', (int)p_sock->ch395_sock, 0);
#endif
/* 连接中状态:连接超时 */
if (p_sock->state == NET_SOCK_STATE_CONNECTING) {
/* TCP Client 连接超时 */
DBG_ERROR("sock%d CONNECT TIMEOUT", p_sock->ch395_sock);
/* 状态 → CREATED(客户端槽位回可复用) */
p_sock->state = NET_SOCK_STATE_CREATED;
} else if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
/* 已建立连接超时 */
DBG_ERROR("sock%d TIMEOUT", p_sock->ch395_sock);
/* Trap 23:异常释放(TIMEOUT,例如客户端中止上传)→ 复监听前隔离,
* 等对端在途数据落尽并排空 RX 残渣(详见 net_relisten_locked 区块注释) */
{
/* 反推描述符 */
int qfd = (int)(p_sock - s_net_socks);
/* 描述符范围保护 */
if ((qfd >= 0) && (qfd < NET_MAX_SOCKETS)) {
/* 登记异常释放隔离(重监听前等待在途数据落尽 + RX 排空) */
net_listener_settle(qfd, NET_LISTENER_SETTLE_QUARANTINE);
}
}
/* Trap 23 待办 #3:标记异常释放——L3 挂起 recv 排空断连后以
* NET_ERR_CONNRESET 了结(而非 EOF),应用(lftpd STOR)回报 5xx
* 而非误报 226(见 net_pending_tick 断连分支) */
p_sock->released_abnormal = 1U;
/* N-30(Trap 25):异常释放同样登记对端端口(幽灵甄别基准) */
p_sock->last_peer_port = p_sock->remote_port;
/* N-32(P1 不变量 I):异常结束同样是"本会话已结束"电平(TIMEOUT/RST
* 已在下方 requarantine,本标志只表达"会话结束了"这一事实,与
* released_abnormal(结束得好不好看)正交) */
p_sock->session_dead = 1U;
/* 状态 → CLOSED */
p_sock->state = NET_SOCK_STATE_CLOSED;
} else if (p_sock->state == NET_SOCK_STATE_LISTENING) {
/* 监听超时(异常情况) */
DBG_ERROR("sock%d LISTEN TIMEOUT", p_sock->ch395_sock);
}
/* 触发超时事件回调(统一) */
fire_event(p_sock, NET_EVENT_TIMEOUT);
}
/*
* 函数功能:触发 Socket 事件回调(若已注册)
* 入口参数:p_sock - 触发事件的 Socket 控制块指针 net_sock_t *
* event - 事件类型 net_event_t
* 出口参数:无
* 返回值:无
* 限定条件:net_init() 已调用
* 函数说明:1. 仅当 p_sock->event_cb 非空时调用回调
* 2. 回调参数为 ch395_sock、event、event_arg
*/
static void fire_event(net_sock_t *p_sock, net_event_t event) {
/* 已注册回调 */
if (p_sock->event_cb != NULL) {
/* 调用回调(ch395_sock、event、用户参数) */
p_sock->event_cb(p_sock->ch395_sock, event, p_sock->event_arg);
}
}
/*
* 函数功能:将点分十进制 IP 地址字符串解析为 4 字节数组
* 入口参数:str - 点分十进制 IP 字符串(如 "192.168.1.100") const char *
* ip_arr - 输出:4 字节 IP 数组(网络序,低字节在前) uint8_t *
* 出口参数:ip_arr - 填充解析后的 IP 地址
* 返回值:无
* 限定条件:str 与 ip_arr 均非空
* 函数说明:1. 逐段解析十进制数写入 ip_arr[0..3]
* 2. 参数非法(NULL)时直接返回,不做解析
*/
static void parse_ip_string(const char *str, uint8_t *ip_arr) {
/* 段循环索引 */
int i;
/* 参数非法(NULL):直接返回,不做解析 */
if (str == NULL ||ip_arr == NULL) {
return;
}
/* 逐段解析(4 段) */
for (i = 0; i < 4; i++) {
/* 本段数值 */
uint32_t val = 0;
/* 逐字符解析十进制数字 */
while (*str >= '0' && *str <= '9') {
val = val * 10 + (*str - '0');
str++;
}
/* 本段写入 ip_arr(段 0 = 最低字节,小端) */
ip_arr[i] = (uint8_t)val;
/* 跳过分隔符点(末段可无点) */
if (*str == '.') {
str++;
}
}
}
/*
* 函数功能:根据描述符获取 Socket 控制块指针(内部使用)
* 入口参数:sockfd - Socket 描述符 int 0 - NET_MAX_SOCKETS-1
* 出口参数:无
* 返回值:Socket 控制块指针;描述符越界或未分配时返回 NULL net_sock_t *
* 限定条件:无
* 函数说明:1. 描述符越界(<0 或 >= NET_MAX_SOCKETS)返回 NULL
* 2. 未使用(in_use == 0)的 Socket 返回 NULL
*/
net_sock_t *net_get_sock(int sockfd) {
/* 描述符越界:返回 NULL */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
return NULL;
}
/* Socket 未在用:返回 NULL */
if (!s_net_socks[sockfd].in_use) {
return NULL;
}
/* 返回控制块指针 */
return &s_net_socks[sockfd];
}
/*
* 函数功能:获取 Socket 本地地址(IP + Port)
* 入口参数:sockfd - socket 描述符 int
* 出口参数:addr - 输出:本地地址 struct net_sockaddr *
* addrlen - 输入输出:地址结构体长度 int *
* 返回值:0 - 成功,-1 - 失败 int
* 限定条件:socket 已创建或已绑定
* 函数说明:返回 sin_family + sin_port(网络序)+ sin_addr(网络序,从 net_init 缓存的 s_local_ip)
*/
int net_getsockname(int sockfd, struct net_sockaddr *addr, int *addrlen) {
/* 描述符越界 → BADF */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 控制块(const:本函数只读 Socket 信息) */
const net_sock_t *p_sock = &s_net_socks[sockfd];
/* 未在用且状态 CLOSED → BADF */
if (!p_sock->in_use && p_sock->state == NET_SOCK_STATE_CLOSED) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 输出参数无效(NULL 或长度不足)→ INVAL */
if (addr == NULL ||addrlen == NULL || *addrlen < (int)sizeof(struct net_sockaddr_in)) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 强转为 IPv4 地址结构体 */
struct net_sockaddr_in *p_addr_in = (struct net_sockaddr_in *)addr;
/* 清零地址结构体(防御:全部字段归零) */
memset(p_addr_in, 0, sizeof(struct net_sockaddr_in));
/* 地址族 = IPv4 */
p_addr_in->sin_family = NET_AF_INET;
/* 本地端口(主机序 → 网络序) */
p_addr_in->sin_port = net_htons(p_sock->local_port);
/* 本地 IP:由 s_local_ip 数组小端组装为 32 位网络序值 */
p_addr_in->sin_addr.s_addr = ((uint32_t)s_local_ip[0]) |
((uint32_t)s_local_ip[1] << 8) |
((uint32_t)s_local_ip[2] << 16) |
((uint32_t)s_local_ip[3] << 24);
/* 回填结构体长度 */
*addrlen = sizeof(struct net_sockaddr_in);
/* 成功(0) */
return 0;
}