feat(net/udp): UDP 数据面并入 netMsgQueue 串行执行(#73) #74
@@ -725,7 +725,7 @@ uint16_t ch395f_link_change_count(void);
|
||||
- **补测阶段又抓到两条(同一批缺口分析顺手挖的)**:③ **槽指针必须在 fd 合法性检查之后取**——`net_udp_recvfrom_exec` 原先先算 `&s_net_udp[sockfd]` 再判 `p_sock == NULL`,越界 fd 立刻得到一个越界槽指针(哪怕当下没解引用,后面登记就是野写)。④ **作废不等于可以不吭声**——`net_udp_bind_exec` 重开芯片 Socket 会丢掉缓冲里的包,却只 `net_udp_clear()`:A 任务正阻塞在 `net_recvfrom`、B 任务重 bind 同一个 fd,A 就永远睡死。现在重开之前先按 `NET_ERR_CONNRESET` 结算并通知(UDP-20 钉住)。教训同 `net_pending`:**任何"取消挂起"的动作都必须走 complete 那条路,clear 只是归零**。
|
||||
- **本轮覆盖率**(收尾那次全仓跑数,`--update-baseline` 已把数字连同本轮提交一起落盘):`net_udp.c` 237/239 行(99.2%)、函数 **14/14**;`net_socket.c` 966/969(99.7%)、函数 **59/59**;`net_pending.c` 173/199(86.9%);全仓 line 8372/9290(90.1%)、47/47 套绿,无回退。剩两处未覆盖:`net_udp.c` 的槽越界兜底(调用方已挡,属防御性代码)与 `net_socket.c` 错误码槽重查竞态(真并发才走得到,改造前后同为那 2 行)。
|
||||
- **台架必验(本轮无板子,主机测替代不了)**:① 两端真机互通,服务器形态收发 + 客户端形态往返,重点看 48 B 这类小应答**载荷完整**且源地址正确;② `sendto` 切形态后 `net_udp_set_peer(fd, NULL)` 能否真恢复收广播——手册只写 DesIP 语义,没写切换时机;③ 截断包排空后下一个包是否完整(scratch 循环在真芯片读指针上的行为);④ 挂起 recv 等待期间的 IWDG 余量;⑤ bind 在 open **之前**写 DesIP 的顺序在真芯片上的实际效果(TRAP-NET-14 的 UDP 侧对照)。
|
||||
- **这 5 条已落成可执行的板级阶段**:阶段 16(`docs/测试说明/板级测试/CH395F_Test_Guide.md` §12.6,板端 `test/board/net_test_task.c` 的 `net_layer_udp_bench_test_main`,PC 侧 `test/host/udp_queue_bench.py`),TC-NET-1601~1609 与 ①~⑤ 的对应关系写在表里。**尚未在真机上跑过**——台架板子 2026-11-03 19:19 起断电(SWD `No target connected`、ping 不通、串口静默),而 PR #74 的合并前提就是这条回归出证据;跑完请把结果回填本条并把上面「台架必验」改写成实测结论。
|
||||
- **这 5 条已落成可执行的板级阶段**:阶段 16(`docs/测试说明/板级测试/CH395F_Test_Guide.md` §12.6,板端 `test/board/net_test_task.c` 的 `net_layer_udp_bench_test_main`,PC 侧 `test/host/udp_queue_bench.py`),TC-NET-1601~1609 与 ①~⑤ 的对应关系写在表里。**真机只跑了个开头**:2026-11-03 夜第一次上台架,① 的上半段已见证据——`TC-NET-1601` 建 socket/bind `:6001`/形态回 SERVER、`TC-NET-1602` 服务端形态收到 20 B 且 `addrlen`/`sin_family`/源端口全对、回显逐字节一致并把形态翻成 CLIENT(剥 8 字节信息头与源地址回填在真芯片上成立);②③④⑤ 未测,板子当晚两次失电(SWD `No target connected`、ping 不通,19:19 与 21:0x 各一次),两趟还各暴露一个**台架**缺陷(peer 被 Windows 的 ICMP 10054 打死;单步预算 8 s < 板端 25 s park 导致整条梯子错位),修法与守卫见上面那份指南。PR #74 的合并前提仍是这条回归跑完整;跑完请把结果回填本条并把上面「台架必验」改写成实测结论。
|
||||
- 本轮改动面:`Drivers/BSP/NET/net_udp.c` 与 `Drivers/BSP/NET/net_udp.h`(新)、`net_socket.c` / `net_socket.h`、`net_pending.c` / `net_pending.h`、`net_types.h`、`MDK-ARM/STM32F407-Demo.uvprojx`、`scripts/check_registration.py`、`test/host/common/ch395f_fake.c` 与 `test/host/common/ch395f_fake.h`、`test/host/net_socket/`(含新分片);文档 → `docs/网络/BSD_Socket_API_使用指南.md` §6.0 / §6.1 / §6.2 / §6.3 / §9.0、`docs/网络/BSD_Socket_API_函数调用关系.md` §4.11、`docs/问题记录/CH395F_Trap_Records.md`(TRAP-NET-14 UDP 段 + 新条)。
|
||||
|
||||
### P3|**明确不做**(把理由写死,免得下次又被列回待办:29 个 `.c` = 4,814 行)
|
||||
|
||||
@@ -1181,11 +1181,30 @@ WAIT_CLOSE 阶段已内置二段补发(只补一次),生产 net 层则靠
|
||||
它只证明**对端脚本**可用,不证明板端行为——真机那一趟仍然一条都不能省。
|
||||
- TC-NET-1607 用 30 s 预算版的挂起等待,不用无限版:同一套 park/唤醒机制,但无人值守的台架不该留一个
|
||||
能把任务钉死的等待。无限版(`timeout_ms = 0` → deadline = 0)的语义由主机单测 UDP-6 钉住。
|
||||
- 本阶段构建实测(2026-11-03,`MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`):Code 257,176 /
|
||||
RO 33,900 / RW 2,060 / ZI 151,596;RW_IRAM1 **96,232 B = 73.4%**(距 92% 红线余 24,354 B),
|
||||
RW_CCM 57,376 B / 87.9% 与产品一致。台账数字以 `test/board/ch395f_test.h` 那段为准。
|
||||
- **本阶段尚未在真机上跑过**:本轮台架的板子 19:19 起断电(SWD 报 `No target connected`、
|
||||
ping 不通、串口静默),而 PR #74 的合并前提就是这条回归跑完拿到证据。
|
||||
- 本阶段构建实测(2026-11-03 加固后,`MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`):Code 257,388 /
|
||||
RO 34,136 / RW 2,060 / ZI 151,596;RW_IRAM1 **96,232 B = 73.4%**(距 92% 红线余 24,354 B),
|
||||
RW_CCM 57,376 B / 87.9% 与产品一致。产品态(宏回默认后重新构建)Code 249,124 / RW 2,052 /
|
||||
ZI 149,996,与本阶段之前逐字节一致——阶段 16 的代码在产品里被预处理干净。台账数字以
|
||||
`test/board/ch395f_test.h` 那段为准。
|
||||
- **真机进度(2026-11-03 夜)**:第一次上台架已跑通**前半段**——板端
|
||||
`TC-NET-1601a/b/c`(建 socket、bind :6001、`net_udp_get_mode` 回 SERVER)与
|
||||
`TC-NET-1602`(服务端形态收到 20 B、`addrlen`/`sin_family`/源端口都对、逐字节一致,回显后模式
|
||||
翻成 CLIENT)**全绿**,PC peer 侧同样收到那 20 B 回显;这就是队列化后的 UDP 收包路径在真芯片上
|
||||
的第一份证据(剥 8 字节信息头 + 源地址回填 + 回显发送全部正确)。**1603 之后仍未测**:板子在
|
||||
21:0x 再次失电(SWD `No target connected`、ping 不通、ST-LINK 探针仍在),与 19:19 那次同一现象。
|
||||
- 首跑抓到两处**台架**缺陷(都不是被测代码的问题,已改掉并各自有守卫):
|
||||
1. peer 死在 `WinError 10054`——板子还没 bind 时那一下 ICMP 端口不可达会被 Windows 记在套接句上,
|
||||
下一次 `recvfrom` 才抛出来;本机 CPython 3.14.7 既没有 `socket.SIO_UDP_CONNRESET` 也不接受它的
|
||||
数值,所以主力防线改成把 10054 当「这一轮没收到」继续重试(`udp_queue_bench.py` 的
|
||||
`new_peer_socket` / `_recv_after` / `_recv_maybe`),并由
|
||||
`test/host/udp_queue_bench_loopback.py` 的冷端口探针守着(用脚本真正在用的那条路判定,
|
||||
同时拿裸套接句证明这台机器确实会回 ICMP,否则探针没有牙)。
|
||||
2. **一步超时→整条梯子错位**:板端每步等 25 s,peer 每步只等 8 s,于是 1602 之后每一步都踩在板子
|
||||
的上一步预算里,日志看起来像"全灭"。现在两侧各自加固:peer 每步都重发且单步预算 `--step-budget`
|
||||
默认 35 s(小于 26 s 会打警告);板端 `udp_bench_expect` 认载荷第 0 字节的步号,比本步小的旧包
|
||||
丢掉继续等(上限 `UDP_BENCH_STALE_MAX = 8`),比本步大的直接报出来。
|
||||
3. 顺带一条纪律:跑台架期间**不要用 serial-assistant 守护进程取证**——它重启后不会重开 COM3
|
||||
(`status` 里 `ready:false`、`rx_bytes:0`),会把整段板端日志丢光;直接 `pyserial` 落盘更稳。
|
||||
|
||||
---
|
||||
|
||||
|
||||
+54
-18
@@ -1023,6 +1023,7 @@ void net_layer_test_main(void)
|
||||
#define UDP_BENCH_LATE_BUDGET 30000U /* 迟发包那一步的等待预算(比 PC 迟发时刻宽得多) */
|
||||
#define UDP_BENCH_LATE_MIN_MS 7000U /* 迟发包实测等待的下界 */
|
||||
#define UDP_BENCH_LATE_MAX_MS 25000U /* 迟发包实测等待的上界 */
|
||||
#define UDP_BENCH_STALE_MAX 8U /* 单步最多丢掉几个旧步数据报(步序自愈的上限) */
|
||||
|
||||
static uint8_t s_udp_buf[UDP_BENCH_BUF_SIZE];
|
||||
|
||||
@@ -1095,27 +1096,62 @@ static int udp_bench_expect(int fd, uint8_t tag, int exp, int cap, uint32_t to_m
|
||||
{
|
||||
struct net_sockaddr_in from;
|
||||
int alen = (int)sizeof(from);
|
||||
uint32_t t_start;
|
||||
uint32_t left_ms;
|
||||
int stale = 0;
|
||||
int n;
|
||||
int bad;
|
||||
|
||||
(void)memset(&from, 0, sizeof(from));
|
||||
n = net_recvfrom_to(fd, s_udp_buf, cap, to_ms,
|
||||
(src != NULL) ? (struct net_sockaddr *)&from : NULL,
|
||||
(src != NULL) ? &alen : NULL);
|
||||
if (n < 0) {
|
||||
DBG_ERROR("%s: recv tag 0x%02X -> %d (errno=%d)", who, (unsigned)tag, n, net_get_errno());
|
||||
return -1;
|
||||
}
|
||||
if (n != exp) {
|
||||
DBG_ERROR("%s: tag 0x%02X len=%d expect=%d", who, (unsigned)tag, n, exp);
|
||||
return -1;
|
||||
}
|
||||
bad = udp_bench_match(s_udp_buf, n, tag);
|
||||
if (bad != 0) {
|
||||
DBG_ERROR("%s: tag 0x%02X pattern breaks at byte %d (got 0x%02X, want 0x%02X)",
|
||||
who, (unsigned)tag, bad - 1, s_udp_buf[bad - 1],
|
||||
(bad == 1) ? (unsigned)tag : (uint8_t)(((uint32_t)(bad - 1) * 7U + 0x11U) & 0xFFU));
|
||||
return -1;
|
||||
/*
|
||||
* 函数说明(步序自愈):步号写在载荷第 0 字节,所以先认步号再认内容。比本步小的
|
||||
* = 上一步超时后对端重发留下的旧包,丢掉继续等;比本步大的 = 对端抢跑(不该发生,
|
||||
* 报出来好定位)。没有这层判断时,任何一步只要超时一次,后面每一步都会错位 25 秒
|
||||
* ——2026-11-03 第一次上台架就是这样:1602 通了,1603 起整条梯子在 PC 侧 8 秒预算
|
||||
* 里全灭,分不清是板子的问题还是纯粹的错位。只允许有限次丢弃,防止对端脚本写错时
|
||||
* 把包无限吞掉。
|
||||
*/
|
||||
t_start = HAL_GetTick();
|
||||
for (;;) {
|
||||
if ((HAL_GetTick() - t_start) >= to_ms) {
|
||||
/* to_ms==0 在 net_recvfrom_to 里是「非法参数」(0 = 无限等由另一个入口表达),
|
||||
* 预算见底时不能再拿 0 去调它,否则报出来的是 INVAL 而不是超时。 */
|
||||
DBG_ERROR("%s: budget %ums exhausted before tag 0x%02X (stale drops=%d)",
|
||||
who, (unsigned)to_ms, (unsigned)tag, stale);
|
||||
return -1;
|
||||
}
|
||||
left_ms = to_ms - (HAL_GetTick() - t_start);
|
||||
(void)memset(&from, 0, sizeof(from));
|
||||
alen = (int)sizeof(from);
|
||||
n = net_recvfrom_to(fd, s_udp_buf, cap, left_ms,
|
||||
(src != NULL) ? (struct net_sockaddr *)&from : NULL,
|
||||
(src != NULL) ? &alen : NULL);
|
||||
if (n < 0) {
|
||||
DBG_ERROR("%s: recv tag 0x%02X -> %d (errno=%d)", who, (unsigned)tag, n, net_get_errno());
|
||||
return -1;
|
||||
}
|
||||
if (n < 1 || s_udp_buf[0] != tag) {
|
||||
if ((n >= 1) && (s_udp_buf[0] < tag) && ((unsigned)stale < UDP_BENCH_STALE_MAX)) {
|
||||
stale++;
|
||||
DBG_INFO("%s: drop stale tag 0x%02X (drop #%d), still waiting for 0x%02X",
|
||||
who, (unsigned)s_udp_buf[0], stale, (unsigned)tag);
|
||||
continue;
|
||||
}
|
||||
DBG_ERROR("%s: tag 0x%02X got 0x%02X (n=%d, stale drops=%d)",
|
||||
who, (unsigned)tag, (n >= 1) ? (unsigned)s_udp_buf[0] : 0U, n, stale);
|
||||
return -1;
|
||||
}
|
||||
if (n != exp) {
|
||||
DBG_ERROR("%s: tag 0x%02X len=%d expect=%d", who, (unsigned)tag, n, exp);
|
||||
return -1;
|
||||
}
|
||||
bad = udp_bench_match(s_udp_buf, n, tag);
|
||||
if (bad != 0) {
|
||||
DBG_ERROR("%s: tag 0x%02X pattern breaks at byte %d (got 0x%02X, want 0x%02X)",
|
||||
who, (unsigned)tag, bad - 1, s_udp_buf[bad - 1],
|
||||
(bad == 1) ? (unsigned)tag : (uint8_t)(((uint32_t)(bad - 1) * 7U + 0x11U) & 0xFFU));
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (src != NULL) {
|
||||
TEST_CHECK(alen == (int)sizeof(struct net_sockaddr_in),
|
||||
|
||||
@@ -66,10 +66,38 @@ def payload(tag: int, n: int) -> bytes:
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def new_peer_socket() -> socket.socket:
|
||||
"""建一条 UDP 源套接句;能关就关掉 Windows 的「ICMP 端口不可达回抛」。
|
||||
|
||||
真机第一次上台架(2026-11-03 20:54)就是栽在这里:板子还没把 UDP socket 打开时,
|
||||
我们发过去的第一颗包换来一个 ICMP 端口不可达,Windows 把它记在套接句上,随后的
|
||||
recvfrom 直接抛 `ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个
|
||||
现有的连接`——对端脚本当场死在第一条用例,板子白等到 25 秒超时,两边看到的都是
|
||||
「对端缺席」,分不清是谁的问题。
|
||||
|
||||
能关掉这个行为当然最好(SIO_UDP_CONNRESET=0),但实测本机 CPython 3.14.7 既没有
|
||||
`socket.SIO_UDP_CONNRESET` 这个常量,也不接受把它的数值 0x9800000C 直接喂给 ioctl
|
||||
(`ValueError: invalid ioctl command`)。所以**主力防线是 recv 侧把 WinError 10054
|
||||
当成「这一轮没收到」继续重试**(见 _recv_after / _recv_maybe 的 except),这里的
|
||||
ioctl 只是有常量时的锦上添花。判据由 udp_queue_bench_loopback.py 的冷端口探针守着。
|
||||
"""
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
flag = getattr(socket, "SIO_UDP_CONNRESET", None)
|
||||
if flag is not None:
|
||||
try:
|
||||
s.ioctl(flag, False)
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
return s
|
||||
|
||||
|
||||
class Peer:
|
||||
def __init__(self, board: str, timeout: float) -> None:
|
||||
def __init__(self, board: str, timeout: float, step_budget: float = 35.0) -> None:
|
||||
self.board = board
|
||||
self.timeout = timeout
|
||||
# 单步总预算:必须盖过板端单步等待(UDP_BENCH_WAIT_MS = 25 s)再加余量,理由见
|
||||
# exchange() 里的说明。--first-wait 只覆盖第一步(板子要启动+起监听)。
|
||||
self.step_budget = step_budget
|
||||
self.total = 0
|
||||
self.failed = 0
|
||||
self.ports = {}
|
||||
@@ -112,26 +140,29 @@ class Peer:
|
||||
# 先清掉上一步可能迟到的回显,避免把它当成本步的应答
|
||||
self._drain(sock)
|
||||
|
||||
deadline = time.time() + (first_wait if first_wait > 0 else self.timeout)
|
||||
# 每一步都重发,且单步预算必须盖过板端单步等待(UDP_BENCH_WAIT_MS = 25 s)再加
|
||||
# 余量:板端只要超时一次就会晚一步,本端若只等 8 秒,后面每一步都会踩进板子的
|
||||
# 上一步预算里,整条梯子一起错位(2026-11-03 第一次上台架就是这样全灭)。
|
||||
# 重发是安全的:板端按载荷第 0 字节的步号丢掉旧包(步序自愈),本端每步开头也
|
||||
# 会先 drain 掉迟到的回显。
|
||||
step_budget = first_wait if first_wait > 0 else self.step_budget
|
||||
deadline = time.time() + step_budget
|
||||
got = None
|
||||
addr = None
|
||||
while got is None:
|
||||
sock.sendto(data_out, dst)
|
||||
# 首步:板子可能还在启动/还没 bind,每 4 秒重发一次直到首步总预算用尽。
|
||||
# 重发不会污染下一步——下一步开头会先 drain 掉多出来的那一份回显。
|
||||
attempt = 4.0 if first_wait > 0 else self.timeout
|
||||
left = deadline - time.time()
|
||||
if left <= 0:
|
||||
return self.check(False, "TC 0x{0:02X} 端口 {1}: 板端无回显(预算内)"
|
||||
.format(tag, port))
|
||||
return self.check(False, "TC 0x{0:02X} 端口 {1}: 板端 {2:.0f}s 内无回显"
|
||||
.format(tag, port, step_budget))
|
||||
attempt = 4.0 if first_wait > 0 else 6.0
|
||||
try:
|
||||
got, addr = self._recv_after(sock, min(attempt, left))
|
||||
self.note_port(sock, tag)
|
||||
except socket.timeout:
|
||||
if first_wait <= 0:
|
||||
return self.check(False, "TC 0x{0:02X} 端口 {1}: 板端 {2:.0f}s 内无回显"
|
||||
.format(tag, port, self.timeout))
|
||||
self.log("TC 0x{0:02X}: 无回显,重发(首步剩余 {1:.0f}s)".format(tag, left))
|
||||
except (socket.timeout, ConnectionResetError):
|
||||
# ConnectionResetError = 对端端口当时没开换来的 ICMP 不可达(见
|
||||
# new_peer_socket 的说明);这里和「这一轮没收到」同等对待,继续重试。
|
||||
self.log("TC 0x{0:02X}: 无回显,重发(本步剩余 {1:.0f}s)".format(tag, left))
|
||||
# 一个数据报进、一个数据报出——唯一例外是本步载荷超过芯片发送缓冲(1KB):
|
||||
# 那是已登记的芯片限制(NET_SEND_CHUNK_MAX 本是 TCP 流的分块口径,UDP 上每块
|
||||
# 各发一包,改造前后同一条路径)。>1KB 的步骤按「预期拆包」判,并把实测拆分打
|
||||
@@ -188,7 +219,7 @@ class Peer:
|
||||
try:
|
||||
sock.settimeout(budget)
|
||||
return sock.recvfrom(4096)
|
||||
except socket.timeout:
|
||||
except (socket.timeout, ConnectionResetError):
|
||||
return None
|
||||
finally:
|
||||
sock.settimeout(self.timeout)
|
||||
@@ -212,6 +243,8 @@ def main() -> int:
|
||||
help="对应 TC-NET-1606 的静默时长(板端探针预算 1 秒)")
|
||||
ap.add_argument("--late", type=float, default=12.0,
|
||||
help="对应 TC-NET-1607 的迟发时长:板端在这一步无限等包")
|
||||
ap.add_argument("--step-budget", type=float, default=35.0,
|
||||
help="单步总预算(含重发),必须盖过板端 25 秒单步等待,默认 35 秒")
|
||||
ap.add_argument("--port", type=int, default=0,
|
||||
help="覆盖阶段 16 的首个监听端口(默认用板端 UDP_BENCH_PORT 6001)")
|
||||
ap.add_argument("--port2", type=int, default=0,
|
||||
@@ -225,15 +258,20 @@ def main() -> int:
|
||||
if not (7.0 < args.late <= 22.0):
|
||||
print("--late 必须落在 (7, 22] 秒内,否则板端 7000~25000ms 判定窗会判失败", file=sys.stderr)
|
||||
return 2
|
||||
if args.step_budget <= 26.0:
|
||||
# 只提醒不拦死:自检脚本用假板子跑,预算给得很小;真机台架则会看到这条。
|
||||
print("[warn] --step-budget={0:.0f}s 小于板端单步等待 25s + 余量:"
|
||||
"板端一旦超时一次,本端后续每一步都会踩进板子的上一步预算里(错位全灭)"
|
||||
.format(args.step_budget), file=sys.stderr)
|
||||
|
||||
peer = Peer(args.board, args.timeout)
|
||||
peer = Peer(args.board, args.timeout, args.step_budget)
|
||||
# 三条独立源套接句 = 三个不同临时源端口,1604「换源端口」就是这么来的。
|
||||
# 端口号不能在未 connect 时用 getsockname() 取(Windows 会 WinError 10022,
|
||||
# 端口要到第一次 sendto 才分配),也不能 connect()——connect 会让内核丢掉
|
||||
# 非对端的包,那正是这一步要验的东西。改成首次收包后再查,照样打得出来。
|
||||
socks = {"A": socket.socket(socket.AF_INET, socket.SOCK_DGRAM),
|
||||
"B": socket.socket(socket.AF_INET, socket.SOCK_DGRAM),
|
||||
"C": socket.socket(socket.AF_INET, socket.SOCK_DGRAM)}
|
||||
socks = {"A": new_peer_socket(),
|
||||
"B": new_peer_socket(),
|
||||
"C": new_peer_socket()}
|
||||
peer.log("对端 = {0},三条源套接句 A/B/C 的临时源端口在首次收包后打印".format(args.board))
|
||||
|
||||
# 板端每一步用新源端口的条件:1604(换源端口验形态恢复)及其后
|
||||
|
||||
@@ -54,9 +54,61 @@ def echo_one(sock, deadline, stop):
|
||||
sock.sendto(echo[off:off + CHIP_SEND_BUF], addr)
|
||||
|
||||
|
||||
def check_cold_port() -> bool:
|
||||
"""对端脚本必须先扛住「板子还没把 socket 打开」这段时间——真机第一次上台架就死在这。
|
||||
|
||||
往一个确定没人监听的端口发一颗包,Windows 会收下 ICMP 端口不可达并记在套接句上,
|
||||
下一次 recvfrom 就抛 WinError 10054。这里走**脚本真正在用的那条路**(Peer._recv_maybe),
|
||||
要求它返回 None(= 这一轮没收到,继续重试),而不是把异常抛穿。裸套接句那一段只是
|
||||
证明这台机器确实会回 ICMP——不然这个探针没有牙,别把它当唯一证据。
|
||||
"""
|
||||
dead = 6099 # 假板子只绑 6001/6002,这个端口确定空着
|
||||
ok = False
|
||||
p = peer.Peer("127.0.0.1", 1.0)
|
||||
s = peer.new_peer_socket()
|
||||
try:
|
||||
s.settimeout(1.0)
|
||||
s.sendto(b"\x00", ("127.0.0.1", dead))
|
||||
try:
|
||||
r = p._recv_maybe(s, 1.0)
|
||||
if r is None:
|
||||
ok = True
|
||||
else:
|
||||
print("[selftest] 冷端口探针:竟然收到了数据报,探针失效", flush=True)
|
||||
except ConnectionResetError:
|
||||
print("[selftest] 冷端口探针:Peer._recv_maybe 把 WinError 10054 抛穿了"
|
||||
"(真机上对端会当场死在第一条用例)", flush=True)
|
||||
finally:
|
||||
s.close()
|
||||
except OSError as exc:
|
||||
print("[selftest] 冷端口探针建句/发送失败:{0}".format(exc), flush=True)
|
||||
|
||||
raw = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
raw.settimeout(1.0)
|
||||
raw.sendto(b"\x00", ("127.0.0.1", dead))
|
||||
try:
|
||||
raw.recvfrom(64)
|
||||
print("[selftest] 裸套接句也收到了数据报(探针无牙,仅记录)", flush=True)
|
||||
except ConnectionResetError:
|
||||
print("[selftest] 裸套接句抛 WinError 10054 —— 这台机器确实会踩,"
|
||||
"上面那条判定有牙", flush=True)
|
||||
except socket.timeout:
|
||||
print("[selftest] 裸套接句超时(本机没回 ICMP,探针无牙,仅记录)", flush=True)
|
||||
except OSError as exc:
|
||||
print("[selftest] 裸套接句探针失败:{0}".format(exc), flush=True)
|
||||
finally:
|
||||
raw.close()
|
||||
return ok
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
if not check_cold_port():
|
||||
print("=== UDP Peer Loopback Self-Test: FAIL(冷端口探针没过,先修 new_peer_socket)===",
|
||||
flush=True)
|
||||
return 1
|
||||
deadline = time.time() + RUNOUT_S
|
||||
stop = threading.Event()
|
||||
for listen_port in LISTEN_PORTS:
|
||||
@@ -71,8 +123,10 @@ def main() -> int:
|
||||
time.sleep(0.3)
|
||||
|
||||
# --late 必须落在板端 7000~25000 ms 判定窗内;这里取 9 秒,比默认 12 秒省一点台架外时间
|
||||
# --step-budget 8:假板子秒回,用不到真机那 35 秒预算;真机台架千万别照抄这个值
|
||||
#(真机上必须盖过板端单步 25 秒等待,否则一次超时就整条梯子错位)。
|
||||
sys.argv = ["udp_queue_bench.py", "--board", "127.0.0.1",
|
||||
"--first-wait", "15", "--timeout", "6",
|
||||
"--first-wait", "15", "--timeout", "6", "--step-budget", "8",
|
||||
"--probe-silence", "2.0", "--late", "9.0"]
|
||||
try:
|
||||
rc = peer.main()
|
||||
|
||||
Reference in New Issue
Block a user