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:基线预存在问题(另一在途任务未提交文件),非本次引入。
This commit is contained in:
2026-10-12 14:10:43 +08:00
parent 15bffce52f
commit 7cb5af05fe
3 changed files with 180 additions and 67 deletions
+170 -58
View File
@@ -535,11 +535,12 @@ int net_poll(void) {
* 函数说明:1. 芯片在快速复连场景会丢 CONNECT/DISCONNECT 中断;丢事件后本层无法靠事件自愈
* 2. 典型:对端连接占着单连接监听的"后备"(芯片 TCP 仍 ESTABLISHED),后续 SYN 全被拒
* 3. 应用因未收到 DISCONNECT 仍以为有客户端,永不调用 accept,事件驱动自愈无路可走
* 4. 故周期回读芯片状态,修正三类失同步:
* 5. ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(应用读到 EOF 后自动重监听)
* 4. 故周期回读芯片状态,修正**两类**失同步:
* 5. ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(只置事实;重建交 pump)
* 6. ② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(复用 handle_connect_event)
* 7. ③ 本层 LISTENING 而芯片监听已消失 → 重建监听(net_relisten_try)
* 7b.④ 本层 CLOSED 但 fd 在用且已绑端口(孤儿监听)→ 重建监听(B10)
* 7. ⚠ N-32 P2(2026-10-12):原③(本层 LISTENING 丢监听)与④(孤儿监听)
* 已**整体移交** `net_listener_pump` —— 本函数内不再有任何重建动作
* (单一所有者);认领门槛相应收窄为 ESTABLISHED/LISTENING
* 8. 单次状态读可能落在"刚 open/listen 尚未生效"的瞬态
* 9. 同一失配需连续 NET_RECONCILE_STABLE_N 次才动手(2026-09-23 实测:无去抖时监听被反复重建直至放弃)
* 10. 同一 socket 动手后冷却 NET_RECONCILE_COOLDOWN_MS,防去抖成风暴
@@ -576,23 +577,13 @@ static void net_reconcile_tick(void) {
/* 失同步类型:1=连接已失 2=握手已成丢中断 3=监听已失 */
int bad = 0;
/* 对账认领范围:ESTABLISHED / LISTENING,外加"孤儿监听"(④)。
* 孤儿 = fd 在用、**监听型**(auto_relisten,仅 net_listen_locked:1135 置位)
* 且已绑端口的 Socket 停在 CLOSED:断开事件已被 handle_disconnect_event
* 消费(置 CLOSED、不再向应用上报 EOF),应用那边还在等 net_recv 的 0
* 才肯重建 —— 两边都不动手,芯片也不会自己回 LISTEN,端口就此永久拒连
* (B10,2026-10-12 实测 :7003 芯片侧 0x05/0x00 = Socket 开着但不在
* LISTEN,30s 不自恢复)。
* ⚠ auto_relisten 是这里的**决定性门**:少了它,"本层 CLOSED + 已绑端口"
* 的出向连接(板级 Phase 7 TC701~703 用 net_connect 绑本地口)会被误判
* 孤儿并被 relisten 成监听器 */
/* 对账认领范围:**仅 ESTABLISHED / LISTENING**。
* ⚠ 原先还认领"孤儿监听"(CLOSED + auto_relisten + 已绑端口)以驱动对账④;
* N-32 P2 把③④整体移交给 net_listener_pump(唯一重建入口),本函数只剩
* ①②——两者都以 ESTABLISHED/LISTENING 为前提,故认领门槛相应收窄。 */
if ((p_sock->in_use == 0U) ||
((p_sock->state != NET_SOCK_STATE_ESTABLISHED) &&
(p_sock->state != NET_SOCK_STATE_LISTENING) &&
!((p_sock->state == NET_SOCK_STATE_CLOSED) &&
(p_sock->auto_relisten != 0U) &&
(p_sock->type == NET_SOCK_STREAM) &&
(p_sock->local_port != 0U)))) {
(p_sock->state != NET_SOCK_STATE_LISTENING))) {
s_bad[i] = 0U;
continue;
}
@@ -616,17 +607,9 @@ static void net_reconcile_tick(void) {
if (!((peer != 0U) && (peer == p_sock->last_peer_port))) {
bad = 2;
}
} else if ((p_sock->state == NET_SOCK_STATE_LISTENING) &&
((st[0] == CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_LISTEN))) {
/* ③ 本层 LISTENING 而芯片监听已消失 → 重建监听 */
bad = 3;
} else if ((p_sock->state == NET_SOCK_STATE_CLOSED) &&
((st[0] == CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_LISTEN))) {
/* ④ 孤儿监听:本层 CLOSED + fd 在用 + **监听型** + 已绑端口,芯片侧不在
* LISTEN(监听型这一条由上方认领门槛保证)→ 与③同一处置,但触发源不同
* (③是监听态丢监听,④是压根没人重建) */
bad = 4;
}
/* ③④(本层 LISTENING 丢监听 / 本层 CLOSED 孤儿监听)已移交
* net_listener_pump —— N-32 P2 单一所有者,本函数不再做重建动作 */
/* 无失同步:清零计数,转下一 socket */
if (bad == 0) {
@@ -647,31 +630,134 @@ static void net_reconcile_tick(void) {
s_cool[i] = now + NET_RECONCILE_COOLDOWN_MS;
if (bad == 1) {
/* 类型①:按断开处理(应用读到 EOF 后自动重监听) */
/* 类型①:按断开处理(应用读到 EOF 后由 pump 重建监听) */
DBG_INFO("sock%d reconcile: chip closed (sock=0x%02X tcp=0x%02X) -> disconnect",
i, st[0], st[1]);
/* 复用断开状态机(含陈旧事件甄别 + 状态迁移 + 事件派发) */
/* 复用断开状态机(含陈旧事件甄别 + 状态迁移 + 事件派发);
* 只置事实,重建由 net_listener_pump 统一做(N-32 P2) */
handle_disconnect_event(p_sock);
} else if (bad == 2) {
} else {
/* 类型②:补做 standalone accept */
DBG_INFO("sock%d reconcile: connection completed w/o CONNECT irq -> accept", i);
/* 复用 CONNECT 事件的簿记路径 */
handle_connect_event(p_sock);
} else if (bad == 3) {
/* 类型③:重建监听(单次尝试,失败进 D10 重试队列) */
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);
} else {
/* 类型④:孤儿监听 —— 补一次重建(B10)。net_relisten_try 自带
* "已在重试队列则不重复登记"保护,与 D10 队列 / Trap 23 沉降隔离兼容 */
DBG_INFO("sock%d reconcile: orphan listener (chip=0x%02X/0x%02X) -> relisten",
i, st[0], st[1]);
(void)net_relisten_try(p_sock);
}
}
}
/* 监听器 pump 周期(对账 200ms 的 1/2:比它快、又不至于像 50ms 那样
* 频繁 close/open 芯片 Socket 与消费者收发撞车——2026-10-12 台架实测
* 50ms 会把 uiTask 最坏单轮从 172ms 推到 1034ms) */
#define NET_LISTENER_PUMP_PERIOD_MS 100U
/*
* 函数功能:监听器 pump(N-32 P2「单一所有者」)—— 本层**唯一**对监听型
* Socket 调 net_relisten_try 的地方(应用显式 NET_MSG_RELISTEN 除外)
* 入口参数:无
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文(由 net_process_messages 每轮调用)
* 函数说明:1. 设计意图(设计文档 §4 不变量 II):其它所有位置只**置事实**
* (session_dead / 状态迁移 / 事件派发),不**做动作**——于是
* "谁先看到 DISCONNECT 就谁重建"这类竞争在结构上消失
* 2. 覆盖三种情形(前两种原属对账③④,第三种为 U11 缺口):
* ① 本层 LISTENING 而芯片监听已失位 → 重建(原对账③)
* ② 本层 CLOSED(会话已结束)而芯片不在 LISTEN → 重建(原对账④;
* 原 accept/recv/pending 三处散点只置事实,重建收敛到这里)
* ③ 本层 CLOSED 而芯片**正在** LISTEN → **只做状态重同步**
* (U11):芯片监听器是健康的,重下发 open+listen 会因 USE
* 错误走进关闭分支、把正听着的 Socket 关掉(U12 同源事故);
* 只把本层状态收回 LISTENING,**不发任何芯片命令**
* 3. 去抖/冷却沿用对账的实测结论(2026-09-23 实证:无去抖时监听被
* 反复重建直至放弃)
* 4. session_dead **不在此清零**(与 net_relisten_locked 同一契约:
* 重建监听 ≠ 应用已确认会话结束;清零只发生在 handle_connect_event)
*/
static void net_listener_pump(void) {
/* 上次轮次时刻 */
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;
/* 未到 pump 周期:直接返回 */
if ((uint32_t)(now - s_last) < NET_LISTENER_PUMP_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};
/* 芯片当前是否在 LISTEN */
int chip_listening;
/* 认领门槛:仅监听型 fd(在用 + 自动重监听 + TCP + 已绑端口)。
* auto_relisten 是决定性门:少了它,"本层 CLOSED + 已绑端口"的出向
* 连接(板级 Phase 7 TC701~703 用 net_connect 绑本地口)会被误判为
* 监听器并被 relisten(同对账④的既定口径) */
if ((p_sock->in_use == 0U) ||
(p_sock->auto_relisten == 0U) ||
(p_sock->type != NET_SOCK_STREAM) ||
(p_sock->local_port == 0U)) {
s_bad[i] = 0U;
continue;
}
/* 只处理与监听生命周期相关的两个状态 */
if ((p_sock->state != NET_SOCK_STATE_LISTENING) &&
(p_sock->state != NET_SOCK_STATE_CLOSED)) {
s_bad[i] = 0U;
continue;
}
/* 回读芯片状态(每监听型 fd 一条命令) */
ch395f_get_socket_status((uint8_t)i, st);
chip_listening = ((st[0] != CH395F_SOCKET_CLOSED) &&
(st[1] == CH395F_TCP_LISTEN));
/* 芯片在听 */
if (chip_listening) {
s_bad[i] = 0U;
/* 本层 CLOSED 而芯片在听 = U11(本层状态滞后)→ 只重同步 */
if (p_sock->state == NET_SOCK_STATE_CLOSED) {
DBG_INFO("sock%d pump: chip listening, resync CLOSED->LISTENING (U11)", i);
/* 只改本层状态,**不发任何芯片命令**(同 net_resync_listen 的
* 既定纪律,理由见该函数注释)。session_dead 不清 */
p_sock->state = NET_SOCK_STATE_LISTENING;
/* N-30(Trap 25):死连接对端端口转登幽灵甄别基准 + 去抖清零
* —— 芯片监听器未被重开,芯片侧对端信息可能仍是上一条连接的,
* 必须转登为基准,否则对账② 的幽灵拦截会失效(同 net_resync_listen) */
p_sock->last_peer_port = p_sock->remote_port;
p_sock->lost_conn_seen = 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;
DBG_INFO("sock%d pump: chip not listening (0x%02X/0x%02X, was state=%d) -> relisten",
i, st[0], st[1], (int)p_sock->state);
/* 非阻塞:失败由 D10 重试轮询接管 */
(void)net_relisten_try(p_sock);
}
}
/*
* 函数功能:netTask 主循环核心 —— 自愈 tick 宿主 + 消息队列分派器。每轮
* 先跑三个非阻塞自愈 tick(D10 重监听 / L3 挂起 recv /
@@ -709,8 +795,11 @@ void net_process_messages(void) {
net_relisten_retry_tick();
/* L3 挂起 recv 完成检查:数据到达 / 到点 / 断连时通知调用方 */
net_pending_tick();
/* Trap 22 状态对账:周期回读芯片状态,自愈丢失的中断 */
/* Trap 22 状态对账:周期回读芯片状态,自愈丢失的中断(①②;③④ 已归 pump) */
net_reconcile_tick();
/* N-32 P2:监听器 pump —— 本层唯一重建入口(置于对账之后:对账①②
* 可能刚把会话置 CLOSED,pump 紧接着就能看到并重建) */
net_listener_pump();
/* 队列排空:超时 0 非阻塞取,每条处理一轮,直到本轮队列为空 */
while (osMessageQueueGet(netMsgQueueHandle, &msg, NULL, 0) == osOK) {
@@ -1344,8 +1433,11 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen
DBG_INFO("sock%d closed-with-data accept (drain pending)", sockfd);
} else {
/* 连接已断开(CLOSED)且无残留数据:先把 Socket 重新置回 LISTEN,
* 下一次 accept 即可等待新连接(BSD 风格监听常驻) */
/* 非阻塞:失败由 D10 重试轮询接管 */
* 下一次 accept 即可等待新连接(BSD 风格监听常驻)。
* N-32 P2 修正:**保留本处即时重建** —— 它由消费者调 accept 触发
* (消费者正在等新连接),不是周期性抢先;删掉会把 CLOSED 窗口
* 拉长、导致调用方高频空转 accept(实测 [WD] task stall)。
* 非阻塞:失败由 D10 重试轮询接管 */
(void)net_relisten_try(p_listen_sock);
}
}
@@ -1485,15 +1577,26 @@ int net_listener_armed(int sockfd) {
if (p_sock == NULL) {
return 0;
}
/* 正常监听态 */
/* 正常监听态:放行。net_lost_connect_probe(丢 CONNECT 补 accept)与常规
* accept 都发生在 net_accept_locked 内部,门控挡在外面就永远到不了 */
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
return 1;
}
/* 断连待重建态:监听器仍在服役(auto_relisten + 已绑端口),
* 芯片侧交给 relisten / 对账恢复;同 net_reconcile_tick ④ 的认领口径 */
/* 断连待重建态:**仅当芯片 RX 可能仍有残留数据**时放行,让
* net_accept_locked 的 "closed-but-readable" 接受(N-30,对端"发完即 FIN")
* 仍能拿到该连接。
* ⚠ 无残留时必须返回 0 —— 否则调用方会在整个 CLOSED 窗口内每轮都发起
* net_accept(RPC:入队 + xTaskNotifyWait 回执**无上界**)→ 队列积压 →
* 调用方任务喂不上狗。2026-10-12 P2a 台架实测:删掉即时 relisten 后 CLOSED
* 窗口被拉长到 pump 周期 + 隔离期(~100ms),:21 0ms×50 期间出现 48 条
* `[WD] task stall`(mask=0x67/req=0x77,缺 bit4=UI)。
* 重建本身由 net_listener_pump 负责,调用方**无需**在此催促。
* readable 由 net_poll 在 RECV_OK 置位,"对端发完即 FIN"场景下 CLOSED 之后
* 仍为 1(无人清除),可作"可能仍有残留"的纯 RAM 判据(不发起 SPI)。 */
if ((p_sock->state == NET_SOCK_STATE_CLOSED) &&
(p_sock->auto_relisten != 0U) &&
(p_sock->local_port != 0U)) {
(p_sock->local_port != 0U) &&
(p_sock->readable != 0U)) {
return 1;
}
return 0;
@@ -1938,7 +2041,12 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
* `net_relisten_try` 无状态门,对已 LISTENING 的 fd 再 open+listen
* 会让 ch395f_open_socket 返 USE,其失败分支会 ch395f_close_socket
* —— 把正听着新 SYN 的 Socket 当场关掉(U12 同源事故)。
* session_dead + 已回 LISTENING = 重建已由别处完成,这里只报 EOF。 */
* session_dead + 已回 LISTENING = 重建已由别处完成,这里只报 EOF。
* N-32 P2 修正(2026-10-12 台架实证):**保留本处即时重建**。
* 它是"消费者动作触发"(消费者正在等这条连接的结局),不是周期性
* "抢先";删掉它会把 CLOSED 窗口从 ~0 拉长到 pump 周期+隔离期
* (~100ms),期间调用方高频发起 net_accept(RPC 无上界)→
* `[WD] task stall`(实测 uis 往返 1063ms)。周期性抢先由 pump 收敛即可。 */
if ((p_sock->state == NET_SOCK_STATE_CLOSED) && (p_sock->auto_relisten != 0U)) {
/* 非阻塞:失败由 D10 重试轮询接管 */
(void)net_relisten_try(p_sock);
@@ -2517,7 +2625,10 @@ static void net_pending_tick(void) {
/* 启用自动重监听:先把监听器重新置回 LISTEN。
* ⚠ 内层 `state == CLOSED` 门同 net_recv_locked:session_dead
* 但已回 LISTENING 时重建已由别处完成,再 open+listen 会因
* USE 错误走进关闭分支、关掉正听着的 Socket(U12 同源) */
* USE 错误走进关闭分支、关掉正听着的 Socket(U12 同源)。
* N-32 P2 修正:**保留本处即时重建** —— 它只在有**挂起 recv**
* 时到达(`p->active != 0`),属"消费者在等这条连接结局"的
* 消费者驱动路径,不是周期性抢先(同 net_recv_locked 的理由)。 */
if ((p_sock->state == NET_SOCK_STATE_CLOSED) &&
(p_sock->auto_relisten != 0U)) {
/* 非阻塞:失败由 D10 重试轮询接管 */
@@ -3724,18 +3835,19 @@ static void handle_disconnect_event(net_sock_t *p_sock) {
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
/* Trap 22 / N-6:监听态收到 DISCONNECT——bit3=1(0x08)下芯片**不**关该 Socket(停 0x05/0x00,芯片 TCP 侧已 CLOSED);不重开则此后所有 SYN 全被拒 = 监听永久死亡;
* 触发 = 断连后 <~1s 快速复连时 CONNECT 中断未上报(控制块滞留 LISTENING)后该连接断开(2026-09-23 生产固件复现);
* → 回读芯片状态:监听已失位即重建(D10 非阻塞重试),仍在监听判陈旧丢弃 */
* N-32 P2:本分支**只回读留痕、不再 relisten** —— 重建统一交
* net_listener_pump(每 50 ms 回读芯片状态,监听失位即重建)。原处直接
* relisten 是 12 个所有者之一,且位于 DISCONNECT 事件路径,可能"抢先"
* 于消费者 recv(P1 已消除其后果,P2 消除其可能) */
/* 回读缓冲(芯片状态 2 字节) */
uint8_t st[2] = {0, 0};
/* 回读芯片状态 */
/* 回读芯片状态(仅为留痕:重建与否由 pump 裁决) */
ch395f_get_socket_status((uint8_t)sockfd, st);
/* 监听已失位(芯片已关闭):留痕并重建监听 */
/* 监听已失位(芯片已关闭) */
if ((st[0] == CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_LISTEN)) {
DBG_INFO("sock%d listen lost (chip sock=0x%02X tcp=0x%02X), relisten now",
DBG_INFO("sock%d listen lost (chip sock=0x%02X tcp=0x%02X), pump will relisten",
sockfd, st[0], st[1]);
/* 非阻塞:失败由 D10 重试轮询接管 */
(void)net_relisten_try(p_sock);
} else {
/* 仍在监听:判陈旧事件丢弃 */
DBG_INFO("sock%d stale listen DISCONNECT ignored (chip tcp=0x%02X)", sockfd, st[1]);
+7 -6
View File
@@ -183,13 +183,14 @@ int net_can_accept(int sockfd);
* `net_lost_connect_probe`(丢 CONNECT 中断补 accept,N-30 实证的
* "数据被吞/425"主根因修复)对该端口等于从未运行
* 2. 放行 LISTENING:探针与 "closed-but-readable 接受" 都发生在
* `net_accept_locked` **内部**,门控挡在外面就永远到不了;LISTENING 态
* accept 要么被探针补中、要么返 WOULDBLOCK,两者均安全
* 3. 放行 CLOSED + auto_relisten:单连接模型断连后 fd 停在 CLOSED 等
* relisten(D10 非阻塞重试 / 对账④),此窗口内新 SYN 可能已被芯片
* 接住 → 放行让 `net_accept_locked` 的状态门自行裁决
* `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);