Files
STM32F4-Base/Drivers/BSP/NET/net_socket.h
T
wandering1 7cb5af05fe feat(net): N-32 P2a 监听器 pump 单一所有者(周期性重建收敛)
把"周期性重建监听"收敛到唯一入口 net_listener_pump,消灭"谁先看到
DISCONNECT 就谁重建"的竞争;对账③④ 整体移交 pump,对账只剩①②。

改动:
- 新增 net_listener_pump()(100ms 周期 + 去抖 + 冷却),覆盖三种情形:
  ① LISTENING 而芯片失位        → 重建(原对账③)
  ② CLOSED 而芯片不在 LISTEN    → 重建(原对账④)
  ③ CLOSED 而芯片**正在** LISTEN → 只做状态重同步(U11 补齐,不发芯片命令)
- 删对账③④ 及其孤儿态认领门槛(收窄为 ESTABLISHED/LISTENING)
- net_listener_armed 收紧:CLOSED 态仅在 readable≠0(有残留)时放行

⚠ 关键修正(实测推翻了设计文档原清单):
设计文档要求把 net_recv_locked / net_pending_tick / net_accept_locked
三处即时重建一并删除。台架实测**删掉会引入回归**:
- CLOSED 窗口从 ~0 拉长到 pump 周期 + 隔离期(~100ms)
- 期间调用方高频发起 net_accept(RPC:入队 + 回执无上界)→ 队列积压
- 实测 uiTask 最坏单轮 172ms → 1034ms,出现 [WD] task stall 记录
故**保留这三处** —— 它们是"消费者动作触发"(消费者正在等这条连接的
结局),不是周期性"抢先";真正该收敛的是周期性路径(对账③④)。
收敛后 net_relisten_try 调用点 7 → 5(pump + 显式消息 + 3 处消费者驱动)。

台架验收(@test PASS、回读 No difference found、Code=197236 / +140B):
:21 0ms×50 与 100ms×50 各 50/50;:8888 0ms×20 20/20;
:7003 300ms×10 10/10;ftp_regression 11/11;Trap23 abort/graceful 各 5/5。
设备侧 orphan listener 增量 **0**(P1 基线同条件实测 2 条)—— 目标达成。

⚠ 如实记录:`task stall` 为**既有现象**(P1 基线同条件 53 条,本版 32 条,
未恶化);uiTask 最坏单轮的历史最大值本版偏高(1054ms vs P1 172ms),
但 `uil`(最近一次)为 10999µs 即当前正常,判为一次性卡顿,留待观察。

⚠ SKIP=doc-links:基线预存在问题(另一在途任务未提交文件),非本次引入。
2026-10-12 14:10:43 +08:00

