Files
STM32F4-Base/Drivers/BSP/NET/net_socket.c
T
wandering1 df13dec479
CI / L0 静态门禁 (pre-commit) (push) Failing after 2m14s
CI / L0.5 主机侧单测 (MSVC) (push) Successful in 22s
refactor(net): 挂起 recv 模型拆出 net_pending.c/h + 留痕探针编译门控 + E15 归因修正
- E15 归因修正:幽灵状态 0x04+旧端口 = 芯片如实报告拆除中的旧连接
  (文档化行为,非芯片故障;bit3=1 为我方设置(ver>=0x46)、
  RFC 9293 §3.3.2 Fig5 拆除顺序、WCH 官方博客方案 = close 完成后重开监听)
- 术语统一:全库"了结"→"结束"(约 45 处);阻塞模型 §4 首次出现处定义"五路"
- 注释清理:删孤儿函数头注释、考古叙事改现在时、去不可检索内部编码"L3"
  (33 处 6 文件)、banner 去重(唯一理由移到分支现场)
- 挂起超时留痕探针:编译期开关 NET_PENDING_TRIPWIRE(默认 0 = 编译剔除,
  省约 150B Flash;排查调用方挂死时置 1,只留痕绝不强制结束)
- 拆模块:net_pending.c/h(363/94 行)承载挂起 recv 模型,
  net_socket.c 4059→3661 行;tick 收控制块表参数、settle_close 双参显式传;
  net_pending_active 声明移配对头(S17 2.2);仅注册 uvprojx +
  check_registration MCU-only 白名单(同 net_listener.c 机制,
  模拟器走 net_socket_sim.c 按外部语义模拟)
- 文档:阻塞模型新增全过程小节(单主干流程图)+ §9 排查辅助;
  NET README / 函数调用关系 / 自愈架构 / BSD 指南同步;引用关系图重生成
- 台架回归:启动闭环 PASS([ERR]/Fault 零命中、回读一致)、
  :7003 重连探针 10/10、FTP 50 连接 STOR 50/50、回归窗口串口日志无异常
- RAM:ER_IROM1 230004B(探针门控后约 -150B),台账已回填
2026-10-14 09:53:38 +08:00

