/* * 模块名称:挂起请求表(阻塞 recv / send 挂起模型)—— 实现 * 文件说明:见 net_pending.h;架构与五路语义见 * docs/网络/Net层阻塞模型架构设计.md * 依赖(全部单向,无反向 include): * net_types.h(net_sock_t / NET_ERR_* / NET_SOCK_STATE_*)/ * net_socket.h(NET_MAX_SOCKETS / net_set_errno / NET_ERR_RETURN_1 / * net_pending_active 声明)/ net_pending.h(本模块接口)/ * net_listener.h(net_listener_request / net_listener_tick)/ ch395f.h(RX 读出 / TX 分块写)/ main.h(HAL_GetTick) * 限定条件:本文件真机专属——模拟器走 sim/net_socket_sim.c, * simulator/CMakeLists.txt 不编译本文件(同 net_listener.c 先例) * * 阻塞 recv / send 挂起模型(结构演进):阻塞等待 = 挂起请求 + 主循环节拍完成 * "无数据 / 发送缓冲未空 + 阻塞等待"不再自旋占用 netTask:登记 pending 后立即返回 * net_pending_tick / net_send_pending_tick(与 D10 重监听重试同节奏, * net_process_messages 开头每轮调用) * recv:按 ①数据 ②到点(仅显式预算) ③断连 顺序检查完成条件并通知调用方 * send:按 ①断连 ②推进写块 ③写完 ④无进展超时 顺序推进并通知调用方 * 关闭/网络重建/陈旧重同步走显式 cancel 路径(取消必通知,调用方永不挂死) * 公开 API 返回契约:recv >0 字节数 / 0 EOF / -1 错误;send >=0 已发字节数(短写由 * 调用方按 ret != len 判定)/ -1 错误(错误码经通知值回传) * 预算归调用方:timeout_ms=0 即无限等待(无默认预算);挂起超时留痕探针为 * 编译期选项(NET_PENDING_TRIPWIRE,默认 0 = 编译剔除) */ #include #include "FreeRTOS.h" #include "task.h" #include "net_types.h" #include "net_socket.h" #include "net_pending.h" #include "net_listener.h" #include "ch395f.h" #include "dbg_log.h" #include "main.h" typedef struct { uint8_t active; /* 1 = 本 fd 有挂起 recv */ uint8_t infinite; /* 1 = 无限等待(无 deadline;0 预算 = 永等) */ #if NET_PENDING_TRIPWIRE uint8_t tripwire_logged; /* 1 = 本轮挂起已留痕(保证只留一条) */ #endif void *buf; /* 调用方缓冲(仅指针,须保持有效至完成) */ int len; /* 缓冲容量 */ uint32_t deadline; /* 截止时刻(tick)= 登记时刻 + 本次预算(infinite=1 时无意义) */ uint32_t registered; /* 登记时刻(tick;留痕探针/日志基准) */ TaskHandle_t caller; /* 调用方任务(完成通知目标) */ } net_pending_t; /* 每 fd 一个挂起(禁止覆盖:覆盖丢失前调用方通知 → 挂死) */ static net_pending_t s_net_pending[NET_MAX_SOCKETS]; /* 执行任务(net_init 捕获;防自通知死锁) */ static TaskHandle_t s_owner_task; /* * 函数功能:捕获执行任务句柄(登记守卫据此拒绝自等待死锁) * 入口参数:无 * 出口参数:无 * 返回值:无 * 限定条件:执行任务上下文(NET 层 = netTask / 硬件层 = testTask),net_init 顶部 */ void net_pending_capture_owner(void) { s_owner_task = xTaskGetCurrentTaskHandle(); } /* * 函数功能:通知调用方并清除本 fd 的挂起请求(所有完成/取消路径的公共出口) * 入口参数:sockfd - Socket 描述符 (0~7) int * result - 32 位结果原值(>0 字节数 / 0 EOF / 负错误码) int * 出口参数:无 * 返回值:无 * 限定条件:netTask 上下文;sockfd 合法且挂起在位(否则静默返回,防重复通知) * 函数说明:通知值 = 结果本身(与消息分派同一回传通道,调用方直接按通知值 * 取结果);失败记入 netTask 自己的 errno 槽位 */ static void net_pending_complete(int sockfd, int result) { /* 本 fd 挂起请求指针 */ net_pending_t *p; /* 描述符越界:静默返回(防重复通知) */ if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { return; } /* 取挂起请求 */ p = &s_net_pending[sockfd]; /* 无在位挂起(已结束/已取消):静默返回 */ if (p->active == 0U) { return; } /* 失败结果:记入 netTask 自己的错误码槽位(不再写入跨任务共享全局) */ if (result < 0) { (void)net_set_errno(result); } /* 覆盖式写入通知值 = 结果本身(32 位原值) */ xTaskNotify(p->caller, (uint32_t)result, eSetValueWithOverwrite); /* 清除挂起状态 */ p->active = 0U; } /* * 函数功能:登记一个挂起 recv 请求(net_recv_locked 的阻塞无数据路径) * 入口参数:sockfd - Socket 描述符 (0~7) int * buf - 调用方接收缓冲(须保持有效至完成) void * * len - 缓冲容量 int * to_ms - 本次等待预算(0 = 无限等待) uint32_t * caller - 调用方任务句柄(完成通知目标) TaskHandle_t * 出口参数:无 * 返回值:NET_PENDING_REGISTERED - 登记成功(分派跳过即时通知) * -1 - 拒绝(同 fd 已有挂起 / 执行任务自身发起) * 限定条件:netTask 上下文 * 函数说明:每 fd 至多一个挂起(禁止覆盖——覆盖丢失前调用方通知 = 该任务永久挂死); * 执行任务自身发起的阻塞等待无他人可完成(tick 跑在执行任务内)→ 自等自 * 通知 = 死锁,必须拒绝(netTask 上下文调用方须用 DONTWAIT) */ int net_pending_register(int sockfd, void *buf, int len, uint32_t to_ms, TaskHandle_t caller) { /* 挂起请求指针 */ net_pending_t *p; /* 登记时刻(tick) */ uint32_t now; /* 描述符越界 → BADF */ if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { return NET_ERR_RETURN_1(NET_ERR_BADF); } /* 取挂起请求 */ p = &s_net_pending[sockfd]; /* 同 fd 已有挂起:拒绝(覆盖将丢失前通知 = 该任务永久挂死) */ if (p->active != 0U) { return NET_ERR_RETURN_1(NET_ERR_BUSY); } /* netTask 自身发起:无他人可完成(tick 跑在 netTask 内)→ 拒绝(自等死锁) */ if ((caller == NULL) || (caller == s_owner_task)) { return NET_ERR_RETURN_1(NET_ERR_BUSY); } /* 当前 tick(截止/留痕探针基准) */ now = HAL_GetTick(); /* 标记挂起在位 */ p->active = 1U; /* 预算形态:0 = 无限等待(无 deadline,tick ② 跳过;留痕探针以登记时刻计) */ p->infinite = (to_ms == 0U) ? 1U : 0U; #if NET_PENDING_TRIPWIRE /* 留痕标记清零(每轮挂起只留一条) */ p->tripwire_logged = 0U; #endif /* 调用方接收缓冲(须保持有效至完成) */ p->buf = buf; /* 缓冲容量 */ p->len = len; /* 截止时刻 = 登记时刻 + 预算(无限等待时不启用,置 0 仅为可观测性) */ p->deadline = (to_ms == 0U) ? 0U : (now + to_ms); /* 登记时刻(留痕探针基准) */ p->registered = now; /* 完成通知目标 */ p->caller = caller; /* 登记成功:分派跳过即时通知 */ return NET_PENDING_REGISTERED; } /* * 函数功能:挂起 recv 完成检查(netTask 每轮 net_process_messages 调用一次,非阻塞) * 入口参数:p_socks - Socket 控制块表首指针(net_socket.c 的 s_net_socks) net_sock_t * * 出口参数:无 * 返回值:无 * 限定条件:netTask 上下文 * 函数说明:1. 逐 fd 检查完成条件:数据到达 / 到点(仅显式预算)/ 断连 * 2. 完成即经 net_pending_complete 通知并清除 * 3. 挂起超时留痕探针(编译期选项 NET_PENDING_TRIPWIRE,默认关):挂起超阈值 * 只 [ERR] 留痕一次("完成逻辑缺位"探针),绝不强制结束、绝不伪造返回值 */ void net_pending_tick(net_sock_t *p_socks) { /* 当前 tick */ uint32_t now = HAL_GetTick(); /* Socket 循环索引 */ int i; for (i = 0; i < NET_MAX_SOCKETS; i++) { /* 本 fd 挂起请求 */ net_pending_t *p = &s_net_pending[i]; /* 本 fd 控制块 */ net_sock_t *p_sock = &p_socks[i]; /* 无挂起请求:跳过 */ if (p->active == 0U) { continue; } /* fd 已关闭 */ if (p_sock->in_use == 0U) { /* fd 已关闭(正常路径在 close 内先结束;此处兜底,防任何路径漏掉通知) */ net_pending_complete(i, NET_ERR_CONNRESET); continue; } /* ① 数据到达(数据优先)——readable 门控:常见无数据路径零额外 SPI 事务 * (INT# 电平触发链保证缓冲有数据时每轮重读 SINT 重位置位) */ if ((p_sock->state == NET_SOCK_STATE_ESTABLISHED) || (p_sock->state == NET_SOCK_STATE_UDP_OPEN)) { /* 可读标志置位 */ if (p_sock->readable != 0U) { /* 查询芯片侧可用字节数 */ int avail = ch395f_get_recv_len((uint8_t)i); /* 有数据 */ if (avail > 0) { /* 本次读取量 = min(可用, 缓冲容量) */ int rd = (avail > p->len) ? p->len : avail; /* 读入调用方缓冲区 */ ch395f_read_recv_buf((uint8_t)i, (uint8_t *)p->buf, (uint16_t)rd); /* 累计接收字节统计 */ p_sock->recv_bytes += (uint32_t)rd; /* 数据已消费,清可读标志 */ p_sock->readable = 0U; /* 结束:以字节数通知 */ net_pending_complete(i, rd); continue; } /* 虚假标志:缓冲无数据,继续等待 */ p_sock->readable = 0U; } } /* ② 到点(仅显式预算登记;无限等待登记无 deadline): * 超时是调用方策略,net_recv_to 的预算在此兑现,不是伪造。 * tick 差值按有符号比较,抗回绕) */ if ((p->infinite == 0U) && ((int32_t)(now - p->deadline) >= 0)) { /* 结束:超时(错误码由 net_pending_complete 记入本任务槽位) */ net_pending_complete(i, NET_ERR_TIMEDOUT); continue; } /* ③ 断连:排空 → 数据或 EOF(0) + 自动重监听。 * 判据同 net_recv_locked(监听生命周期 P1 不变量 I):`state == CLOSED || * session_dead`。只判 CLOSED 时,抢先 relisten 后 ①(要求 ESTABLISHED) * 与 ③(要求 CLOSED) 两头落空 → 挂起的 recv 只能干等 ② deadline 超时 * (lftpd 阻塞 recv 的"消费者卡死"原型)。无限等待登记没有 ②, * 漏 ③ = 永久挂死(留痕探针只留痕、不唤醒)—— * ③ 与显式 cancel 是无限 recv 的全部唤醒责任方,勿动判据。 */ if ((p_sock->state == NET_SOCK_STATE_CLOSED) || (p_sock->session_dead != 0U)) { /* 查询残留字节数 */ int rem = ch395f_get_recv_len((uint8_t)i); /* 有残留数据:按数据完成 */ if (rem > 0) { /* 本次读取量 = min(残留, 缓冲容量) */ int rd = (rem > p->len) ? p->len : rem; /* 读入调用方缓冲区 */ ch395f_read_recv_buf((uint8_t)i, (uint8_t *)p->buf, (uint16_t)rd); /* 累计接收字节统计 */ p_sock->recv_bytes += (uint32_t)rd; /* 数据已消费,清可读标志 */ p_sock->readable = 0U; /* 结束:以字节数通知 */ net_pending_complete(i, rd); } else { /* 启用自动重监听:登记重建请求(动作在 net_listener_tick 一处)。 * 原内层 `state == CLOSED` 门已**上收到 tick**——它防的是"对已回 * LISTENING 的 fd 再 open+listen → 因 USE 错误走进关闭分支、关掉 * 正听着的 Socket"(U12 同源),属执行侧安全不变量,不由登记点复述。 * 监听生命周期 P2 修正:**保留本处即时登记** —— 它只在有**挂起 recv** * 时到达(`p->active != 0`),属"消费者在等这条连接结局"的 * 消费者驱动路径,不是周期性抢先(同 net_recv_locked 的理由)。 */ if (p_sock->auto_relisten != 0U) { /* 非阻塞:下一轮 net_listener_tick 执行(入口收敛到 request) */ net_listener_request(i, NET_LISTENER_REQ_PENDING); } /* TRAP-NET-18 待办 #3:异常释放(TIMEOUT/RST)不是正常 EOF(对端未发正常 FIN) * → 按连接重置结束:应用(lftpd STOR)对异常结束回 5xx 而非误报 226; * 正常 FIN(DISCONNECT)仍回 0(在途 FIN 与正常完成不可区分——协议边界,见 TRAP-NET-18 记录) */ /* 异常释放:按连接重置结束(回 5xx,不误报 226) */ if (p_sock->released_abnormal != 0U) { /* 异常释放(错误码由 net_pending_complete 记入本任务槽位) */ net_pending_complete(i, NET_ERR_CONNRESET); } else { /* 正常 EOF:以 0 结束 */ net_pending_complete(i, 0); } } continue; } /* 其他状态(CONNECTING/LISTENING/…):保持等待状态迁移——CONNECT→ESTABLISHED * 是合法迁移,不做状态推断取消,避免误杀正在建链的等待;陈旧 resync 由显式 cancel 处理 */ /* ④ 挂起超时留痕探针(编译期选项 NET_PENDING_TRIPWIRE,默认关):挂起超 * 阈值 = "完成逻辑缺位"探针 → 只留一条 [ERR],**绝不强制结束、绝不伪造 * TIMEDOUT 返回值**(强制结束会把完成路径缺陷伪装成普通超时,调用方 * 拿到假结果、缺陷永远潜伏)。判据:无限登记按登记时刻计;显式登记按 * deadline 计(② 正常不会让它越过阈值,能到这里本身就是完成逻辑缺位)。 */ #if NET_PENDING_TRIPWIRE if (p->tripwire_logged == 0U) { /* 探针基准时刻:无限 = 登记时刻;显式 = 截止时刻 */ uint32_t base = (p->infinite != 0U) ? p->registered : p->deadline; if ((int32_t)(now - base) >= (int32_t)NET_RECV_TRIPWIRE_MS) { /* 留痕(挂起已持续毫秒数,自基准起算) */ DBG_ERROR("sock%d recv pending %ums (base=%s): completion source missing", i, (unsigned)((int32_t)now - (int32_t)base), (p->infinite != 0U) ? "infinite" : "deadline"); /* 只留痕一次(每轮挂起一条,防日志风暴) */ p->tripwire_logged = 1U; } } #endif } } /* * 函数功能:查询本 fd 是否有在途挂起 recv —— 宿主能力,供 net_listener 用 * 入口参数:sockfd - Socket 描述符 (0~7) int * 出口参数:无 * 返回值:1 - 有挂起;0 - 无(含描述符越界) int * 限定条件:任意上下文(纯 RAM 读,不发起 RPC) * 函数说明:1. 监听生命周期模块化:net_listener_tick 用它跳过"重开 Socket * 会摧毁在途连接"的 fd(挂起 recv 保险,原为直接读 s_net_pending[i].active) * 2. 声明在 net_pending.h(配对头,规范 S17 2.2) */ int net_pending_active(int sockfd) { /* 描述符越界:视为无挂起 */ if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { return 0; } return (s_net_pending[sockfd].active != 0U) ? 1 : 0; } /* * 函数功能:清除本 fd 的挂起请求(不通知;用于"新 fd 不得继承"与 close 后的幂等清理) * 入口参数:sockfd - Socket 描述符 (0~7) int * 出口参数:无 * 返回值:无 * 限定条件:无 * 函数说明:1. 静默清理不发通知:close 路径先经 settle 通知,本调用仅保证状态归零(幂等) * 2. 新建 fd 时调用,防复用槽位继承上一条连接的挂起请求 */ void net_pending_clear(int sockfd) { /* 描述符越界:直接返回 */ if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { return; } /* 挂起状态清零(静默,不发通知) */ s_net_pending[sockfd].active = 0U; } /* * 函数功能:取消本 fd 的挂起请求并通知调用方(显式路径:陈旧 resync 等) * 入口参数:sockfd - Socket 描述符 (0~7) int * err - 通知的错误码(如 NET_ERR_CONNRESET) int * 出口参数:无 * 返回值:无(无在位挂起时静默返回) * 限定条件:netTask 上下文 * 函数说明:经 net_pending_complete 以指定错误码通知调用方(取消必通知,防挂死) */ void net_pending_cancel(int sockfd, int err) { /* complete 内部校验范围与在位(未在位则静默返回) */ net_pending_complete(sockfd, err); } /* * 函数功能:取消全部挂起请求并逐一通知(网络重建路径) * 入口参数:err - 通知的错误码 int * 出口参数:无 * 返回值:无 * 限定条件:netTask 上下文;须在控制块清零与芯片复位之前调用 * 函数说明:网络重建(net_stack_bring_up)前调用,保证所有在位调用方均被唤醒 */ void net_pending_cancel_all(int err) { /* 逐 fd 遍历 */ for (int i = 0; i < NET_MAX_SOCKETS; i++) { /* 取消并通知(未在位则静默跳过) */ net_pending_complete(i, err); } } /* * 函数功能:关闭前先结束本 fd 的挂起 recv——把结果回传并唤醒调用方(有数据先 * 送达,无数据则取消),再关闭 Socket(per-fd 串行:先完成、再关闭) * 入口参数:sockfd - 将被关闭的描述符 (0~7) int * p_sock - 将被关闭的 Socket 控制块指针(与 sockfd 对应) net_sock_t * * 出口参数:无 * 返回值:无(无在位挂起时静默返回) * 限定条件:netTask 上下文;须在 ch395f_close_socket 之前调用 * 函数说明:先排空尝试——关闭不丢已到达数据(芯片缓冲在 close 前可读); * 有数据按字节数通知,否则取消(NET_ERR_CONNRESET) */ void net_pending_settle_close(int sockfd, net_sock_t *p_sock) { /* 通知用结果值(默认为取消) */ int val; /* 芯片 RX 可用字节数 */ int avail; /* 描述符越界或无在位挂起:静默返回 */ if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { return; } if (s_net_pending[sockfd].active == 0U) { return; } /* 默认结果 = 取消(CONNRESET) */ val = NET_ERR_CONNRESET; /* 查询芯片 RX 可用字节(缓冲内数据在 close 前可读) */ avail = ch395f_get_recv_len((uint8_t)sockfd); /* 数据已到达:排空,关闭不丢数据 */ if (avail > 0) { /* 本次读取量 = min(可用, 缓冲容量) */ int rd = (avail > s_net_pending[sockfd].len) ? s_net_pending[sockfd].len : avail; /* 读入调用方缓冲区 */ ch395f_read_recv_buf((uint8_t)sockfd, (uint8_t *)s_net_pending[sockfd].buf, (uint16_t)rd); /* 累计接收字节统计 */ p_sock->recv_bytes += (uint32_t)rd; /* 数据已消费,清可读标志 */ p_sock->readable = 0U; /* 有数据:以字节数通知 */ val = rd; } /* 结束(必通知) */ net_pending_complete(sockfd, val); } /* ================= 挂起 send 请求表(SEND pending,二期) ================= */ typedef struct { uint8_t active; /* 1 = 本 fd 有挂起 send */ const void *buf; /* 调用方缓冲基址(仅指针,须保持有效至完成) */ int len; /* 本次发送总长度(含登记前已写部分) */ int sent; /* 已写入芯片的字节数(进度;登记时可 > 0) */ uint32_t deadline; /* 无进展超时时刻 = 上次成功写块时刻 + NET_SEND_TIMEOUT_MS */ uint32_t registered; /* 登记时刻(tick;日志基准) */ TaskHandle_t caller; /* 调用方任务(完成通知目标) */ } net_send_pending_t; /* 每 fd 一个挂起 send(与 recv 表独立:收发两方向互不阻塞) */ static net_send_pending_t s_net_send[NET_MAX_SOCKETS]; /* * 函数功能:通知调用方并清除本 fd 的挂起 send(所有完成/取消路径的公共出口) * 入口参数:sockfd - Socket 描述符 (0~7) int * result - 结果原值(本模型恒为**已发字节数** >= 0) int * 出口参数:无 * 返回值:无 * 限定条件:netTask 上下文;sockfd 合法且挂起在位(否则静默返回,防重复通知) * 函数说明:send 结果恒 >= 0(短写语义),故不在此改写错误码——取消/超时的原因 * 由各调用路径在调用本函数**之前**经 net_set_errno 记录 */ static void net_send_pending_complete(int sockfd, int result) { /* 本 fd 挂起请求指针 */ net_send_pending_t *p; /* 描述符越界:静默返回(防重复通知) */ if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { return; } /* 取挂起请求 */ p = &s_net_send[sockfd]; /* 无在位挂起(已结束/已取消):静默返回 */ if (p->active == 0U) { return; } /* 覆盖式写入通知值 = 已发字节数(32 位原值) */ xTaskNotify(p->caller, (uint32_t)result, eSetValueWithOverwrite); /* 清除挂起状态 */ p->active = 0U; } /* * 函数功能:登记一个挂起 send 请求(net_send_locked 的"等发送缓冲"超预算路径) * 入口参数:见 net_pending.h * 出口参数:无 * 返回值:NET_PENDING_REGISTERED - 登记成功;-1 - 拒绝(见头注释) * 限定条件:netTask 上下文 * 函数说明:每 fd 至多一个挂起 send(禁止覆盖——覆盖丢失前调用方通知 = 永久挂死); * 执行任务自身发起(net_send_sock 阻塞用法)无他人可完成 → 拒绝(自等死锁) */ int net_send_pending_register(int sockfd, const void *buf, int len, int sent, TaskHandle_t caller) { /* 挂起请求指针 */ net_send_pending_t *p; /* 登记时刻(tick) */ uint32_t now; /* 描述符越界 → BADF */ if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { return NET_ERR_RETURN_1(NET_ERR_BADF); } /* 参数非法(缓冲无效 / 长度非正 / 进度越界)→ INVAL */ if ((buf == NULL) || (len <= 0) || (sent < 0) || (sent >= len)) { return NET_ERR_RETURN_1(NET_ERR_INVAL); } /* 取挂起请求 */ p = &s_net_send[sockfd]; /* 同 fd 已有挂起 send:拒绝(覆盖将丢失前通知 = 该任务永久挂死) */ if (p->active != 0U) { return NET_ERR_RETURN_1(NET_ERR_BUSY); } /* netTask 自身发起:无他人可完成(tick 跑在 netTask 内)→ 拒绝(自等死锁) */ if ((caller == NULL) || (caller == s_owner_task)) { return NET_ERR_RETURN_1(NET_ERR_BUSY); } /* 当前 tick(停滞计时基准) */ now = HAL_GetTick(); /* 标记挂起在位 */ p->active = 1U; /* 调用方发送缓冲基址(须保持有效至完成) */ p->buf = buf; /* 本次发送总长度 */ p->len = len; /* 进度起点(登记前已写入芯片的字节数) */ p->sent = sent; /* 无进展超时起点(NET_SEND_TIMEOUT_MS = 0 表示不限,同快速路径口径) */ p->deadline = (NET_SEND_TIMEOUT_MS > 0) ? (now + (uint32_t)NET_SEND_TIMEOUT_MS) : 0U; /* 登记时刻(日志基准) */ p->registered = now; /* 完成通知目标 */ p->caller = caller; /* 登记成功:分派跳过即时通知 */ return NET_PENDING_REGISTERED; } /* * 函数功能:判"本 Socket 的发送会话是否已不可继续"(TCP/UDP 统一判据) * 入口参数:p_sock - Socket 控制块指针 net_sock_t * * 出口参数:无 * 返回值:1 - 不可继续(应报 CONNRESET/NOTCONN);0 - 可继续 int * 限定条件:p_sock 非空;任意上下文(纯读控制块) * 函数说明:TCP 的门是 ESTABLISHED,UDP 的门是 UDP_OPEN(芯片侧由 net_bind 打开)。 * 判据只写这一处:net_send_locked 的入口门与两处中途检查、 * net_send_pending_tick 的 ① 都用它——两条发送路径各自复述一遍 * "state != ESTABLISHED"正是 UDP 接入时最容易漏改的形态。 */ int net_send_state_down(const net_sock_t *p_sock) { if (p_sock->type == NET_SOCK_DGRAM) { return (p_sock->state != NET_SOCK_STATE_UDP_OPEN) ? 1 : 0; } return (p_sock->state != NET_SOCK_STATE_ESTABLISHED) ? 1 : 0; } /* * 函数功能:挂起 send 推进与完成检查(netTask 每轮 net_process_messages 调用一次,非阻塞) * 入口参数:p_socks - Socket 控制块表首指针(net_socket.c 的 s_net_socks) net_sock_t * * 出口参数:无 * 返回值:无 * 限定条件:netTask 上下文 * 函数说明:1. 逐 fd 按序:①会话不可继续(TCP 断连 / UDP 芯片侧被关)→ 以已发字节数结束 * (剩余字节不可能送达) * 2. ②推进:发送缓冲空闲则写下一块(每轮至多一块——写完即清 send_ready, * 下一块须等 SENBUF_FREE 中断重新置位,由 net_poll 刷新) * 3. ③全部写完 → 以总长度结束;④无进展超时 → 以已发字节数结束 * 4. 结果恒为已发字节数(短写由调用方按 `ret != len` 判定) * 5. 停滞计时按**无进展**刷新:大文件传输总时长必然超过单块预算, * 按总时长判会误杀(与 recv 侧"到点"语义的差异点) */ void net_send_pending_tick(net_sock_t *p_socks) { /* Socket 循环索引 */ int i; for (i = 0; i < NET_MAX_SOCKETS; i++) { /* 本 fd 挂起请求 */ net_send_pending_t *p = &s_net_send[i]; /* 本 fd 控制块 */ net_sock_t *p_sock = &p_socks[i]; /* 无挂起请求:跳过 */ if (p->active == 0U) { continue; } /* fd 已关闭(正常路径在 close 内先取消;此处兜底,防任何路径漏掉通知) */ if (p_sock->in_use == 0U) { net_send_pending_complete(i, p->sent); continue; } /* ① 会话已不可继续 → 剩余字节不可能送达,按已发字节数结束。 * 判据见 net_send_state_down(TCP:state 离开 ESTABLISHED 后即便再回到 * ESTABLISHED 也是**新会话**,旧会话剩余字节绝不能投到新连接上; * UDP:芯片侧 Socket 被关/复位即不可继续)。session_dead 一并覆盖 * (net_types.h:TIMEOUT/DISCONNECT 两条路径都同时置 CLOSED,此处为 * 与 recv 侧判据对齐的冗余保险) */ if ((net_send_state_down(p_sock) != 0) || (p_sock->session_dead != 0U)) { DBG_ERROR("sock%d send pending aborted (%d/%d): session down", i, p->sent, p->len); (void)net_set_errno(NET_ERR_CONNRESET); net_send_pending_complete(i, p->sent); continue; } /* ② 推进:发送缓冲空闲则写下一块 */ if ((p->sent < p->len) && (p_sock->send_ready != 0U)) { /* 本次写块长度 = min(剩余, 硬件发送缓冲上限) */ int chunk = p->len - p->sent; if (chunk > NET_SEND_CHUNK_MAX) { chunk = NET_SEND_CHUNK_MAX; } /* 写入芯片发送 FIFO(与快速路径同一分块粒度,TRAP-NET-16) */ ch395f_write_send_buf((uint8_t)i, (uint8_t *)p->buf + p->sent, (uint16_t)chunk); /* 标记发送缓冲占用中(等 SENBUF_FREE 中断重新置位) */ p_sock->send_ready = 0U; /* 累计进度与统计 */ p->sent += chunk; p_sock->send_bytes += (uint32_t)chunk; /* 进度刷新:停滞判定按"无进展"而非"总时长" */ p->deadline = (NET_SEND_TIMEOUT_MS > 0) ? (HAL_GetTick() + (uint32_t)NET_SEND_TIMEOUT_MS) : 0U; } /* ③ 全部写完:以总长度结束(调用方见 ret == len 即成功) */ if (p->sent >= p->len) { net_send_pending_complete(i, p->sent); continue; } /* ④ 无进展超时:以已发字节数结束(短写;调用方须检查返回值) */ if ((NET_SEND_TIMEOUT_MS > 0) && ((int32_t)(HAL_GetTick() - p->deadline) >= 0)) { DBG_ERROR("sock%d send pending stall (%d/%d)", i, p->sent, p->len); (void)net_set_errno(NET_ERR_TIMEDOUT); net_send_pending_complete(i, p->sent); continue; } } } /* * 函数功能:清除本 fd 的挂起 send(不通知;用于"新 fd 不得继承"与 close 后的幂等清理) * 入口参数:sockfd - Socket 描述符 (0~7) int * 出口参数:无 * 返回值:无 * 限定条件:无 * 函数说明:静默清理不发通知:close / 重建路径先经 cancel 通知,本调用仅保证状态归零(幂等) */ void net_send_pending_clear(int sockfd) { /* 描述符越界:直接返回 */ if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { return; } /* 挂起状态清零(静默,不发通知) */ s_net_send[sockfd].active = 0U; } /* * 函数功能:取消本 fd 的挂起 send 并通知调用方(close / drop_conn / resync / 重建) * 入口参数:sockfd - Socket 描述符 (0~7) int * err - 取消原因(记入 netTask 错误码槽位 + 日志) int * 出口参数:无 * 返回值:无(无在位挂起时静默返回) * 限定条件:netTask 上下文 * 函数说明:以**已发字节数**结束(已写入芯片的字节不回收——对端可能已收到;未发部分 * 放弃,由调用方按短写处理)。取消必通知,否则调用方永久挂死 */ void net_send_pending_cancel(int sockfd, int err) { /* 本 fd 挂起请求指针(只读:完成经 net_send_pending_complete 按 fd 操作) */ const net_send_pending_t *p; /* 描述符越界:静默返回 */ if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { return; } /* 取挂起请求 */ p = &s_net_send[sockfd]; /* 无在位挂起:静默返回 */ if (p->active == 0U) { return; } /* 留痕:取消原因 + 进度(短写程度) */ DBG_ERROR("sock%d send pending cancel (%d/%d, err=%d)", sockfd, p->sent, p->len, err); /* 原因记入 netTask 自己的错误码槽位(结果 >= 0 不会被 complete 代记) */ (void)net_set_errno(err); /* 结束:以已发字节数通知 */ net_send_pending_complete(sockfd, p->sent); } /* * 函数功能:取消全部挂起 send 并逐一通知(网络重建路径) * 入口参数:err - 取消原因 int * 出口参数:无 * 返回值:无 * 限定条件:netTask 上下文;须在控制块清零与芯片复位之前调用 * 函数说明:网络重建(net_stack_bring_up → net_init)前调用,保证所有在位调用方均被唤醒 */ void net_send_pending_cancel_all(int err) { /* 逐 fd 遍历 */ for (int i = 0; i < NET_MAX_SOCKETS; i++) { /* 取消并通知(未在位则静默跳过) */ net_send_pending_cancel(i, err); } } /* * 函数功能:查询本 fd 是否有在途挂起 send —— 宿主能力,供 net_listener 用 * 入口参数:sockfd - Socket 描述符 (0~7) int * 出口参数:无 * 返回值:1 - 有挂起;0 - 无(含描述符越界) int * 限定条件:任意上下文(纯 RAM 读,不发起 RPC) * 函数说明:net_listener_tick 用它跳过"重开 Socket 会摧毁在途传输"的 fd * (挂起 send 保险;与 net_pending_active 的挂起 recv 保险并列) */ int net_send_pending_active(int sockfd) { /* 描述符越界:视为无挂起 */ if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { return 0; } return (s_net_send[sockfd].active != 0U) ? 1 : 0; }