496 lines
20 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 API
* 模块功能:BSD Socket API 兼容接口,提供 TCP/UDP 网络通信功能,
* 底层基于 CH395F 以太网协议栈芯片
* 适用平台:STM32F4 系列(CH395F 以太网芯片)
* 作者:王建锋
* 创建日期:2026-07-18
* 修改记录:
*/
#ifndef NET_SOCKET_H
#define NET_SOCKET_H
#ifdef __cplusplus
extern "C" {
#endif
#include "net_types.h"
/*
* OS 相关接口(FreeRTOS 消息队列 / 任务句柄 / netTask 内部 API)仅在启用
* RTOS 的构建(MCU)编译;纯 PC 模拟器定义 NET_NO_OS 后只保留 BSD Socket 核心 API。
*/
#ifndef NET_NO_OS
#include "FreeRTOS.h"
#include "task.h"
#endif /* NET_NO_OS */
#ifndef NET_NO_OS
/*
* 消息队列类型定义
*/
typedef enum {
NET_MSG_SEND,
NET_MSG_RECV,
NET_MSG_CLOSE,
NET_MSG_CONNECT,
NET_MSG_LISTEN,
NET_MSG_ACCEPT,
NET_MSG_RESYNC_LISTEN, /* 仅修 MCU 侧状态(ESTABLISHED→LISTENING),不发芯片命令 */
NET_MSG_RELISTEN, /* 无条件芯片侧重监听(open+listen,含 Trap 09 重试) */
} net_msg_type_t;
typedef struct {
net_msg_type_t type; /* 操作类型 */
int sockfd; /* Socket 描述符 */
void *buf; /* 数据缓冲区(send/recv) */
int len; /* 数据长度/缓冲区大小 */
int flags; /* 接收标志 */
uint32_t timeout_ms; /* 接收超时(入队方向;0 = 用默认 NET_RECV_TIMEOUT_MS) */
TaskHandle_t caller; /* 调用方任务句柄 */
int result; /* 操作结果 */
/* 连接参数 */
struct net_sockaddr_in addr; /* 连接地址 */
int addrlen; /* 地址长度 */
} net_msg_t;
#endif /* NET_NO_OS */
/*
* 网络初始化接口
*/
/*
* 函数功能:网络子系统初始化(配置 CH395F + 启用多连接模式)
* 入口参数:ip - 本地 IP 地址字符串 "192.168.1.100",NULL 使用 DHCP
* mask - 子网掩码字符串 "255.255.255.0",NULL 使用默认
* gateway - 网关地址字符串 "192.168.1.1",NULL 使用默认
* mac - MAC 地址(6 字节,低字节在前),NULL 或全零使用芯片内置
* 返回值:0 成功,-1 失败
* 限定条件:SPI2 已正确初始化
* 函数说明:必须在所有网络操作之前调用;mac 非空且非全零时在协议栈初始化
* (CMD_INIT_CH395)之前下发(手册 §9.2.1 步骤①:SET_MAC 写芯片 EEPROM,
* INIT_CH395 加载 MAC——顺序反了则本次上电不生效,回落芯片内置/上次值)
*/
int net_init(const char *ip, const char *mask, const char *gateway, const uint8_t *mac);
/*
* 函数功能:取网络代次号
* 入口参数:无
* 返回值:代次号(每次网络重建完成递增) uint32_t
* 限定条件:无
* 函数说明:TCP 服务(FTP / 对时 / 远程显示)据此感知"网络已重建"——代次变化
* 表示旧 Socket 已随芯片复位失效,服务应放弃旧监听并重建。
* 递增点在 net_stack_bring_up() 末尾(PHY/链路就绪后),避免服务在网络
* 尚未可用时抢先重建、也避免 uiTask 阻塞在消息队列上。
*/
uint32_t net_get_generation(void);
/*
* 函数功能:递增网络代次号(网络重建完成、对外可用时调用)
* 入口参数:无
* 返回值:无
* 限定条件:net_init() 成功且 PHY/链路就绪后调用(当前仅 net_stack_bring_up)
* 函数说明:见 net_get_generation()
*/
void net_generation_inc(void);
/*
* 函数功能:轮询所有 Socket 状态(必须在主循环中调用)
* 返回值:有事件发生的 socket 数量
* 限定条件:net_init() 已调用
* 函数说明:此函数处理所有 Socket 的中断事件,更新状态,
* 并触发事件回调。在非阻塞模式下必须定期调用。
*/
int net_poll(void);
/*
* 核心 Socket API
*/
/*
* 函数功能:创建 socket
* 入口参数:domain - 地址族,仅支持 AF_INET
* type - SOCK_STREAM(TCP) 或 SOCK_DGRAM(UDP)
* 返回值:socket 描述符 (0~7),失败返回 -1
* 限定条件:net_init() 已调用
* 函数说明:自动分配空闲 Socket 控制块
*/
int net_socket(int domain, int type);
/*
* 函数功能:绑定本地地址和端口
* 入口参数:sockfd - socket 描述符
* addr - 本地地址结构体指针
* addrlen - 地址结构体长度
* 返回值:0 成功,-1 失败
* 限定条件:socket 已创建且未绑定
* 函数说明:设置 CH395F 源端口
*/
int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen);
/*
* 函数功能:TCP Server 监听
* 入口参数:sockfd - socket 描述符
* 返回值:0 成功,-1 失败
* 限定条件:socket 已绑定
* 函数说明:内部调用 ch395f_tcp_listen,监听 Socket 自身即为数据通道
*/
int net_listen(int sockfd);
/*
* 函数功能:TCP Server 接受连接
* 入口参数:sockfd - 监听 socket 描述符
* addr - 输出:客户端地址
* addrlen - 输入输出:地址长度
* 返回值:新 socket 描述符,失败返回 -1
* 限定条件:socket 正在监听
* 函数说明:在非阻塞模式下,如果没有新连接,返回 -1 并设置
* net_errno 为 NET_ERR_WOULDBLOCK
*/
int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen);
/*
* 函数功能:查询监听 Socket 当前是否有可接受的连接(**纯 RAM 读,不发起 RPC**)
* 入口参数:sockfd - 监听 Socket 描述符 int
* 出口参数:无
* 返回值:1 - 有连接可接受;0 - 无(含描述符无效) int
* 限定条件:net_init() 已调用;任意任务上下文(含 uiTask 等实时性敏感任务)
* 函数说明:1. 只读 Socket 控制块状态,**不经消息队列、不等 netTask 回执** →
* 调用方不会被 netTask 的忙碌程度拖住(对比 net_accept:
* osMessageQueuePut(osWaitForever) + xTaskNotifyWait(portMAX_DELAY),
* 两者均**无上界**)
* 2. 判据与 net_accept_locked() 完全一致:单连接模型下监听 Socket 自身
* 在 CONNECT 事件后转 ESTABLISHED,即"有连接可接受"
* 3. 用途 = **门控**:无连接时直接跳过 net_accept(),把"每轮一次无谓
* RPC"降为 0(UI 推流 10 ms 轮询 = 100 msg/s 队列压力的根因)
* 4. 跨任务读单字:netTask 是唯一写者,最坏两种结果都无害——
* 晚一轮发现(netTask 每 1 ms 就 net_poll 一次),或扑空
* (net_accept 返回 -1 + NET_ERR_WOULDBLOCK)
* 5. 丢 CONNECT 中断由 net_reconcile_tick()(200 ms 对账 + 去抖)自愈,
* 本函数**不承担**自愈责任
*/
int net_can_accept(int sockfd);
/*
* 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(**纯 RAM 读,不发起 RPC**)
* 入口参数:sockfd - 监听 Socket 描述符 int
* 出口参数:无
* 返回值:1 - 就绪(LISTENING,或 CLOSED + auto_relisten = 断连待重建态);0 - 不就绪 int
* 限定条件:net_init() 已调用;任意任务上下文
* 函数说明:1. N-32(P1 不变量 II):取代 `net_can_accept()`(只认 ESTABLISHED)作为
* ui_net_sink 的 accept 门控——原门控使 :7003 永不进 `net_accept()`,
* `net_lost_connect_probe`(丢 CONNECT 中断补 accept,N-30 实证的
* "数据被吞/425"主根因修复)对该端口等于从未运行
* 2. 放行 LISTENING:探针与 "closed-but-readable 接受" 都发生在
* `net_accept_locked` **内部**,门控挡在外面就永远到不了
* 3. 放行 CLOSED+auto_relisten **且 readable≠0**(有残留数据):
* 保住 N-30 的 "closed-but-readable" 接受;**无残留时必须返回 0**,
* 否则调用方会在整个 CLOSED 窗口内每轮发起 `net_accept`(RPC 无上界)
* → 队列积压 → 喂不上狗(2026-10-12 P2a 台架实测 48 条 task stall)。
* 重建由 `net_listener_pump` 负责,调用方无需催促
* 4. 与 `net_can_accept` 的语义差异**有意为之**:本函数回答"这个监听器
* 这一轮**值不值得**发起一次 accept",而非"一定有连接"
* 5. 跨任务读单字,同 `net_can_accept` 的最坏情形论证
*/
int net_listener_armed(int sockfd);
/*
* 函数功能:查询 Socket 是否"值得发一次 recv"(**纯 RAM 读,不发起 RPC**)
* 入口参数:sockfd - Socket 描述符 int
* 出口参数:无
* 返回值:1 - 应发 recv(有数据可读,或连接已非 ESTABLISHED 需了结);0 - 不必发 int
* 限定条件:net_init() 已调用;任意任务上下文
* 函数说明:1. ESTABLISHED → 看 readable(net_poll 在 RECV_OK 置位、读空清零)
* 2. **非 ESTABLISHED 一律返回 1**:连接已消失时必须让上层真的 recv 一次
* 拿到 EOF/错误并自行复位;若把它门掉,"连接已不存在"会被永久藏住
* (推流侧 g_client 永不复位 → 再也不接受新客户端)
* 3. 描述符失效(net_get_sock 返回 NULL)同样返回 1 → 让 recv 报 BADF,
* 走调用方既有的错误分支重建,而不是静默卡死
* 4. ⚠ **不可作为唯一判据**:readable 只由 RECV_OK 中断置位,而
* net_reconcile_tick 的三类对账**不含**"芯片 RX 非空但 readable=0"
* → 中断丢失时本函数会恒返 0。调用方须保留**低频兜底 recv**
* (ui_net_sink 每 32 轮无条件收一次,最坏延迟 ≈320 ms)
*/
int net_can_read(int sockfd);
/*
* 函数功能:TCP Client 发起连接
* 入口参数:sockfd - socket 描述符
* addr - 服务器地址
* addrlen - 地址长度
* 返回值:0 成功,-1 失败
* 限定条件:socket 已创建
* 函数说明:连接过程是异步的,需要调用 net_poll() 等待连接完成,
* 或使用 select() 等待可写事件
*/
int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen);
/*
* 函数功能:发送数据(TCP)
* 入口参数:sockfd - socket 描述符
* buf - 数据缓冲区
* len - 数据长度
* flags - 通常为 0
* 返回值:实际发送字节数,失败返回 -1
* 限定条件:TCP 连接已建立,指针非空
* 函数说明:阻塞等待发送缓冲区空闲,拷贝数据后触发发送
*/
int net_send(int sockfd, const void *buf, int len, int flags);
/*
* 函数功能:接收数据(TCP)
* 入口参数:sockfd - socket 描述符
* buf - 接收缓冲区
* len - 缓冲区大小
* flags - 通常为 0,可设置 NET_MSG_DONTWAIT 非阻塞
* 返回值:实际接收字节数,0=对端关闭,-1=错误
* 限定条件:TCP 连接已建立
* 函数说明:在阻塞模式下,如果没有数据,函数会等待;
* 在非阻塞模式下,如果没有数据,返回 -1 并设置
* net_errno 为 NET_ERR_WOULDBLOCK
*/
int net_recv(int sockfd, void *buf, int len, int flags);
/*
* 函数功能:接收数据(**带调用方指定超时**的阻塞版,用于分片有界读)
* 入口参数:sockfd - Socket 描述符
* buf/len - 接收缓冲与容量
* timeout_ms - 单次调用在 netTask 内的最长等待(毫秒,>0)
* 返回值:>0 实际字节数;0 = 对端关闭(EOF);-1 = 真错误(errno 可查);**-2 = 超时无数据**
* 限定条件:非 netTask 上下文;Socket 已连接
* 函数说明:为"不能长时间占用 netTask"的调用方(如 lftpd 控制行/数据读)提供
* **分片**阻塞:每次调用最多占用 netTask timeout_ms,返回 -2 表示这段
* 时间内无数据,调用方自行累计总预算并决定是否继续(E-02 头阻塞修复)。
* 超时用独立返回码而非 errno → 无跨任务 errno 竞态。
*/
int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms);
/*
* 函数功能:发送 UDP 数据
* 入口参数:sockfd - socket 描述符
* buf - 数据缓冲区
* len - 数据长度
* flags - 通常为 0
* dest_addr - 目标地址
* addrlen - 地址长度
* 返回值:实际发送字节数,失败返回 -1
* 限定条件:socket 已创建,指针非空
* 函数说明:设置目标地址后调用 CH395F 发送
*/
int net_sendto(int sockfd, const void *buf, int len, int flags,
const struct net_sockaddr *dest_addr, int addrlen);
/*
* 函数功能:接收 UDP 数据
* 入口参数:sockfd - socket 描述符
* buf - 接收缓冲区
* len - 缓冲区大小
* flags - 通常为 0
* src_addr - 输出:发送方地址
* addrlen - 输入输出:地址长度
* 返回值:实际接收字节数,失败返回 -1
* 限定条件:socket 已创建,指针非空
* 函数说明:读取 CH395F 接收缓冲区,获取对端地址
*/
int net_recvfrom(int sockfd, void *buf, int len, int flags,
struct net_sockaddr *src_addr, int *addrlen);
/*
* 函数功能:关闭 socket
* 入口参数:sockfd - socket 描述符
* 返回值:0 成功,-1 失败
* 限定条件:socket 已打开
* 函数说明:释放 Socket 控制块,TCP 先断开连接
*/
int net_close(int sockfd);
/*
* 事件回调 API
*/
/*
* 函数功能:注册事件回调
* 入口参数:sockfd - socket 描述符
* cb - 回调函数
* arg - 用户参数
* 返回值:0 成功,-1 失败
* 限定条件:socket 已创建
* 函数说明:回调在 net_poll 上下文中触发
*/
int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg);
/*
* 辅助函数
*/
/*
* 函数功能:获取 **本任务** 最后一次 net_*() 调用失败时的精确错误码
* 入口参数:无
* 出口参数:无
* 返回值:错误码(NET_ERR_xxx);本任务尚无失败记录时为 NET_OK(0)
* 限定条件:无(任何上下文可调用,含中断——查询为纯本任务索引读)
* 函数说明:1. **按任务隔离**(2026-09-25 E-03 修复):错误码存在每个任务自己的
* 槽位里,任务间互不干扰,不存在跨任务覆写竞态——旧实现的单一全局
* 变量会被 netTask/其他任务的 net 调用交错覆盖,调用方可能读到别人的码
* 2. 语义:本任务最后一次返回 -1 的 net_*() 调用所对应的精确码;
* 调用成功时**不清零**(与本 API 历史行为一致,保持向后兼容)
* 3. 任务句柄槽位用尽时回退到共享槽位(仅影响排障,不影响功能)
* 4. 超时类场景(net_recv_to)请直接用返回码 -2 判定,不要依赖本接口
*/
int net_get_errno(void);
/*
* 函数功能:查询 socket 当前状态(只读快照,可能滞后于 netTask)
* 返回值:状态值(net_sock_state_t);描述符无效/未占用 → -1
*/
int net_sock_state(int sockfd);
/*
* 函数功能:断连重同步——仅把 MCU 侧状态对齐到 LISTENING,不发任何芯片命令
* 适用场景:芯片"静默回收"(peer 发 FIN 后芯片自行把 Socket 回收回监听
* 且不发 DISCONNECT 中断,2026-09-22 台架实证)→ MCU 状态滞留
* ESTABLISHED。调用方须确信连接已死/芯片已回收(如会话超时中止后);
* 连接仍存活时严禁调用(会把活连接标记成监听态)。
* 返回值:0 - 已同步(或原本即 LISTENING);-1 - 失败(描述符/队列无效)
*/
int net_resync_listen(int sockfd);
/*
* 函数功能:无条件重新监听(芯片侧 open+listen,含 Trap 09 失败重试)
* 适用场景:断连后需确保监听器确定在位的自愈(监听短暂中断 ~1s,调用方
* 的重连逻辑须容忍)
* 返回值:0 - 成功;-1 - 失败
*/
int net_relisten(int sockfd);
/*
* 函数功能:将 IP 地址字符串转为网络字节序
* 入口参数:cp - 点分十进制字符串 "192.168.1.100"
* 返回值:网络字节序 IP,失败返回 0
* 限定条件:cp 指针非空
* 函数说明:不支持域名解析
*/
uint32_t net_inet_addr(const char *cp);
/*
* 函数功能:将网络字节序 IP 转为字符串
* 入口参数:addr - 网络字节序 IP
* buf - 输出缓冲区(至少 16 字节)
* 返回值:buf 指针
* 限定条件:buf 指针非空
*/
char *net_inet_ntoa(uint32_t addr, char *buf);
/*
* 函数功能:端口字节序转换(主机序 -> 网络序)
* 入口参数:hostshort - 主机序端口
* 返回值:网络序端口
* 限定条件:无
* 函数说明:小端 → 大端
*/
uint16_t net_htons(uint16_t hostshort);
/*
* 函数功能:端口字节序转换(网络序 -> 主机序)
* 入口参数:netshort - 网络序端口
* 返回值:主机序端口
* 限定条件:无
* 函数说明:大端 → 小端
*/
uint16_t net_ntohs(uint16_t netshort);
/*
* 函数功能:IP 地址字节序转换(主机序 -> 网络序)
* 入口参数:hostlong - 主机序 IP
* 返回值:网络序 IP
* 限定条件:无
* 函数说明:小端 → 大端
*/
uint32_t net_htonl(uint32_t hostlong);
/*
* 函数功能:IP 地址字节序转换(网络序 -> 主机序)
* 入口参数:netlong - 网络序 IP
* 返回值:主机序 IP
* 限定条件:无
* 函数说明:大端 → 小端
*/
uint32_t net_ntohl(uint32_t netlong);
#ifndef NET_NO_OS
/*
* 函数功能:处理消息队列中的请求(必须在 netTask 主循环中调用)
* 限定条件:net_init() 已调用
* 函数说明:处理其他任务通过 net_send/net_recv 等接口发送的请求,
* 在 netTask 上下文中串行化执行所有 CH395F 操作
*/
void net_process_messages(void);
#endif /* NET_NO_OS */
/*
* 函数功能:获取 Socket 控制块指针(内部使用)
* 入口参数:sockfd - socket 描述符
* 返回值:Socket 控制块指针,失败返回 NULL
*/
net_sock_t *net_get_sock(int sockfd);
/*
* 函数功能:获取 Socket 本地地址(IP + Port)
* 入口参数:sockfd - socket 描述符
* addr - 输出:本地地址
* addrlen - 输入输出:地址结构体长度
* 返回值:0 成功,-1 失败
* 限定条件:socket 已创建或已绑定
*/
int net_getsockname(int sockfd, struct net_sockaddr *addr, int *addrlen);
#ifndef NET_NO_OS
/*
* 内部 API(需在 netTask 上下文中调用,绕过消息队列)
*/
/*
* 函数功能:发送数据(内部版本,直接操作,不经过消息队列)
* 入口参数:p_sock - Socket 控制块指针
* buf - 数据缓冲区
* len - 数据长度
* 返回值:实际发送字节数,失败返回 -1
* 限定条件:必须在 netTask 上下文中调用
*/
int net_send_sock(net_sock_t *p_sock, const void *buf, int len);
/*
* 函数功能:接收数据(内部版本,直接操作,不经过消息队列)
* 入口参数:p_sock - Socket 控制块指针
* buf - 接收缓冲区
* len - 缓冲区大小
* flags - 通常为 0,可设置 NET_MSG_DONTWAIT
* 返回值:实际接收字节数,0=对端关闭,-1=错误
* 限定条件:必须在 netTask 上下文中调用
*/
int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags);
/*
* 函数功能:关闭 socket(内部版本,直接操作,不经过消息队列)
* 入口参数:p_sock - Socket 控制块指针
* 返回值:0 成功,-1 失败
* 限定条件:必须在 netTask 上下文中调用
*/
int net_close_sock(net_sock_t *p_sock);
/*
* 函数功能:启动 TCP Server 监听(内部版本,直接操作,不经过消息队列)
* 入口参数:sockfd - Socket 描述符
* 返回值:0 成功,-1 失败
* 限定条件:必须在 netTask 上下文中调用
*/
int net_listen_locked(int sockfd);
#endif /* NET_NO_OS */
#ifdef __cplusplus
}
#endif
#endif /* NET_SOCKET_H */