3662 lines
186 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 "net_pending.h"
#include "ch395f.h"
#include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */
#include "net_latency_test.h" /* 延迟探针接口(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) */
/* E6 实验插桩(U15 失配频率,docs/网络/网络自愈架构设计.md §4.8;裁决锚点 §5.2 E6):
* 假设=「RX 非空但 readable=0」(RECV_OK 事件未收到的数据面形态)在生产真实
* 发生。置 1 烧实验固件:net_poll 每 32 轮对在用 fd 发一条 GET_RECV_LEN,
* 命中计数 + 低频日志(每小时 ≤1 条防刷屏);跑 24h 常态 + E1 压测。
* 判据:命中 >0 → 阶段 3 项 4(U15 内化)立项;=0(含压测)→ 上层 32 轮
* 盲收保留现状、U15 登记观察项。**默认 0 = 产品构建零成本(编译剔除)**。
* E6 已裁决(2026-10-13,《网络自愈架构设计》§5.2 E6):命中=0(含压测)→ 项 4 不立项,维持默认 0。
* ⚠ 必须定义在文件最前部:bring_up 的 armed 日志与 net_poll 探测环两处消费。 */
#define NET_U15_PROBE 0
#if NET_U15_PROBE
#define NET_U15_PROBE_PERIOD 32U /* 每 32 轮 net_poll 探测一次(≈32ms) */
#define NET_U15_PROBE_LOG_MS 3600000U /* 命中日志每小时至多 1 条 */
static uint32_t s_u15_round; /* net_poll 轮次计数 */
static uint32_t s_u15_hits; /* 累计确认命中次数(判据数据) */
static uint32_t s_u15_last_log; /* 上次命中日志时刻(tick) */
static uint8_t s_u15_cand[NET_MAX_SOCKETS]; /* 两级确认的候选标记(per-socket) */
#endif
/*
* 全局变量定义区 - 尽量避免使用全局变量
*/
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_drop_conn_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);
/*
* 函数功能:判断 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-NET-19;
* 写芯片 EEPROM,每上电重放同值幂等)
*/
int net_init(const char *ip, const char *mask, const char *gateway, const uint8_t *mac) {
ch395f_status_t ret;
/* 网络重建前取消全部挂起请求并通知调用方(调用方阻塞等回执,
* 取消不通知 = 永久挂死);必须早于控制块清零与芯片复位 */
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;
net_pending_capture_owner(); /* 执行任务 = 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();
/* 芯片版本留痕 + 0-6 版本门控(自愈收敛阶段0,§5.52 表):bit3 SOCKET_CLOSE
* 需芯片 ≥0x46、TCP_SERVER 需 ≥0x44。本台架芯片实测 ≥0x46(TRAP-NET-20 定案
* SOCKET_CLOSE 实际生效);门控防批次漂移——版本不足则**拒置 bit3**
* (退回芯片自动关断语义)并 [ERR] 留痕,不静默带病运行 */
uint8_t chip_ver = ch395f_get_version();
/* FUN_PARA 实际下发值(版本不足 = 0,bit3 拒置) */
uint8_t fun_para = 0x00U;
if (chip_ver >= 0x46U) {
fun_para = CH395F_FUN_PARA_SOCKET_CLOSE;
} else {
DBG_ERROR("ch395f ver=0x%02X < 0x46: SOCKET_CLOSE(bit3) unsupported, cleared",
chip_ver);
}
/* 版本 + 实际生效 FUN_PARA 留痕(旧日志硬编码 0x08 改为实际值) */
DBG_INFO("ch395f ver=0x%02X fun_para=0x%02X", chip_ver, fun_para);
/* 功能参数(§5.52 表):bit3=1 SOCKET_CLOSE——DISCONNECT/TIMEOUT 后芯片
* 不清缓冲,由 MCU 读出残留数据后再关 Socket(TRAP-NET-20 三次定案的排空契约);
* 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-NET-15,
* 0x08 下的多 Socket 台架复测登记 N-31) */
ch395f_set_fun_para(fun_para);
/* 设置 TCP MSS:维持 1024(512 裕量方案未采纳,当年支撑它的台架判据不可信,
* 已随 TC-NET-604 结案为非缺陷一并撤回(2026-09-23)) */
/* 手册(§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-NET-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;
#if NET_U15_PROBE
/* E6:确认插桩固件已生效(挂机期间据此核对烧录身份) */
DBG_INFO("U15 probe armed: period=%u log_per_h=1", (unsigned int)NET_U15_PROBE_PERIOD);
#endif
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();
/* 自愈收敛阶段 3(F-DRV-13 接线):把全局位喂驱动——bit2 PHY_CHANGE 时驱动
* 重读 PHY 更新 link_up 缓存 + 变化计数,net 层在 reconciler 订阅留痕
* (运行期拔网线不再静默;不自动重配,重配仍走 UI/配置引擎路径) */
ch395f_glob_event_feed(int_status);
#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);
/* 自愈收敛阶段 3(§5 项 1 基础):每轮全量刷新在用 Socket 的驱动状态缓存
* (唯一刷新点;E2 实测单次回读 18.1us,在用 5~6 个 ≈100us ≪ 1ms 轮询
* 周期)。reconciler / 探针 / 镜像降级决策点一律读缓存(
* ch395f_sock_state_cached),不再各自发状态 SPI。放在中断处理之后:
* 事件处理(handle_*_event)内部若再读状态,拿到的也是本轮最新值。 */
for (int i = 0; i < NET_MAX_SOCKETS; i++) {
if (s_net_socks[i].in_use != 0U) {
(void)ch395f_get_sock_state((uint8_t)i);
/* 自愈收敛阶段 3(E-02 收口):驱动 sock_reset 状态机在 net_poll 步进
* (驱动头注释契约"在 net_poll 或测试循环里周期调用")。IDLE 槽位
* 纯 RAM 判返回 0 无 SPI;仅活跃复位机做短 SPI。作用:同步包装
* (net_sock_reset_sync)600ms 放弃后复位机不搁浅,芯片条件满足
* (对端回 FIN 等)时自动走完,槽位可复用(begin 不再恒返 -1)。 */
(void)ch395f_sock_reset_poll((uint8_t)i);
}
}
#if NET_U15_PROBE
/* E6 探测(宏说明见文件前部):RX 非空而 readable=0 = RECV_OK 事件未收到
* 的数据面形态。**两级确认去良性竞态**:数据刚到、本轮中断尚未读取的
* 竞态窗(≤1 个 poll)会在下一探测轮自然消失;事件未收到则持续存在直到
* 盲收/超时。候选轮标记 → 下一探测轮仍成立才计确认命中。排除 CLOSED:
* closed-with-data 是已知被救回形态,非 U15 假设对象。独立于上面的
* 缓存刷新环:GET_RECV_LEN 是独立 SPI。 */
s_u15_round++;
if ((s_u15_round % NET_U15_PROBE_PERIOD) == 0U) {
for (int i = 0; i < NET_MAX_SOCKETS; i++) {
uint16_t rlen;
if (s_net_socks[i].in_use == 0U) {
s_u15_cand[i] = 0U;
continue;
}
rlen = ch395f_get_recv_len((uint8_t)i);
if ((rlen > 0U) && (s_net_socks[i].readable == 0U) &&
(s_net_socks[i].state == NET_SOCK_STATE_ESTABLISHED)) {
if (s_u15_cand[i] != 0U) {
s_u15_cand[i] = 0U;
s_u15_hits++;
/* 首次命中必打(last_log=0 会被 uptime<1h 抑制);此后每小时 ≤1 条 */
if ((s_u15_hits == 1U) ||
((uint32_t)(HAL_GetTick() - s_u15_last_log) >= NET_U15_PROBE_LOG_MS)) {
s_u15_last_log = HAL_GetTick();
DBG_INFO("U15 probe: sock%d rx=%u readable=0 hits=%lu",
i, (unsigned int)rlen, (unsigned long)s_u15_hits);
}
} else {
s_u15_cand[i] = 1U;
}
} else {
s_u15_cand[i] = 0U;
}
}
}
#endif
/* 返回本次累计发生的事件数量(0 表示无事件) */
return event_count;
}
/* 对账+pump 合并周期(自愈收敛阶段 3:原 200ms 对账 / 100ms pump 合一) */
#define NET_RECONCILE_PERIOD_MS 100U
/* 同一 socket 两次动手的最小间隔(防抖成风暴) */
#define NET_RECONCILE_COOLDOWN_MS 1000U
/* 分级去抖门槛(单位=轮) */
#define NET_RECON_STABLE_ACCEPT 1U /* 形态② CONNECT 事件未收到补 accept(幽灵甄别兜底假阳性) */
#define NET_RECON_STABLE_RELISTEN 2U /* 形态③④ 监听失位重建 / ⑤ 静默回收 RESYNC */
#define NET_RECON_STABLE_DEATH 4U /* 形态① 连接已失(M3 让位窗 400ms,勿改小) */
/*
* 函数功能:芯片/本层 socket 状态周期对账(TRAP-NET-17 事件面未收到自愈,E14 复核:非芯片丢事件是被读清消费)+ 监听器 pump(N-32 P2)—— 自愈收敛阶段 3 合并为单一 100ms tick(docs/网络/网络自愈架构设计.md §4.1)
* 入口参数:无
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文(由 net_process_messages 每轮调用)
* 函数说明:1. 芯片快速复连会收不到 CONNECT/DISCONNECT 事件(读清消费竞态,E14 复核:非芯片丢事件),本层无法靠事件自愈
* 2. 判据读**驱动状态缓存**(唯一刷新点=net_poll 每轮全量刷新,E2 实测单次回读 18.1us)
* 3. 覆盖形态(原对账①②⑤ + 原 pump③④/U11):
* ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(只置事实)
* ② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(命中时
* 才回读对端端口做幽灵甄别——本 tick 唯一按需 SPI)
* ⑤(F-NET-2)本层 ESTABLISHED 而芯片静默回收回 LISTEN(0x05/0x01 无
* DISCONNECT)→ RESYNC 只修本层
* ③ 本层 LISTENING(监听型 fd)而芯片监听失位 → 重建
* ④ 本层 CLOSED(监听型 fd)而芯片不在 LISTEN → 重建
* U11 本层 CLOSED(监听型 fd)而芯片在听 → 只重同步,**不发芯片命令**
* (重下发因 USE 错误关掉正听着的 Socket,U12 同源事故)
* 4. 去抖分级(100ms/轮;E2:LISTEN 可见 100% <=10ms 无长尾):②=1 轮
* (幽灵甄别挡拆链窗的旧连接 ESTABLISHED);③④⑤=2 轮(2026-09-23 实证:
* 无去抖时监听被反复重建直至放弃);①=4 轮(**勿改小**:F-NET-17 M3
* 可疑电平让位再入,400ms 让位窗=在途投递不可毁的最后租约,E9a 实证)
* 5. 动手后冷却 NET_RECONCILE_COOLDOWN_MS 防风暴(U11 被动重同步不占)
* 6. 链路态订阅(F-DRV-13):驱动 link 变化计数变化 → 留痕(不自动重配)
*/
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;
/* 未到对账周期(100ms):直接返回(缓存刷新在 net_poll,与本函数无关) */
if ((uint32_t)(now - s_last) < NET_RECONCILE_PERIOD_MS) {
return;
}
/* 更新本轮起始时刻 */
s_last = now;
/* 链路态订阅(F-DRV-13):net_poll 已喂驱动,这里只看计数变化 */
{
/* 链路变化计数订阅基线(0xFFFF=未建档) */
static uint16_t s_link_last = 0xFFFFU;
uint16_t lc = ch395f_link_change_count();
if (s_link_last != lc) {
if (s_link_last != 0xFFFFU) {
DBG_INFO("net: PHY link %s (changes=%u)",
ch395f_link_up_cached() ? "up" : "down", (unsigned)lc);
}
s_link_last = lc;
}
}
for (i = 0; i < NET_MAX_SOCKETS; i++) {
/* 取控制块(直接数组下标,无需越界判断) */
net_sock_t *p_sock = &s_net_socks[i];
/* 驱动状态缓存(net_poll 每轮刷新,本 tick 读到的新鲜度 <=1ms) */
ch395f_sock_state_t c = ch395f_sock_state_cached((uint8_t)i);
/* 芯片当前是否在 LISTEN(二段关闭中间态 05/00 不算在听) */
int chip_listening = ((c.open != CH395F_SOCKET_CLOSED) &&
(c.tcp_state == CH395F_TCP_LISTEN));
/* 监听型 fd 门(原 pump 认领口径:auto_relisten 是决定性门——少了它,
* "本层 CLOSED + 已绑端口"的出向连接会被误判为监听器并被 relisten) */
int listener_fd = ((p_sock->auto_relisten != 0U) &&
(p_sock->type == NET_SOCK_STREAM) &&
(p_sock->local_port != 0U));
/* 失同步类型:1=连接已失 2=握手已成事件未收到 3=监听失位 4=孤儿监听
* 5=芯片静默回收回监听(编号沿用历史口径,U11 重同步不设编号——被动零动作) */
int bad = 0;
/* 本形态去抖门槛(默认按重建档) */
uint8_t stable_need = NET_RECON_STABLE_RELISTEN;
if (p_sock->in_use == 0U) {
s_bad[i] = 0U;
continue;
}
if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
/* ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理 */
if ((c.open == CH395F_SOCKET_CLOSED) || (c.tcp_state == CH395F_TCP_CLOSED)) {
bad = 1;
stable_need = NET_RECON_STABLE_DEATH;
} else if ((c.open != CH395F_SOCKET_CLOSED) &&
(c.tcp_state == CH395F_TCP_LISTEN)) {
/* ⑤(F-NET-2 缺口,自愈收敛阶段1):芯片静默回收——本层 ESTABLISHED
* 而芯片已自行把 Socket 收回 LISTEN(0x05/0x01,**不发 DISCONNECT
* 中断**,run8 台架实证)→ RESYNC 语义 */
bad = 5;
}
} else if (p_sock->state == NET_SOCK_STATE_LISTENING) {
if (c.tcp_state == CH395F_TCP_ESTABLISHED) {
/* ② 本层 LISTENING 而芯片已完成握手(CONNECT 事件未收到)→ 补做 accept
* N-30(TRAP-NET-20)幽灵态甄别:拆链收尾窗口内旧连接未拆净,状态如实
* 显示 ESTABLISHED(E15:bit3=1 保留现场契约 + TCP 拆链状态机,非谎报),
* 回读对端端口 = 上一条连接端口 → 不补 accept(补了会把数据发给
* 已死的旧连接 = 生产 LIST#4 数据被吞的直接成因),等拆净或新 SYN。
* 这是本 tick 唯一的按需 SPI(仅形态②命中时发) */
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;
stable_need = NET_RECON_STABLE_ACCEPT;
}
} else if (!chip_listening && listener_fd) {
/* ③ 本层 LISTENING 而芯片监听失位 → 重建(原 pump①) */
bad = 3;
}
} else if ((p_sock->state == NET_SOCK_STATE_CLOSED) && listener_fd) {
if (chip_listening) {
/* U11:本层 CLOSED 而芯片在听 = 本层状态滞后 → 只重同步。
* **不发任何芯片命令**(重下发 open+listen 会因 USE 错误走进
* 关闭分支、把正听着的 Socket 关掉,U12 同源事故)。
* 被动修正零风险:不去抖、不占冷却(原 pump 同口径) */
DBG_INFO("sock%d reconcile: chip listening, resync CLOSED->LISTENING (U11)", i);
p_sock->state = NET_SOCK_STATE_LISTENING;
/* N-30(TRAP-NET-20):死连接对端端口转登幽灵甄别基准 + 探针计数清零
* —— 芯片监听器未被重开,芯片侧对端信息可能仍是上一条连接的,
* 必须转登为基准,否则形态② 的幽灵拦截会失效 */
p_sock->last_peer_port = p_sock->remote_port;
p_sock->lost_conn_seen = 0U;
s_bad[i] = 0U;
continue;
}
/* ④ 本层 CLOSED(会话已结束)而芯片不在 LISTEN → 重建(原 pump②) */
bad = 4;
} else {
/* 其余状态(CREATED/BOUND/UDP_OPEN 等)不归本 tick 认领 */
s_bad[i] = 0U;
continue;
}
/* 无失同步:清零计数,转下一 socket */
if (bad == 0) {
s_bad[i] = 0U;
continue;
}
/* 累加连续失配计数(封顶至本形态门槛) */
if (s_bad[i] < stable_need) {
s_bad[i]++;
}
/* 去抖未满或处于冷却期:本轮不动手 */
if ((s_bad[i] < stable_need) ||
((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 后由形态③④重建监听) */
DBG_INFO("sock%d reconcile: chip closed (0x%02X/0x%02X) -> disconnect",
i, c.open, c.tcp_state);
/* 复用断开状态机(含陈旧事件甄别 + 状态迁移 + 事件派发);
* 只置事实,重建由本 tick 形态③④统一做(N-32 P2 单一所有者) */
handle_disconnect_event(p_sock);
} else if (bad == 5) {
/* 类型⑤:静默回收 → RESYNC 语义(原 NET_MSG_RESYNC_LISTEN 分派体
* 原样并入,自愈收敛阶段1;纪律同 U11:**不发任何芯片命令**——
* 重下发 close/重监听会摧毁芯片侧健康监听器 → 端口永拒,
* run8 实证 170s 拒绝) */
DBG_INFO("sock%d reconcile: chip re-listened w/o DISCONNECT (0x%02X/0x%02X) -> resync",
i, c.open, c.tcp_state);
/* 仅修 MCU 侧;新连接的 CONNECT 中断会把两侧重新同步 */
p_sock->state = NET_SOCK_STATE_LISTENING;
/* N-30(TRAP-NET-20):死连接对端端口转登幽灵甄别基准 + 探针计数清零 */
p_sock->last_peer_port = p_sock->remote_port;
p_sock->lost_conn_seen = 0U;
/* 取消本 fd 在死连接上的挂起 recv(必通知,防调用方挂死) */
net_pending_cancel(i, NET_ERR_CONNRESET);
} else if ((bad == 3) || (bad == 4)) {
/* 类型③④:监听失位/孤儿监听 → 重建(本层唯一重建入口;
* 非阻塞:失败由 D10 重试轮询接管) */
DBG_INFO("sock%d reconcile: chip not listening (0x%02X/0x%02X, was state=%d) -> relisten",
i, c.open, c.tcp_state, (int)p_sock->state);
(void)net_listener_try(i);
} 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 重监听 / 挂起 recv /
* TRAP-NET-17 状态对账),再非阻塞排空队列按 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 总时限到期放弃留痕;本 fd 有在途挂起 recv 时跳过(挂起 recv 保险)
* 3. net_pending_tick:按节拍结束挂起 recv(数据/到点[仅显式预算]/断连);
* 挂起超时留痕探针(编译期选项,默认关):挂起超阈值只 DBG_ERROR 留痕一次,绝不强制结束/伪造返回值
* 4. net_reconcile_tick(TRAP-NET-17 + pump 合并,自愈收敛阶段 3):每 100ms 对账
* ①②⑤ + ③④ 重建 + U11 重同步,判据读驱动状态缓存(net_poll 每轮
* 全量刷新,E2 背书);去抖分级(②=1 轮/③④⑤=2 轮/①=4 轮,M3 让位窗)
* + 1s 冷却;重建失败进 D10 重试队列,reconciler 不重复催
* 5. net_msg_t 按值入队,netTask 侧副本调用方不可见 → 结果只能搭任务通知值回传
* 6. 32 位整体即结果,不做位域压缩
* 7. RECV 特例:无数据+阻塞 → 登记 pending 哨兵 NET_PENDING_REGISTERED(0x70000000),
* do_notify=0,回执推迟到 net_pending_tick 完成时
*/
void net_process_messages(void) {
net_msg_t msg;
/* 队列句柄未就绪(net_init 未完成):直接返回 */
if (netMsgQueueHandle == NULL) {
return;
}
/* 三个非阻塞自愈 tick 置于消息处理之前:断连后即使无应用消息,监听器也能自主恢复 */
/* D10 重监听重试(TRAP-NET-09):沉降后重开芯片监听器 */
net_listener_retry_tick();
/* 挂起 recv 完成检查:数据到达 / 到点 / 断连时通知调用方 */
net_pending_tick(s_net_socks);
/* TRAP-NET-17 状态对账 + 监听器 pump(自愈收敛阶段 3 合并单一 100ms tick):
* 判据读驱动缓存(net_poll 每轮全量刷新),①②⑤ 失同步 + ③④ 重建 +
* U11 重同步全部在此(本层唯一周期重建入口,N-32 P2 单一所有者) */
net_reconcile_tick();
/* 队列排空:超时 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_DROP_CONN: {
/* 自愈收敛阶段 3(E-02 收口):应用放弃会话的主动拆除(拆连接、保监听) */
net_sock_t *p_sock = net_get_sock(msg.sockfd);
if (p_sock == NULL) {
/* 描述符无效 → BADF */
net_set_errno(NET_ERR_BADF);
msg.result = -1;
} else if (!p_sock->in_use || p_sock->type != NET_SOCK_STREAM) {
/* 不在用 / 非 TCP 流 → INVAL */
net_set_errno(NET_ERR_INVAL);
msg.result = -1;
} else if (p_sock->state != NET_SOCK_STATE_ESTABLISHED &&
p_sock->state != NET_SOCK_STATE_CONNECTING) {
/* 无活动会话:幂等成功(监听原样,不动芯片) */
msg.result = 0;
} else {
msg.result = net_drop_conn_locked(p_sock);
}
break;
}
/* 自愈收敛阶段1(F-NET-3):NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 两个
* 手动自愈消息已删——RESYNC 语义并入对账⑤(net_reconcile_tick),
* RELISTEN 与 reconciler/D10 完全重叠且对健康监听器是 footgun;
* 生产调用者本就为零(仅 test/board Phase 13,已改由自动机制接管) */
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;
/* 自愈收敛阶段 1.5:阻塞模式默认 = 阻塞(BSD/Python 语义;非阻塞用
* net_setblocking(fd, 0) 显式设置)。close 的 memset 会清零本字段,
* 新 fd 必须在此显式置 1(同 released_abnormal 的"不得继承"纪律) */
p_sock->blocking = 1;
/* 新 fd 不得继承上一条连接的释放原因(TRAP-NET-18 待办 #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 请求 */
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) */
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;
}
/*
* 函数功能:驱动 sock_reset 原语的有界确定性排渣等待(P2b 收口,自愈收敛阶段 3;
* 替代原"close + 盲等 500ms"式忙等)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* mode - CH395F_RESET_GRACEFUL(等 100ms)/ QUARANTINE(等 400ms)
* 出口参数:无
* 返回值:0 - 复位完成(芯片 byte0=00H、事件已排空);-1 - 启动失败或 600ms 超时 int
* 限定条件:仅 netTask 上下文(SPI 串行契约);调用前本层已放弃该会话
* 函数说明:1. 原语本身非阻塞(begin/poll 分步),本包装是**同步 API 路径**
* (listen open 重试 / close)用的有界等待:2ms 节奏 poll,600ms 封顶
* 2. 内部序列=E4 定案:读空 RX -> CLOSE -> 排空事件(quiescent) ->
* 轮询 byte0=00H;WAIT_CLOSE 内置 LISTEN 态二段 CLOSE 补发
* (E2 补充发现:一次 CLOSE 只拆监听 byte0 恒 05)
* 3. 超时留痕返 -1,调用方按原语义兜底(TRAP-NET-09 重试环 / close 放行)
*/
static int net_sock_reset_sync(int sockfd, ch395f_reset_mode_t mode) {
/* 起始时刻(600ms 上限基准) */
uint32_t t0 = HAL_GetTick();
/* 状态机步进返回值 */
int rc;
/* 启动状态机(本 fd 已在运行返 -1:netTask 串行上下文不应发生) */
if (ch395f_sock_reset_begin((uint8_t)sockfd, mode) != 0) {
return -1;
}
/* 有界等待:2ms 节奏步进(每步仅短 SPI 事务,netTask 无长忙等) */
for (;;) {
rc = ch395f_sock_reset_poll((uint8_t)sockfd);
/* 0=完成 / -1=失败(槽位已复位可重开):都退出等待 */
if (rc != 1) {
break;
}
if ((uint32_t)(HAL_GetTick() - t0) > 600U) {
DBG_ERROR("sock%d sock_reset_sync timeout (600ms)", sockfd);
return -1;
}
osDelay(2);
}
return (rc == 0) ? 0 : -1;
}
/*
* 函数功能:启动 TCP Server 监听(内部版本,必须在 netTask 上下文调用)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 出口参数:无
* 返回值:0 - 成功,-1 - 失败 int
* 限定条件:sockfd 已绑定且为 TCP;netTask 上下文
* 函数说明:1. 芯片侧 open + TCP_LISTEN(端口=控制块 local_port;open 失败重试 3 次,见 TRAP-NET-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-NET-09),故失败重试最多 3 次。
* P2b 收口(自愈收敛阶段 3):原"close + 盲等 500ms"改**确定性排渣**——
* sock_reset(读空 RX -> CLOSE -> 排空事件 -> 等 byte0=00H,内置 LISTEN
* 态二段 CLOSE 补发;E4 定案 60 轮实证复位后 open 必成)。GRACEFUL
* 最短等 100ms ≪ 原 500ms 盲等;复位失败/超时仍走下一轮重试,TRAP-NET-09 语义保留 */
for (int open_try = 0; open_try < 3; open_try++) {
/* 尝试打开芯片侧 Socket */
status = ch395f_open_socket(sockfd);
if (status == CH395F_ERR_SUCCESS) {
/* 打开成功,退出重试 */
break;
}
/* 打开失败:确定性复位本 Socket(排渣/二段关闭/等 CLOSED 一体) */
(void)net_sock_reset_sync(sockfd, CH395F_RESET_GRACEFUL);
}
/* 3 次仍失败,返回错误 */
if (status != CH395F_ERR_SUCCESS) {
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 排事件(读即清)必须在 LISTEN 命令**之前**(E14 复核实验 2026-10-14:
* 原形态为 listen 后读清——快速复连时新连接的 CONNECT 在 LISTEN 生效后、
* 读清前落进 SINT,被本方读清吞掉,即"CONNECT 事件未收到→数据被吞"的
* 生产侧自伤机制(台架矩阵 3/48 被 drain-int 探针现场抓获);芯片侧
* 从不丢事件。挪前后若仍有滞后 DISCONNECT 落进新会话,
* handle_disconnect_event 的 LISTENING 分支回读状态裁决(状态面为真相),
* 无害。
* 读即清:仅本 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-NET-08)。
* 全局寄存器交 net_poll 集中读清(AGENTS 模块约定:所有 CH395F 中断经
* net_poll 集中读取并清除);本 Socket 残留的全局位被 net_poll 读到时
* SINT 已为 0,自然收尾。TRAP-NET-12/-18 指定的两级预清落在
* net_connect_locked / net_close_locked / relisten 隔离段,均为单 Socket,
* 本次未动。 */
(void)ch395f_get_sock_int_status((uint8_t)sockfd);
/* 下发 TCP LISTEN 命令,进入监听状态 */
status = ch395f_tcp_listen(sockfd);
/* LISTEN 失败 → BUSY */
if (status != CH395F_ERR_SUCCESS) {
/* 0-3(自愈收敛阶段0,U12 对称):open 已成而 LISTEN 失败必须 close,
* 否则芯片 Socket 卡"OPEN 无监听"、后续 open 恒返 USE(同
* net_relisten_locked 对应分支) */
ch395f_close_socket(sockfd);
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-NET-20):新监听会话,事件未收到去抖计数清零 */
p_sock->lost_conn_seen = 0U;
/* 监听建立完成 */
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 台架实证;
* E14 复核 2026-10-14:事件非芯片侧丢失,是被读清消费——机制不变)=====
* 台架结论(剥离 OS/net/FTP 直接对芯片做受控 close→open→listen→connect 矩阵):
* 芯片关闭连接后的拆链收尾窗口**变长且不可见**(状态寄存器显示 CLOSED 也不代表
* 拆完,实测 >2s 仍可能未拆完);该窗口内重新 listen 后,新连接握手照常完成
* (芯片状态寄存器 → ESTABLISHED)但 **CONNECT 事件未收到**(E14 复核:被读清
* 消费竞态吞掉)——纯事件驱动的 accept
* 永远等不到 → 数据被吞 / 425。固定沉降延时、Socket 轮换、listen 后 close 重试、
* 缓冲重分配均已被实验证伪;唯一有效 = "中断 + 状态轮询"双信源(修复组 15/15
* 全过,机制触发 5 次全部救回)。→ CH395F_Trap_Records.md TRAP-NET-20
* 幽灵态甄别:拆链窗口内旧连接未拆净,状态如实为 ESTABLISHED(E15:bit3=1 保留现场
* 契约,非谎报),此时回读
* 对端端口 = 上一条连接端口;新连接临时端口几乎必然不同 → 端口相同不 accept,
* 等旧连接拆净 / 新 SYN 到达(新连接若事件也未收到,后续轮询以新端口命中)。 */
/*
* 函数功能:监听等待期的芯片状态轮询探针——芯片已 ESTABLISHED 而 CONNECT 事件
* 未收到时补做 accept(复用 CONNECT 事件簿记路径)。
* 入口参数:p_sock - 处于 LISTENING 的监听控制块 net_sock_t *
* 出口参数:无(命中时 p_sock->state 经 handle_connect_event 转 ESTABLISHED)
* 返回值:无
* 限定条件:netTask 上下文(net_accept_locked 内调用,其开头已 net_poll(),
* 驱动缓存新鲜度有保证)
* 函数说明:1. 自愈收敛阶段 3(F-MS-9):判据改读**驱动状态缓存**(零 SPI;唯一
* 刷新点=net_poll),**探针去抖删除**——原去抖防的两类瞬态已
* 结构性消除:"刚 listen 尚未生效"显示 TCP=00 永不命中 ESTABLISHED
* 门(E2:LISTEN 可见 100% <=10ms 无长尾);拆链窗假 ESTABLISHED
* 由幽灵甄别拦截。NET_LOST_CONN_STABLE_N 随之下线
* 2. 幽灵态(对端端口 = last_peer_port)不动手,计数清零
*/
static void net_lost_connect_probe(net_sock_t *p_sock) {
/* 对端 IP+端口回读缓冲(4B IP + 2B 端口小端) */
uint8_t remote[6] = {0, 0, 0, 0, 0, 0};
/* 驱动状态缓存(net_poll 每轮刷新;本探针入口前 accept 已 net_poll()) */
ch395f_sock_state_t c = ch395f_sock_state_cached(p_sock->ch395_sock);
/* 芯片尚未完成握手:可疑电平计数清零,本轮无动作 */
if (c.tcp_state != CH395F_TCP_ESTABLISHED) {
p_sock->lost_conn_seen = 0U;
return;
}
/* 已 ESTABLISHED:回读对端端口做幽灵甄别(按需 SPI,仅命中态发) */
ch395f_get_remot_ipp(p_sock->ch395_sock, remote);
/* 对端端口(小端 2 字节) */
uint16_t peer = (uint16_t)(remote[4] | (remote[5] << 8));
/* 幽灵甄别:仅"端口非零且 = 上一条连接端口"才判幽灵拦截;
* peer==0(芯片对端端口提交滞后,TRAP-NET-13 同类)不拦截——宁可补 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;
}
/* 自愈收敛阶段 3(F-MS-9):去抖已删(判据读缓存,两类瞬态结构性消除,
* 见函数头注释 1);lost_conn_seen 仅作 M3 可疑电平标志用,动手前清零 */
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-NET-20)
* 3. CLOSED + 芯片 RX 缓冲有残留数据:接受"已关但可读"连接排空
* (FUN_PARA §5.52 ② 保留现场契约,TRAP-NET-20 三次定案)
*/
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-NET-20):监听等待期兼做芯片状态第二信源——CONNECT 事件未收到时
* 去抖补 accept(台架实证:拆链窗口事件未收到是数据被吞/425 的主根因,
* E14 复核:事件非芯片丢失、被读清消费吞掉;纯事件等待无自愈可能;
* 详见 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;
}
/* 自愈收敛阶段1(F-NET-4):net_can_accept 已删除——它只认 ESTABLISHED,作为 accept
* 门控会把"监听器在服役但本轮还没 CONNECT"的窗口全挡掉(:2404 CONNECT 事件补 accept 探针
* 因此从不运行,与已修的 :7003 探针失效同型);唯一生产调用者 iec104 已迁
* net_listener_armed。net_can_read(RECV 门控)不受影响、保留。 */
/*
* 函数功能:查询监听 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,自愈收敛阶段1 删除)作为监听 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;
}
/* 连接已到、应用尚未 accept:单连接模型下 CONNECT 处理(net_poll 分派 /
* 探针补 accept / 对账② standalone accept)把监听 fd 转 ESTABLISHED,
* 这就是"待 accept"电平——**必须放行**(原 net_can_accept 唯一放行态;
* 自愈收敛阶段1 i104 台架实证:200ms 任务周期抢不过对账②的 ~400ms 去抖,
* 对账②先转 ESTABLISHED → 不放行则应用永不 accept、会话整场饿死)。
* 无 RPC 风暴风险:此态下 accept 必返 fd,一次调用即离开本分支 */
if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
return 1;
}
/* 正常监听态:放行。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
* 窗口被拉长到 reconciler 周期 + 隔离期(~100ms),:21 0ms×50 期间出现 48 条
* `[WD] task stall`(mask=0x67/req=0x77,缺 bit4=UI)。
* 重建本身由 net_reconcile_tick 形态③④ 负责,调用方**无需**在此催促。
* 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-NET-17 对账不含"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 次,每次失败走 sock_reset 确定性排渣,
* 见 TRAP-NET-09 / P2b)+ 配置源/目的端口与目的 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-NET-09),故失败重试最多 3 次。
* P2b 收口(自愈收敛阶段 3):原"close + 盲等 500ms"改**确定性排渣**——
* sock_reset(读空 RX -> CLOSE -> 排空事件 -> 等 byte0=00H,内置 LISTEN
* 态二段 CLOSE 补发;E4 定案 60 轮实证复位后 open 必成)。GRACEFUL
* 最短等 100ms ≪ 原 500ms 盲等;复位失败/超时仍走下一轮重试,TRAP-NET-09 语义保留 */
for (int open_try = 0; open_try < 3; open_try++) {
/* 尝试打开芯片侧 Socket */
status = ch395f_open_socket(sockfd);
if (status == CH395F_ERR_SUCCESS) {
/* 打开成功,退出重试 */
break;
}
/* 打开失败:确定性复位本 Socket(排渣/二段关闭/等 CLOSED 一体) */
(void)net_sock_reset_sync(sockfd, CH395F_RESET_GRACEFUL);
}
/* 3 次仍失败 → BUSY */
if (status != CH395F_ERR_SUCCESS) {
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 清掉本 Socket 可能残留的中断状态,丢弃上一条连接遗留的 DISCONNECT。
* F-NET-7(自愈收敛阶段 3):只清**本 Socket** SINT(驱动护栏原语)——原
* "再读清全局"会焊死其它在用 Socket 的事件位(全局寄存器唯一读者=net_poll) */
(void)ch395f_sock_events((uint8_t)sockfd);
/* 设置本地(源)端口 */
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-NET-16),
* 否则单次写 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-NET-16)分块写入 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-NET-16:防写穿 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)
/* 延迟探针起点(chunk_wait):发送缓冲写入刚完成
* (样本 = 写缓冲 → SENBUF_FREE 等待,见 链路延迟测试说明 §3) */
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 = 无限等待,自愈收敛阶段 1.5 起无默认值)uint32_t
* caller - 调用方任务句柄(挂起请求的完成通知目标) TaskHandle_t
* 出口参数:buf - 填充接收到的数据
* 返回值:实际接收字节数(> 0);0 表示对端关闭;-1 表示出错/非阻塞无数据 int
* NET_PENDING_REGISTERED 表示已登记挂起请求(分派据此跳过即时通知)
* 限定条件:p_sock 非空;netTask 上下文
* 函数说明:1. 数据已在(快速路径)→ 立即读出返回
* 2. 非阻塞(NET_MSG_DONTWAIT 单次覆盖 **或** socket blocking 属性为 0,
* 自愈收敛阶段 1.5 模式属性)无数据立即返回 -1(NET_ERR_WOULDBLOCK)
* 3. 无数据 + 阻塞等待 → 登记挂起请求(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);
}
/* 超时来源:只有调用方显式预算(net_recv_to);0 = 无限等待
* (自愈收敛阶段 1.5:NET_RECV_TIMEOUT_MS 30s 默认预算已删,API 不强加隐藏值) */
uint32_t to_ms = 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;
/* 已消费数据(部分读安全:手册 §5.34 未读数据保留可续读,旧"部分读归零
* 部分读归零"已降级为未证实历史观察;下一次 RECV_OK 重新置 readable) */
p_sock->readable = 0;
/* 返回本次读出字节数 */
return read_len;
}
/* 非阻塞模式:NET_MSG_DONTWAIT 单次覆盖,或 socket 的 blocking 属性为 0
* (net_setblocking(fd, 0),自愈收敛阶段 1.5 模式挂控制块属性) */
if ((flags & NET_MSG_DONTWAIT) || (p_sock->blocking == 0U)) {
/* 非阻塞且当前无数据:若连接已断开则视为 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)。周期性抢先由 reconciler 收敛即可。 */
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
* 继续主循环(事件泵 + 队列),其他消费者零头阻塞。 */
/* 登记挂起请求:返回值即登记结果 */
return net_pending_register(sockfd, buf, len, to_ms, caller);
}
/*
* 函数功能:关闭 Socket(内部版本,不经过消息队列)
* 入口参数:p_sock - 要关闭的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:0 - 关闭成功 int
* 限定条件:必须在 netTask 上下文中调用;p_sock 有效
* 函数说明:1. P2b 收口(自愈收敛阶段 3):原"close + 500ms 盲等排空循环"改
* **确定性 sock_reset**(读空 RX -> CLOSE(幂等) -> 排空本 Socket
* 事件(quiescent) -> 等 byte0=00H,内置 LISTEN 态二段 CLOSE 补发
* ——E2 补充发现:一次 CLOSE 只拆监听 byte0 恒 05)
* 2. F-NET-7:排空只碰本 Socket SINT,**不再读清全局中断寄存器**
* (唯一读者=net_poll;原循环读全局会焊死其它 Socket 事件位)
* 3. 快路径:芯片已 CLOSED(byte0=00H)→ 跳过复位只清残留事件
* 4. 复位超时照常放行(同原 500ms 超时容忍;后续 relisten 由
* TRAP-NET-09 open 重试环吸收)
*/
static int net_close_locked(net_sock_t *p_sock) {
/* 由控制块指针反推描述符 */
int sockfd = (int)(p_sock - s_net_socks);
/* 先结束本 fd 的挂起 recv(排空/取消 + 必通知),再关芯片 Socket
* ——关闭不得让等待中的调用方永久悬挂 */
net_pending_settle_close((int)(p_sock - s_net_socks), p_sock);
/* 排渣 + 关闭 + 等 CLOSED 一体(见函数头注释 1);
* 已 CLOSED 的 fd 只清本 Socket 残留事件(防下一连接误判"刚建立就断开") */
if (ch395f_cmd_done((uint8_t)sockfd, CH395F_DONE_CLOSE) == 1) {
(void)ch395f_sock_events((uint8_t)sockfd);
} else if (net_sock_reset_sync(sockfd, CH395F_RESET_GRACEFUL) != 0) {
DBG_ERROR("sock%d close: sock_reset not done, proceed anyway", sockfd);
}
/* 清零控制块(memset 0 = 释放本 fd 全部状态) */
/* N-30(TRAP-NET-20):幽灵甄别基准跨 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 的挂起状态(幂等) */
net_pending_clear(sockfd);
/* 关闭成功 */
return 0;
}
/*
* 函数功能:主动拆掉当前 TCP 会话、保留监听(内部版本,netTask 上下文)
* 入口参数:p_sock - 会话所在 Socket 控制块(调用方已判 ESTABLISHED/CONNECTING)
* 出口参数:无
* 返回值:0 - 拆除完成(监听型 fd 已重监听或交 D10) int
* 限定条件:netTask 上下文;p_sock 在用且为 TCP 流
* 函数说明:1. E-02 收口(《网络自愈架构设计》§5.2 E13):应用放弃的会话若 TCP 仍活
* (PC 内核自动 ACK keepalive,僵尸判不死),单连接监听被永久
* 占住;应用又不得 net_close 监听 fd(毁监听)——本函数补上
* "拆连接不拆监听"的应用侧形态
* 2. 拆除=sock_reset QUARANTINE(TRAP-NET-18 异常释放语义:FIN + 排渣 +
* 排空事件(quiescent) + 等 byte0=00H,内置二段 CLOSE 补发)
* 3. auto_relisten(监听型 fd):收口 CLOSED 后即时 net_listener_try
* (芯片已 CLOSED 直接 open 必成,E4 定案;失败交 D10 重试 +
* reconciler 形态④);非 auto_relisten(client fd):收口 CLOSED,
* 是否重连由调用方决定
* 4. 挂起 recv 先结束(settle_close:排空尝试或取消,必通知)
*/
static int net_drop_conn_locked(net_sock_t *p_sock) {
/* 由控制块指针反推描述符 */
int sockfd = (int)(p_sock - s_net_socks);
/* 先结束本 fd 挂起 recv(排空/取消 + 必通知),再拆芯片会话 */
net_pending_settle_close((int)(p_sock - s_net_socks), p_sock);
/* 确定性拆除(异常释放 → QUARANTINE 400ms 底,TRAP-NET-18 语义) */
if (net_sock_reset_sync(sockfd, CH395F_RESET_QUARANTINE) != 0) {
DBG_ERROR("sock%d drop_conn: sock_reset not done", sockfd);
/* 超时继续收口本层;芯片侧交 reconciler/pump 对账接管 */
}
/* 会话簿记收口(保留 fd:不 memset、不动 local_port/auto_relisten) */
p_sock->state = NET_SOCK_STATE_CLOSED;
p_sock->readable = 0U;
/* N-30(TRAP-NET-20):新会话起点,事件未收到去抖计数清零 */
p_sock->lost_conn_seen = 0U;
if (p_sock->auto_relisten) {
/* 监听型 fd:即时重建(非阻塞;失败 D10 重试轮询接管) */
(void)net_listener_try(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-NET-14)
* 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;
* **默认无限阻塞**——自愈收敛阶段 1.5 语义主流化,30s 默认预算已删)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位,0=按 socket 阻塞属性等待,NET_MSG_DONTWAIT=单次非阻塞 int
* 出口参数:无
* 返回值:实际接收字节数 int >= 0 || 0 || -1
* 0 - 对端关闭连接 int
* -1 - 接收失败(参数无效、非阻塞无数据=WOULDBLOCK、
* close/网络重建/陈旧重同步取消=CONNRESET 等,errno 可查) int
* 限定条件:Socket 处于 ESTABLISHED 状态;net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文
* 函数说明:1. 将接收请求放入消息队列,等待 netTask 处理完毕并返回结果。netTask 内请使用 net_recv_sock()
* 2. 无数据 + 阻塞(blocking 属性为默认值 1 且未置 DONTWAIT)→ 登记挂起
* **无限等待**;唤醒源 = 数据 / EOF / close / 网络重建(CONNRESET);
* 会话死亡由对账① ~400ms 判死兜底,不存在"对端消失后永久挂死"窗口
* 3. ⚠ 文档契约(嵌入式特有):IWDG required_mask 内任务禁止无限阻塞
* recv——必须 net_select 超时或 net_recv_to 显式预算(超时归使用者
* 对受监控任务是义务不是选项),否则"对端连上不发数据"= IWDG 复位
* 4. 超时首选 net_select(...);net_recv_to(...) 为显式预算便捷入口
*/
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=按 socket 阻塞属性,NET_MSG_DONTWAIT=单次非阻塞 */
msg.flags = flags;
/* 0 = 无限等待(自愈收敛阶段 1.5:无默认预算;显式预算走 net_recv_to) */
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;
}
/*
* 函数功能:接收数据(非阻塞具名入口——自愈收敛阶段 1.5 把 NET_MSG_DONTWAIT 路径转正,
* 对齐 BSD recv(MSG_DONTWAIT) / Python recv 非阻塞模式的"名如其义")
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* 出口参数:buf - 填充接收到的数据
* 返回值:实际接收字节数(> 0);0 = 对端关闭(net 层已自动重监听); int >= 0 || -1
* -1 = 错误(无数据 = NET_ERR_WOULDBLOCK,其余 errno 可查) int
* 限定条件:非 netTask 上下文;net 层已初始化
* 函数说明:1. 等价 net_recv(sockfd, buf, len, NET_MSG_DONTWAIT):单次强制非阻塞,
* 不改变 socket 的 blocking 属性(模式用 net_setblocking)
* 2. 受 IWDG 监控 / 有周期职责的任务(netTask 除外)的推荐默认入口;
* 需要"等但不无限等"用 net_select / net_recv_to
*/
int net_recv_nb(int sockfd, void *buf, int len) {
return net_recv(sockfd, buf, len, NET_MSG_DONTWAIT);
}
/*
* 函数功能:接收数据(带调用方指定超时的阻塞版,用于分片有界读;
* 自愈收敛阶段 1.5 起为**显式预算**便捷入口,等价 Python settimeout,
* 不再有隐藏默认值)
* 入口参数: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 等"等对端下一行/下一块数据"的调用方
* 若无限阻塞 recv,会把 netTask(全部 socket 操作的唯一执行者)长期占住
* 2. 导致其余任务的网络操作整体冻结(M3 实测一次空闲 FTP 登录冻结远端显示 17.7s)
* 3. 用本接口把等待切成 ≤timeout_ms 的片,调用方自行累计总预算:总超时语义不变、单次占用有界
* 4. 超时用独立返回码 -2(而非靠 errno 区分)→ 无跨任务 errno 竞态
* 5. ⚠ IWDG required_mask 内任务必须走本接口或 net_select(net_recv 契约)
*/
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 侧意味着"**无限等待**"
* (自愈收敛阶段 1.5 删除 30s 默认预算),恰与本接口"有界等待"的存在
* 理由相反,直接拒绝 ---- */
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;
}
/*
* 函数功能:主动拆掉当前 TCP 会话、保留监听(线程安全版本,经消息队列委托 netTask)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* 出口参数:无
* 返回值:0 - 成功(含幂等:无活动会话直接 0) int
* -1 - 失败(BADF/INVAL/入队失败) int
* 限定条件:1. 必须在【非 netTask】上下文调用(同其它 net_* API,netTask 内会自等死锁)
* 2. 场景=应用放弃会话(对时 30s 预算耗尽、协议超时);EOF 路径**不要**调
* (net 层 auto_relisten 已接管,见 AGENTS 单连接断开语义)
* 函数说明:1. 单连接模型下挂起但 TCP 仍活的客户端会永久占住监听(keepalive
* 被 PC 内核自动 ACK 判不死,《网络自愈架构设计》§5.2 E13);应用不得
* net_close 监听 fd → 本 API 拆连接不拆监听(E-02 收口)
* 2. netTask 侧执行 sock_reset QUARANTINE + 监听即时重建(见
* net_drop_conn_locked);等待期 netTask 有界占用 ~400-600ms
*/
int net_drop_conn(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);
}
/* 消息类型:拆会话保监听 */
msg.type = NET_MSG_DROP_CONN;
/* 目标 Socket */
msg.sockfd = sockfd;
/* 无超时 */
msg.timeout_ms = 0U;
/* 调用方任务句柄(netTask 凭它回传回执) */
msg.caller = xTaskGetCurrentTaskHandle();
/* 结果占位 */
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("dcp", sockfd);
if (msg.result < 0) {
/* 错误码经通知值回传;对外保持 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-NET-16) */
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. 无数据时(自愈收敛阶段 1.5,与 net_recv 同构):DONTWAIT / 非阻塞属性
* 立即返回 -1(WOULDBLOCK);阻塞模式**无限等待**(30s 默认预算已删;
* 本接口为轮询自旋实现且无生产调用者,受 IWDG 监控的任务勿用无限形态)
*/
int net_recvfrom(int sockfd, void *buf, int len, int flags,
struct net_sockaddr *src_addr, int *addrlen) {
/* 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);
}
/* 缓冲区无效 → INVAL */
if (buf == NULL || len <= 0) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 轮询直到:有数据 / 非阻塞(自愈收敛阶段 1.5:阻塞 = 无限等待,无默认预算) */
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;
}
}
/* 非阻塞模式:NET_MSG_DONTWAIT 单次覆盖,或 socket blocking 属性为 0
* (自愈收敛阶段 1.5,与 net_recv_locked 同判据):无数据立即返回 */
if ((flags & NET_MSG_DONTWAIT) || (p_sock->blocking == 0U)) {
/* 置 WOULDBLOCK */
return NET_ERR_RETURN_1(NET_ERR_WOULDBLOCK);
}
/* 阻塞模式:无数据继续轮询等待(自愈收敛阶段 1.5:无限等待,无默认预算——
* 旧 NET_RECV_TIMEOUT_MS 30s 兜底已删;唤醒源 = 数据,与 net_recv 的
* 差异是本接口为自旋实现,勿在受 IWDG 监控任务中使用无限形态) */
}
}
/*
* 函数功能:发送数据(内部版本,绕过消息队列,供 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. pending 化后: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 上下文,无队列往返);
* timeout_ms = 0(无限等待语义,但 netTask 自调用会被挂起登记拒绝 →
* 本接口实际仅 DONTWAIT / 非阻塞属性可用);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;
}
/*
* 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(自愈收敛阶段 1.5,模式挂控制块属性,
* 对齐 BSD O_NONBLOCK / Python setblocking)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* on - 1 = 阻塞(net_socket 默认);0 = 非阻塞 int
* 出口参数:无
* 返回值:0 - 设置成功 int
* -1 - 失败(描述符越界 / 未占用) int
* 限定条件:socket 已创建(in_use == 1);任意上下文(同 net_set_event_cb 先例)
* 函数说明:1. 属性对 net_recv / net_recv_sock 生效:非阻塞下无数据立即
* NET_ERR_WOULDBLOCK;EOF 判定语义不变(CLOSED/session_dead 无数据
* 仍返回 0 并触发自动重监听)
* 2. 直写控制块单字节(ARM 原子写,先例 net_set_event_cb):与 netTask
* 竞态的最坏结果 = 一次在途 recv 用旧模式,无撕裂、无需过消息队列
* 3. 与超时预算正交:模式是粘滞属性;单次预算用 net_recv_to / net_select
*/
int net_setblocking(int sockfd, int on) {
/* 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);
}
/* 写模式属性:非 0 = 阻塞,0 = 非阻塞(单字节原子写) */
p_sock->blocking = (on != 0) ? 1U : 0U;
/* 设置成功 */
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;
}
/* 自愈收敛阶段1(F-NET-3):手动自愈 API net_resync_listen / net_relisten 已删除——
* RESYNC 语义并入对账⑤(net_reconcile_tick 自动认领"芯片静默回收回 LISTEN"形态,
* 200ms×2 去抖 + 1s 冷却沿用);RELISTEN 与 reconciler/D10 完全重叠且对健康监听器是
* footgun(无条件 open→USE→close 自毁 ~1s,原注释自认)。生产调用者本就为零
* (仅 test/board Phase 13,已改由自动机制接管)。历史实现见本文件 git 历史。 */
/*
* 函数功能:将点分十进制 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 转为主机字节序再格式化输出
* 2. ⚠ 段序(E13 定案 修复):NTOHL 后主机序数值的高字节才是
* 点分第一段(192.168.1.100 → 0xC0A80164 → "192.168.1.100")。
* 原实现低段在前,显示成字节反转的 "100.1.168.192"
*/
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 >> 24) & 0xFF),
(unsigned long)((naddr >> 16) & 0xFF),
(unsigned long)((naddr >> 8) & 0xFF),
(unsigned long)(naddr & 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;
/* F-NET-17 M3 前置:清可疑电平——探针(LISTENING 期)残留计数会带进新会话,
* 不补此清零则 M3 矛盾判据首轮即被旁路(T6 实测 grep:connect 原不清此字段) */
p_sock->lost_conn_seen = 0U;
/* 新开 Socket 发送缓冲按空闲计 */
p_sock->send_ready = 1;
/* 清除残留的 TIMEOUT 中断标志 */
/* 读即清:本 Socket 中断状态寄存器 */
(void)ch395f_get_sock_int_status(p_sock->ch395_sock);
/* §5.56:TCP 服务器须在 CONNECT 后开启 KeepAlive。2026-10-12 曾加后
* 撤回(当时仅证明"物理断链"已被 +2s TIM_OUT 快速覆盖,其余场景
* 无法注入、未证明);E11(2026-10-13,《网络自愈架构设计》)用"禁用 PC 网卡"
* 注入"链路在、对端静默"(NAT/黑洞)补上证据链:生产参数 60/5/3
* 实测 ≈30 s 判死 → TIM_OUT → QUARANTINE → reconciler 重建;对端存活时
* 探测被 ACK、空闲会话不误杀。消费链路现成,按手册位置转正。 */
ch395f_set_keepalive_enable(p_sock->ch395_sock, 1);
/* 触发连接建立事件回调 */
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;
/* F-NET-17 M3 前置:清可疑电平(同 LISTENING 分支) */
p_sock->lost_conn_seen = 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),不得跳过
* 显式 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-NET-17 / N-6:监听态收到 DISCONNECT——bit3=1(0x08)下芯片**不**关该 Socket(停 0x05/0x00,芯片 TCP 侧已 CLOSED);不重开则此后所有 SYN 全被拒 = 监听永久死亡;
* 触发 = 断连后 <~1s 快速复连时 CONNECT 中断未上报(控制块滞留 LISTENING)后该连接断开(2026-09-23 生产固件复现);
* N-32 P2:本分支**只回读留痕、不再 relisten** —— 重建统一交
* net_reconcile_tick 形态③(每 100 ms 读驱动缓存,监听失位即重建)。原处直接
* relisten 是 12 个所有者之一,且位于 DISCONNECT 事件路径,可能"抢先"
* 于消费者 recv(P1 已消除其后果,P2 消除其可能)。
* ⚠ 本留痕回读保持**主动 SPI**(不用驱动缓存):事件路径上缓存必然滞后
* 于刚到达的中断(刷新在 net_poll 末尾、事件处理之后),仅周期对账可用缓存 */
/* 回读缓冲(芯片状态 2 字节) */
uint8_t st[2] = {0, 0};
/* 回读芯片状态(仅为留痕:重建与否由 reconciler 裁决) */
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), reconciler 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;
}
/* F-NET-17 M3(自愈专项实验 2026-10-13):矛盾态可疑门——芯片 Socket 仍 OPEN(0x05)
* 而 TCP 槽 0x00 且本层 ESTABLISHED = 逻辑矛盾(活连接应 0x04):陈旧 DISCONNECT
* (拆链完成滞后锁存,TRAP-NET-12)与"真断开恰被旧拆链覆写槽"在 D20 上不可分辨
* (结构性失明,F-NET-15 Q1)。处置:不吃边沿,置可疑电平返回,交对账①
* (ESTABLISHED + 芯片 TCP==CLOSED)电平去抖再入本函数(自愈收敛阶段 3 合并
* 后=4×100ms,NET_RECON_STABLE_DEATH,让位租约 400ms 不变 + 冷却):
* flag==1 旁路本门 → 正常释放;连接真活则租约内
* 会话正常完成(风暴链掐断,E9a n30 0/50 实证)。
* 零额外 SPI 于常规路径(st[] 已读;recv_len 仅可疑分支多读一条)。
* ⚠ E9a/E9b 实测两轮收窄:①必须含 recv_bytes==0——已读数据的会话遇
* 0x05/0x00 多为真死(探针控制通道教训);②必须含芯片 RX>0——延迟保护的
* 本质是"在途投递不可毁"(closed-with-data 救回同源红线:RX 有数据=消费者
* 资产)。@0ms 快复连(连上即关、零数据,E9b :21 46/50 教训)RX 空 → 立即
* 释放,误杀也无数据损失,后到数据由救回路径接住。边界:滞后锁存杀
* "已读数据或 RX 空"会话的形态不在本门保护域(E8 窗口未出现该受害形态)。
* 零误伤:健康 FIN=0x07、静默回收=0x00/0x00 均不命中(E5 20/20 样本) */
if ((st[0] == CH395F_SOCKET_OPEN) && (st[1] == CH395F_TCP_CLOSED) &&
(p_sock->lost_conn_seen == 0U) && (p_sock->recv_bytes == 0U) &&
(ch395f_get_recv_len((uint8_t)sockfd) > 0U)) {
/* 置可疑电平(复用探针字段,零 RAM;connect/relisten 清零) */
p_sock->lost_conn_seen = 1U;
/* 留痕(INFO 级:这是预期自愈路径,非错误,勿污染 [ERR] 台架判据) */
DBG_INFO("sock%d suspect DISCONNECT on live conn (chip 0x%02X/0x%02X), defer to reconcile",
sockfd, st[0], st[1]);
/* 不释放、不登记沉降、不动 session_dead——保留 400ms 租约窗口 */
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-NET-18 推广(2026-09-24 台架实证):正常 DISCONNECT 释放留下的芯片收尾状态
* 会在复用同一 fd 的下一条连接上以"伪 DISCONNECT"形式投递(实测 accept 后约 10~20ms)→ 该连接被提前释放,应用把已到的 2048B 当整份文件并回报 226;
* 仅**本连接收过数据**时登记沉降(实测触发源 = 收过数据的数据通道被关闭),纯连接即关/只发不收的连接免沉降,保住 TRAP-NET-17 的快速复连时序(:21 @0ms ×50 全通过) */
/* F-NET-15 M1(自愈专项实验 2026-10-12):沉降**无条件**登记。原 recv_bytes!=0 门的
* 受害者由事件时序定义(accept 后 ~20ms 被陈旧 DISCONNECT 杀 → recv_bytes==0 是
* 构造性必然),门对"残渣循环自持"形态失效(n30_repro 背靠背 9/10 失败,E1 实测);
* 门原保护目标(:21 @0ms 快复连)已被 N-32 单一重建入口延迟化(恢复窗实测 0.3~1s)架空,
* +100ms 沉降落在窗内不可见。沉降窗口同时让残渣 DISCONNECT 在 state==CLOSED
* (无害、仅留痕)时被消费,断开"杀下一条 → 新拆链 → 再杀"的繁殖链 */
{
/* 反推描述符(沉降按 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-NET-20):登记本连接对端端口(幽灵态甄别基准,须在状态
* 迁移前记录——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(省一次 SPI 查询)
*/
static void handle_recv_event(net_sock_t *p_sock) {
/* RECV_OK 中断已触发,直接通知上层。不在此读 recv_len,
* 单一读取点在 net_recv_locked(省 SPI;原"部分读归零"顾虑已降级) */
/* 置可读标志,供 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-NET-18:异常释放(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-NET-18 待办 #3:标记异常释放——挂起 recv 排空断连后以
* NET_ERR_CONNRESET 结束(而非 EOF),应用(lftpd STOR)回报 5xx
* 而非误报 226(见 net_pending_tick 断连分支) */
p_sock->released_abnormal = 1U;
/* N-30(TRAP-NET-20):异常释放同样登记对端端口(幽灵甄别基准) */
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;
}
/*
* 函数功能:获取 Socket 对端地址(IP + Port)—— E13 定案 lftpd 对端显示收口
* 入口参数:sockfd - socket 描述符 int
* 出口参数:addr - 输出:对端地址 struct net_sockaddr *
* addrlen - 输入输出:地址结构体长度 int *
* 返回值:0 - 成功,-1 - 失败(NOTCONN:尚无 CONNECT 事件回读过对端) int
* 限定条件:socket 已连接(standalone accept / connect 成功后)
* 函数说明:1. 数据源=CONNECT 事件时 ch395f_get_remot_ipp 回读并缓存的
* remote_ip/remote_port(net_types.h),**不发 SPI**
* 2. 单连接模型下监听 fd 转数据通道后同样有效(对端=刚接入的客户端)
*/
int net_getpeername(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];
/* 未在用 → BADF */
if (!p_sock->in_use) {
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);
}
/* 尚无对端(未连接/未收到 CONNECT 回读)→ NOTCONN */
if (p_sock->remote_port == 0U) {
return NET_ERR_RETURN_1(NET_ERR_NOTCONN);
}
/* 强转为 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->remote_port);
/* 对端 IP:CONNECT 时已按网络序缓存,直传 */
p_addr_in->sin_addr.s_addr = p_sock->remote_ip;
/* 回填结构体长度 */
*addrlen = sizeof(struct net_sockaddr_in);
/* 成功(0) */
return 0;
}