修复Trap22快速复连丢中断致监听死亡:net层周期对账自愈(去抖+冷却),复现用例与回归全绿
This commit is contained in:
@@ -412,6 +412,89 @@ int net_poll(void) {
|
||||
return event_count;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:芯片/本层 socket 状态周期对账(Trap 22 丢中断自愈)
|
||||
* 限定条件:netTask 上下文(由 net_process_messages 每轮调用)
|
||||
* 函数说明:芯片在快速复连场景会丢 CONNECT/DISCONNECT 中断;丢事件后本层无法靠事件
|
||||
* 自愈——典型:对端连接一直占着单连接监听的"后备"(芯片 TCP 仍 ESTABLISHED),
|
||||
* 后续 SYN 全被拒,而应用因未收到 DISCONNECT 仍以为有客户端(永不调用 accept,
|
||||
* 事件驱动自愈无路可走)。故周期回读芯片状态,修正三类失同步:
|
||||
* ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(应用读到 EOF 后自动重监听)
|
||||
* ② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(复用 handle_connect_event)
|
||||
* ③ 本层 LISTENING 而芯片监听已消失 → 重建监听(net_relisten_try)
|
||||
* **去抖 + 冷却**:单次状态读可能落在"刚 open/listen 尚未生效"的瞬态
|
||||
* (2026-09-23 实测:无去抖时监听被反复重建直至放弃),故同一失配需连续
|
||||
* NET_RECONCILE_STABLE_N 次才动手;同一 socket 动手后冷却 NET_RECONCILE_COOLDOWN_MS。
|
||||
* 周期 NET_RECONCILE_PERIOD_MS;每次每在用 socket 一条状态命令,开销可忽略。
|
||||
*/
|
||||
#define NET_RECONCILE_PERIOD_MS 200U /* 对账周期 */
|
||||
#define NET_RECONCILE_STABLE_N 2U /* 连续失配次数(去抖,≈400ms) */
|
||||
#define NET_RECONCILE_COOLDOWN_MS 1000U /* 同一 socket 两次动手的最小间隔(防抖成风暴) */
|
||||
|
||||
static void net_reconcile_tick(void) {
|
||||
static uint32_t s_last = 0U;
|
||||
static uint8_t s_bad[NET_MAX_SOCKETS]; /* 连续失配计数(去抖) */
|
||||
static uint32_t s_cool[NET_MAX_SOCKETS]; /* 冷却到期时刻 */
|
||||
uint32_t now = HAL_GetTick();
|
||||
int i;
|
||||
|
||||
if ((uint32_t)(now - s_last) < NET_RECONCILE_PERIOD_MS) {
|
||||
return;
|
||||
}
|
||||
s_last = now;
|
||||
|
||||
for (i = 0; i < NET_MAX_SOCKETS; i++) {
|
||||
net_sock_t *p_sock = &s_net_socks[i];
|
||||
uint8_t st[2] = {0, 0};
|
||||
int bad = 0;
|
||||
|
||||
if ((p_sock->in_use == 0U) ||
|
||||
((p_sock->state != NET_SOCK_STATE_ESTABLISHED) &&
|
||||
(p_sock->state != NET_SOCK_STATE_LISTENING))) {
|
||||
s_bad[i] = 0U;
|
||||
continue;
|
||||
}
|
||||
ch395f_get_socket_status((uint8_t)i, st);
|
||||
if ((p_sock->state == NET_SOCK_STATE_ESTABLISHED) &&
|
||||
((st[0] == CH395F_SOCKET_CLOSED) || (st[1] == CH395F_TCP_CLOSED))) {
|
||||
bad = 1; /* 芯片已无连接而本层以为还连着 */
|
||||
} else if ((p_sock->state == NET_SOCK_STATE_LISTENING) &&
|
||||
(st[1] == CH395F_TCP_ESTABLISHED)) {
|
||||
bad = 2; /* 芯片已完成握手而无 CONNECT 中断 */
|
||||
} else if ((p_sock->state == NET_SOCK_STATE_LISTENING) &&
|
||||
((st[0] == CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_LISTEN))) {
|
||||
bad = 3; /* 芯片监听已消失(异常关闭) */
|
||||
}
|
||||
|
||||
if (bad == 0) {
|
||||
s_bad[i] = 0U;
|
||||
continue;
|
||||
}
|
||||
if (s_bad[i] < NET_RECONCILE_STABLE_N) {
|
||||
s_bad[i]++;
|
||||
}
|
||||
if ((s_bad[i] < NET_RECONCILE_STABLE_N) ||
|
||||
((int32_t)(now - s_cool[i]) < 0)) {
|
||||
continue; /* 去抖未满,或处于冷却期:本轮不动手 */
|
||||
}
|
||||
s_bad[i] = 0U;
|
||||
s_cool[i] = now + NET_RECONCILE_COOLDOWN_MS;
|
||||
|
||||
if (bad == 1) {
|
||||
DBG_INFO("sock%d reconcile: chip closed (sock=0x%02X tcp=0x%02X) -> disconnect",
|
||||
i, st[0], st[1]);
|
||||
handle_disconnect_event(p_sock); /* 内置陈旧事件甄别 + 状态迁移 + 事件派发 */
|
||||
} else if (bad == 2) {
|
||||
DBG_INFO("sock%d reconcile: connection completed w/o CONNECT irq -> accept", i);
|
||||
handle_connect_event(p_sock); /* 补做 standalone accept 簿记 */
|
||||
} else {
|
||||
DBG_INFO("sock%d reconcile: listen lost (sock=0x%02X tcp=0x%02X) -> relisten",
|
||||
i, st[0], st[1]);
|
||||
(void)net_relisten_try(p_sock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:处理消息队列中的请求
|
||||
* 限定条件:必须在 netTask 上下文中调用(独占 CH395F 和 s_net_socks 访问权)
|
||||
@@ -430,6 +513,7 @@ void net_process_messages(void) {
|
||||
* L3:挂起 recv 的完成检查同位(两 tick 共享主循环节拍,均为 O(每 fd) 非阻塞)。 */
|
||||
net_relisten_retry_tick();
|
||||
net_pending_tick();
|
||||
net_reconcile_tick(); /* Trap 22:芯片/本层状态周期对账(丢中断自愈) */
|
||||
|
||||
while (osMessageQueueGet(netMsgQueueHandle, &msg, NULL, 0) == osOK) {
|
||||
int do_notify = 1;
|
||||
@@ -851,7 +935,10 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen
|
||||
net_poll();
|
||||
|
||||
/* 单连接:本 Socket 自身在 CONNECT 后已转为 ESTABLISHED,
|
||||
* 只有处于 ESTABLISHED 才表示有可接受的连接;否则尚无连接 */
|
||||
* 只有处于 ESTABLISHED 才表示有可接受的连接;否则尚无连接。
|
||||
* 注:芯片丢中断导致的失同步不在此处自愈——单次状态读会误判"刚 listen 尚未生效",
|
||||
* 曾致监听反复重建直至放弃(2026-09-23 实测风暴)。统一由 net_reconcile_tick()
|
||||
* 周期对账(带去抖)处理。 */
|
||||
if (p_listen_sock->state != NET_SOCK_STATE_ESTABLISHED) {
|
||||
/* 连接已断开(CLOSED)且启用自动重监听:先把 Socket 重新置回 LISTEN,
|
||||
* 下一次 accept 即可等待新连接(BSD 风格监听常驻) */
|
||||
@@ -2560,7 +2647,23 @@ static void handle_disconnect_event(net_sock_t *p_sock) {
|
||||
int sockfd = (int)(p_sock - s_net_socks);
|
||||
|
||||
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
|
||||
DBG_ERROR("WARNING: listen sock%d got disconnect", p_sock->ch395_sock);
|
||||
/* Trap 22 / N-6:监听态收到 DISCONNECT —— 芯片在断开后会自动关闭该 Socket
|
||||
* (FUN_PARA 默认行为),本层若不重开则**监听永久死亡**(此后所有 SYN 被拒、不自恢复)。
|
||||
* 触发场景(2026-09-23 生产固件复现:FTP :21 断连后 100ms 快速复连,第 29 轮起全拒):
|
||||
* 断连后 <~1s 的快速复连,芯片完成握手但 CONNECT 中断未上报(无 standalone accept,
|
||||
* 控制块滞留 LISTENING),随后该连接断开 → DISCONNECT 落到本分支。
|
||||
* 处置:回读芯片 socket 状态——已不在监听(芯片已关闭)即重建监听
|
||||
* (net_relisten_try 走 D10 非阻塞重试,失败由重试轮询接管);仍在监听则判陈旧事件丢弃。 */
|
||||
uint8_t st[2] = {0, 0};
|
||||
|
||||
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), relisten now",
|
||||
sockfd, st[0], st[1]);
|
||||
(void)net_relisten_try(p_sock);
|
||||
} 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 会被"关闭 → 重新监听 → 再连接"循环复用,而上一条
|
||||
|
||||
Reference in New Issue
Block a user