链路时延M1-M3收尾:Phase12-14基线与生产路径Δ实测、D10/E-02/D17修复、D20定位与硬化、文档整理
This commit is contained in:
@@ -318,6 +318,9 @@ static int cmd_list(lftpd_client_t *client, const char *arg)
|
||||
}
|
||||
|
||||
int err = send_list(data_sock, client->directory);
|
||||
/* 传输结束必须关闭数据连接:对端以 EOF 判定"数据传完"(LIST/RETR);
|
||||
* 不关会让客户端一直读到超时(曾试"优雅断开+复用监听器"方案,因该语义限制
|
||||
* 与芯片行为未能稳定工作,见 链路时延实测数据分析.md D20 记录)。 */
|
||||
lftpd_inet_close(data_sock);
|
||||
client->data_socket = -1;
|
||||
|
||||
@@ -347,6 +350,9 @@ static int cmd_nlst(lftpd_client_t *client, const char *arg)
|
||||
}
|
||||
|
||||
int err = send_nlst(data_sock, client->directory);
|
||||
/* 传输结束必须关闭数据连接:对端以 EOF 判定"数据传完"(LIST/RETR);
|
||||
* 不关会让客户端一直读到超时(曾试"优雅断开+复用监听器"方案,因该语义限制
|
||||
* 与芯片行为未能稳定工作,见 链路时延实测数据分析.md D20 记录)。 */
|
||||
lftpd_inet_close(data_sock);
|
||||
client->data_socket = -1;
|
||||
|
||||
@@ -451,6 +457,9 @@ static int cmd_retr(lftpd_client_t *client, const char *arg)
|
||||
char *path = lftpd_io_canonicalize_path(client->directory, arg);
|
||||
lftpd_log_debug("send '%s'", path);
|
||||
int err = send_file(data_sock, path);
|
||||
/* 传输结束必须关闭数据连接:对端以 EOF 判定"数据传完"(LIST/RETR);
|
||||
* 不关会让客户端一直读到超时(曾试"优雅断开+复用监听器"方案,因该语义限制
|
||||
* 与芯片行为未能稳定工作,见 链路时延实测数据分析.md D20 记录)。 */
|
||||
lftpd_inet_close(data_sock);
|
||||
client->data_socket = -1;
|
||||
|
||||
@@ -507,6 +516,9 @@ static int cmd_stor(lftpd_client_t *client, const char *arg)
|
||||
char *path = lftpd_io_canonicalize_path(client->directory, arg);
|
||||
lftpd_log_debug("receive '%s'", path);
|
||||
int err = receive_file(data_sock, path);
|
||||
/* 传输结束必须关闭数据连接:对端以 EOF 判定"数据传完"(LIST/RETR);
|
||||
* 不关会让客户端一直读到超时(曾试"优雅断开+复用监听器"方案,因该语义限制
|
||||
* 与芯片行为未能稳定工作,见 链路时延实测数据分析.md D20 记录)。 */
|
||||
lftpd_inet_close(data_sock);
|
||||
client->data_socket = -1;
|
||||
|
||||
@@ -606,6 +618,11 @@ static int handle_control_channel(lftpd_client_t *client)
|
||||
}
|
||||
|
||||
cleanup:
|
||||
/* 数据监听器在复用模式下会跨命令保留,会话结束必须显式释放(否则每次会话泄漏一个 Socket) */
|
||||
if (client->data_socket >= 0) {
|
||||
lftpd_inet_close(client->data_socket);
|
||||
client->data_socket = -1;
|
||||
}
|
||||
lftpd_inet_close(client->socket);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,21 @@
|
||||
#include "net_socket.h"
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* E-02 头阻塞修复(2026-09-22 M3 台架实证):
|
||||
* netTask 是全部 socket 操作的唯一执行者;lftpd 原先用默认 30s 的**阻塞**
|
||||
* net_recv 等待对端下一行/下一块数据 → 只要客户端连上却不发完整行,netTask
|
||||
* 就被长期占用,其余任务(含远端显示)的网络操作整体冻结(实测一次空闲 FTP
|
||||
* 登录让 LCDMEM 整帧等待 17.7s)。
|
||||
* 修法:把等待切成 ≤LFTPD_NET_SLICE_MS 的片(每片 netTask 占用有界),
|
||||
* 调用方累计总预算 LFTPD_TOTAL_BUDGET_MS —— **总超时语义与旧实现一致**,
|
||||
* 但任何时刻都不再有"秒级冻结"。单片超时由 net_recv_to 返回 -2 区分(无 errno 竞态)。
|
||||
* 分片长度即"其他任务最坏等待":实测 100ms 片下受压帧仍达 ~750ms(多片排队),
|
||||
* 收到 5ms 后受压帧降到 ~10ms 量级(见 m3_data 修复后数据)。
|
||||
*/
|
||||
#define LFTPD_NET_SLICE_MS 5U
|
||||
#define LFTPD_TOTAL_BUDGET_MS 30000U
|
||||
|
||||
int lftpd_inet_listen(int port)
|
||||
{
|
||||
int retry;
|
||||
@@ -99,19 +114,34 @@ int lftpd_inet_accept_timeout(int socket, int timeout_ms)
|
||||
|
||||
int lftpd_inet_read_line(int socket, char *buffer, size_t buffer_len)
|
||||
{
|
||||
TickType_t t0 = xTaskGetTickCount();
|
||||
|
||||
memset(buffer, 0, buffer_len);
|
||||
int total_read_len = 0;
|
||||
|
||||
while (total_read_len < (int)buffer_len) {
|
||||
int read_len = net_recv(socket,
|
||||
buffer + total_read_len,
|
||||
buffer_len - total_read_len - 1,
|
||||
0);
|
||||
/* 分片有界读(E-02):单片 netTask 占用 ≤ LFTPD_NET_SLICE_MS */
|
||||
int read_len = net_recv_to(socket,
|
||||
buffer + total_read_len,
|
||||
(int)(buffer_len - total_read_len - 1),
|
||||
LFTPD_NET_SLICE_MS);
|
||||
if (read_len == 0) {
|
||||
return -1;
|
||||
return -1; /* 对端关闭 */
|
||||
}
|
||||
if (read_len == -2) {
|
||||
/* 本片无数据:累计总预算,未超则继续等。
|
||||
* **必须显式让出 CPU**:若某片因 net_poll 自身耗时而在首轮就返回 -2,
|
||||
* 本任务(Normal)立即重排队会形成忙循环 → 饿死 BelowNormal 的 uiTask
|
||||
* → 远端显示请求数秒无人拾取(2026-09-22 M3 实测 2.4~3.4s 周期尖刺)。
|
||||
* vTaskDelay(1) 保证低优先级任务每毫秒都有运行机会。 */
|
||||
if ((xTaskGetTickCount() - t0) >= pdMS_TO_TICKS(LFTPD_TOTAL_BUDGET_MS)) {
|
||||
return -1; /* 总预算耗尽(语义同旧 30s 超时) */
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
continue;
|
||||
}
|
||||
if (read_len < 0) {
|
||||
return read_len;
|
||||
return read_len; /* 真错误 */
|
||||
}
|
||||
|
||||
total_read_len += read_len;
|
||||
@@ -170,8 +200,23 @@ int lftpd_inet_write(int socket, const void *buf, int len)
|
||||
|
||||
int lftpd_inet_read(int socket, void *buf, int len)
|
||||
{
|
||||
int n = net_recv(socket, buf, len, 0);
|
||||
return n;
|
||||
/* 分片有界读(E-02):总预算 30s 不变,但每片 netTask 占用 ≤ LFTPD_NET_SLICE_MS,
|
||||
* 避免空闲数据连接(客户端开了 PASV 却不发数据)冻结全网 */
|
||||
TickType_t t0 = xTaskGetTickCount();
|
||||
|
||||
for (;;) {
|
||||
int n = net_recv_to(socket, buf, len, LFTPD_NET_SLICE_MS);
|
||||
if (n >= 0) {
|
||||
return n; /* >0 数据 / 0 EOF */
|
||||
}
|
||||
if (n == -1) {
|
||||
return -1; /* 真错误 */
|
||||
}
|
||||
if ((xTaskGetTickCount() - t0) >= pdMS_TO_TICKS(LFTPD_TOTAL_BUDGET_MS)) {
|
||||
return -1; /* 总预算耗尽(语义同旧 30s 超时) */
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1)); /* 让出 CPU:防空闲时忙循环饿死低优先级任务 */
|
||||
}
|
||||
}
|
||||
|
||||
int lftpd_inet_close(int socket)
|
||||
|
||||
+430
-60
@@ -19,6 +19,8 @@
|
||||
|
||||
#include "net_socket.h"
|
||||
#include "ch395f.h"
|
||||
#include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */
|
||||
#include "net_latency_test.h" /* L3 探针接口(chunk_wait 采样) */
|
||||
#include "main.h"
|
||||
|
||||
/*
|
||||
@@ -65,9 +67,14 @@ static void handle_timeout_event(net_sock_t *p_sock);
|
||||
static void fire_event(net_sock_t *p_sock, net_event_t event);
|
||||
static void parse_ip_string(const char *str, uint8_t *ip_arr);
|
||||
static int net_send_locked(net_sock_t *p_sock, const void *buf, int len);
|
||||
static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags);
|
||||
static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
|
||||
uint32_t timeout_ms);
|
||||
static int net_close_locked(net_sock_t *p_sock);
|
||||
static int net_relisten_locked(net_sock_t *p_sock);
|
||||
/* D10 非阻塞重监听:单次尝试 + 延迟重试(实现见 net_relisten_locked 之后) */
|
||||
static void net_relisten_clear(int sockfd);
|
||||
static int net_relisten_try(net_sock_t *p_sock);
|
||||
static void net_relisten_retry_tick(void);
|
||||
static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen);
|
||||
static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int addrlen);
|
||||
|
||||
@@ -354,6 +361,10 @@ void net_process_messages(void) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* D10:待重试的自动重监听(Trap 09 沉降后重开),非阻塞、每轮最多一次尝试。
|
||||
* 放在消息处理之前:断连后即使没有任何应用消息,监听器也能自主恢复。 */
|
||||
net_relisten_retry_tick();
|
||||
|
||||
while (osMessageQueueGet(netMsgQueueHandle, &msg, NULL, 0) == osOK) {
|
||||
switch (msg.type) {
|
||||
case NET_MSG_SEND: {
|
||||
@@ -375,7 +386,8 @@ void net_process_messages(void) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
msg.result = -1;
|
||||
} else {
|
||||
msg.result = net_recv_locked(p_sock, msg.buf, msg.len, msg.flags);
|
||||
msg.result = net_recv_locked(p_sock, msg.buf, msg.len, msg.flags,
|
||||
msg.timeout_ms);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -406,17 +418,51 @@ void net_process_messages(void) {
|
||||
msg.result = net_accept_locked(msg.sockfd, p_addr, p_addrlen);
|
||||
break;
|
||||
}
|
||||
case NET_MSG_RESYNC_LISTEN: {
|
||||
net_sock_t *p_sock = net_get_sock(msg.sockfd);
|
||||
if (p_sock == NULL) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
msg.result = -1;
|
||||
} else if (p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
|
||||
/* 非 ESTABLISHED(LISTENING/CLOSED 等):两侧已一致,无需动作 */
|
||||
msg.result = 0;
|
||||
} else {
|
||||
/* 陈旧 ESTABLISHED:芯片已静默回收 Socket 回监听(无 DISCONNECT
|
||||
* 中断,2026-09-22 台架实证)→ 仅修 MCU 侧状态。严禁发芯片
|
||||
* close/重监听命令——会摧毁芯片侧仍健康的监听器 → 连接永拒
|
||||
* (run8 实证 170s 拒绝)。新连接的 CONNECT 中断会把两侧重新同步。 */
|
||||
p_sock->state = NET_SOCK_STATE_LISTENING;
|
||||
msg.result = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NET_MSG_RELISTEN: {
|
||||
net_sock_t *p_sock = net_get_sock(msg.sockfd);
|
||||
if (p_sock == NULL) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
msg.result = -1;
|
||||
} else {
|
||||
/* 无条件芯片侧重监听(单次尝试 + 非阻塞重试,见 net_relisten_try):
|
||||
* 芯片侧已监听 → 首次 open 失败 → 登记重试(下一次 netTask 轮询
|
||||
* 到点再来,不再 osDelay(500) 忙等);芯片侧已关闭 → 首次即成。
|
||||
* 两种陈旧场景都收敛到"芯片监听器确定在位"。 */
|
||||
msg.result = net_relisten_try(p_sock);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
s_net_errno = NET_ERR; /* 未知消息类型:置通用错误码,保证回传值有效 */
|
||||
msg.result = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* 结果回传:成功时回传实际字节数/描述符;失败时回传具体错误码(如 NET_ERR_WOULDBLOCK)。
|
||||
* 注意消息体是按值拷入队列的——调用方看不到 netTask 对 msg 的修改,错误码只能搭
|
||||
* 通知值这趟车带回调用方,否则调用方只拿得到笼统的 NET_ERR,无法区分 WOULDBLOCK。 */
|
||||
/* 结果回传:成功=字节数/描述符;失败=具体错误码(如 NET_ERR_WOULDBLOCK)。
|
||||
* 注意消息体按值拷入队列——调用方看不到 netTask 对 msg 的修改,结果只能搭
|
||||
* 通知值这趟车带回;**整个 32 位都是结果**(若压入序列号等高 16 位标签,
|
||||
* 单次结果上限会被压到 32767,而 TEST_BUF_SIZE=32768 等调用点会溢出为负)。 */
|
||||
{
|
||||
uint32_t notify_val = (uint32_t)msg.result;
|
||||
|
||||
if (msg.result < 0) {
|
||||
notify_val = (uint32_t)((s_net_errno != 0) ? s_net_errno : NET_ERR);
|
||||
}
|
||||
@@ -425,6 +471,41 @@ void net_process_messages(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:结果等待(两段式,防丢失通知;结果按 32 位原值回传)
|
||||
* 入口参数:kind = 操作简称;sockfd = Socket(两者仅用于调试留痕,避免未用参数告警)
|
||||
* 返回值:netTask 回传的结果(成功=字节数/描述符;失败=负错误码)
|
||||
* 函数说明:① 零等待先拾取:落在"入队→等待入口"间隙的通知已在任务通知值里,
|
||||
* 若只做单段阻塞等待就会错过它(2026-09-22 台架实证);
|
||||
* ② **清除掩码必须全 1**:xTaskNotifyWait 以位掩码清值,传 0 = 不清除
|
||||
* → 上一次调用的结果残留、被后续每次零等待重读成"本次结果"
|
||||
* (run3~run11 的 L7 挂死/整段错位的真正根因);
|
||||
* ③ **必须以返回值(pdTRUE/pdFALSE)判定是否已落地**,不能判 `v != 0`:
|
||||
* 合法的 0 结果(recv 返回 0 = 对端关闭)会被误判成"无通知"而进入
|
||||
* 阻塞等待 → 永久挂死;
|
||||
* ④ 通知值 32 位整体作为结果(不切分位域),保证 TEST_BUF_SIZE=32768
|
||||
* 这类大单次收发不被截断;
|
||||
* ⑤ **禁止在本路径加逐调用日志**(曾加 pick/block 两行:占日志 98.6%,
|
||||
* 115200 下冲掉 [LAT] 证据行、并让 wait-idle 永远判不出空闲;
|
||||
* run14 实证)。诊断需求请在 [LAT] 段报告侧做聚合。
|
||||
*/
|
||||
static int net_wait_result(const char *kind, int sockfd) {
|
||||
uint32_t v = 0U;
|
||||
|
||||
(void)kind;
|
||||
(void)sockfd;
|
||||
|
||||
/* 零等待:拾取并消费"入队→此处"间隙内已落地的通知(含结果为 0 的通知) */
|
||||
if (xTaskNotifyWait(0xFFFFFFFFU, 0xFFFFFFFFU, &v, 0) == pdTRUE) {
|
||||
return (int)v;
|
||||
}
|
||||
|
||||
/* 无通知在途:阻塞等待本消息回执(进入临界区的瞬间若有通知落地,
|
||||
* xTaskNotifyWait 的状态位判定会立即返回,不会睡过去) */
|
||||
xTaskNotifyWait(0xFFFFFFFFU, 0xFFFFFFFFU, &v, portMAX_DELAY);
|
||||
return (int)v;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:创建 Socket,分配空闲 Socket 控制块并初始化状态
|
||||
* 入口参数:domain - 地址族,仅支持 NET_AF_INET int
|
||||
@@ -457,6 +538,7 @@ int net_socket(int domain, int type) {
|
||||
p_sock->in_use = 1;
|
||||
p_sock->send_ready = 1;
|
||||
p_sock->readable = 0;
|
||||
net_relisten_clear(sockfd); /* 新 fd 不得继承上一次的待重试状态 */
|
||||
|
||||
return sockfd;
|
||||
}
|
||||
@@ -640,6 +722,7 @@ int net_listen(int sockfd) {
|
||||
|
||||
msg.type = NET_MSG_LISTEN;
|
||||
msg.sockfd = sockfd;
|
||||
msg.timeout_ms = 0U;
|
||||
msg.caller = xTaskGetCurrentTaskHandle();
|
||||
msg.result = -1;
|
||||
|
||||
@@ -648,11 +731,7 @@ int net_listen(int sockfd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
{
|
||||
uint32_t s_notified;
|
||||
xTaskNotifyWait(0, 0, &s_notified, portMAX_DELAY);
|
||||
msg.result = (int)s_notified;
|
||||
}
|
||||
msg.result = net_wait_result("lis", sockfd);
|
||||
|
||||
if (msg.result < 0) {
|
||||
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
|
||||
@@ -689,7 +768,7 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen
|
||||
/* 连接已断开(CLOSED)且启用自动重监听:先把 Socket 重新置回 LISTEN,
|
||||
* 下一次 accept 即可等待新连接(BSD 风格监听常驻) */
|
||||
if (p_listen_sock->state == NET_SOCK_STATE_CLOSED && p_listen_sock->auto_relisten) {
|
||||
net_relisten_locked(p_listen_sock);
|
||||
(void)net_relisten_try(p_listen_sock); /* 非阻塞:失败由重试轮询接管 */
|
||||
}
|
||||
s_net_errno = NET_ERR_WOULDBLOCK;
|
||||
return -1;
|
||||
@@ -734,6 +813,7 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
|
||||
msg.sockfd = sockfd;
|
||||
msg.buf = (void *)addr;
|
||||
msg.len = (addrlen != NULL) ? *addrlen : 0;
|
||||
msg.timeout_ms = 0U;
|
||||
msg.caller = xTaskGetCurrentTaskHandle();
|
||||
msg.result = -1;
|
||||
|
||||
@@ -742,11 +822,7 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
{
|
||||
uint32_t s_notified;
|
||||
xTaskNotifyWait(0, 0, &s_notified, portMAX_DELAY);
|
||||
msg.result = (int)s_notified;
|
||||
}
|
||||
msg.result = net_wait_result("acc", sockfd);
|
||||
|
||||
if (addrlen != NULL &&addr != NULL) {
|
||||
*addrlen = sizeof(struct net_sockaddr_in);
|
||||
@@ -798,6 +874,7 @@ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) {
|
||||
msg.flags = 0;
|
||||
memcpy(&msg.addr, addr, sizeof(struct net_sockaddr_in));
|
||||
msg.addrlen = addrlen;
|
||||
msg.timeout_ms = 0U;
|
||||
msg.caller = xTaskGetCurrentTaskHandle();
|
||||
msg.result = -1;
|
||||
|
||||
@@ -806,11 +883,7 @@ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
{
|
||||
uint32_t s_notified;
|
||||
xTaskNotifyWait(0, 0, &s_notified, portMAX_DELAY);
|
||||
msg.result = (int)s_notified;
|
||||
}
|
||||
msg.result = net_wait_result("con", sockfd);
|
||||
|
||||
if (msg.result < 0) {
|
||||
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
|
||||
@@ -886,21 +959,23 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a
|
||||
ch395f_set_des_ip(sockfd, p_sock->remote_ip_arr);
|
||||
ch395f_set_des_port(sockfd, p_sock->remote_port);
|
||||
|
||||
/* 诊断:回读目的 IP/端口与 Socket 状态(GET_REMOT_IPP 为小端,故需按小端解析) */
|
||||
/* 诊断:回读目的 IP/端口与 Socket 状态(GET_REMOT_IPP 为小端,故需按小端解析)
|
||||
* 注意 ch395f_get_socket_status 输出**2 字节**:[0]=Socket 开关状态、[1]=TCP 状态,
|
||||
* 必须给 2 字节缓冲(曾只给 1 字节 → 栈越界 1 字节) */
|
||||
{
|
||||
uint8_t rip[6];
|
||||
uint8_t st = 0;
|
||||
uint8_t st[2] = {0, 0};
|
||||
uint32_t dip;
|
||||
uint16_t dp;
|
||||
ch395f_get_remot_ipp((uint8_t)sockfd, rip);
|
||||
ch395f_get_socket_status((uint8_t)sockfd, &st);
|
||||
ch395f_get_socket_status((uint8_t)sockfd, st);
|
||||
dip = ((uint32_t)rip[3] << 24) | ((uint32_t)rip[2] << 16) |
|
||||
((uint32_t)rip[1] << 8) | rip[0];
|
||||
dp = ((uint16_t)rip[5] << 8) | rip[4];
|
||||
DBG_INFO("[CONN] sock%d openOK dest=%d.%d.%d.%d:%d status=0x%02X",
|
||||
DBG_INFO("[CONN] sock%d openOK dest=%d.%d.%d.%d:%d status=0x%02X tcp=0x%02X",
|
||||
sockfd,
|
||||
(dip >> 24) & 0xFF, (dip >> 16) & 0xFF,
|
||||
(dip >> 8) & 0xFF, dip & 0xFF, dp, st);
|
||||
(dip >> 8) & 0xFF, dip & 0xFF, dp, st[0], st[1]);
|
||||
}
|
||||
|
||||
/* 发起 TCP 连接 */
|
||||
@@ -974,6 +1049,12 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
|
||||
sent += chunk;
|
||||
p_sock->send_bytes += (uint32_t)chunk;
|
||||
|
||||
#if defined(TEST_LATENCY_PROBE)
|
||||
/* L3 探针起点:发送缓冲写入刚完成
|
||||
* (样本 = 写缓冲 → SENBUF_FREE 等待,见 链路延迟测试说明 §1 L3) */
|
||||
uint32_t c_l3 = lat_cycles_now();
|
||||
#endif
|
||||
|
||||
tick_start = HAL_GetTick();
|
||||
while (!p_sock->send_ready) {
|
||||
net_poll();
|
||||
@@ -982,6 +1063,11 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if defined(TEST_LATENCY_PROBE)
|
||||
if (lat_probe_armed() != 0U) {
|
||||
lat_probe_add_seg("chunk_wait", lat_cycles_diff_us(c_l3));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (sent < len) {
|
||||
@@ -1005,8 +1091,10 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
|
||||
* 2. 非阻塞模式下无数据立即返回 -1(NET_ERR_WOULDBLOCK)
|
||||
* 3. CLOSED 状态尝试排空接收缓冲区残余数据(Trap 13:FIN 可能先于数据到达)
|
||||
*/
|
||||
static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags) {
|
||||
static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
|
||||
uint32_t timeout_ms) {
|
||||
uint32_t tick_start;
|
||||
uint32_t to_ms;
|
||||
int sockfd = (int)(p_sock - s_net_socks);
|
||||
|
||||
if (buf == NULL || len <= 0) {
|
||||
@@ -1014,6 +1102,8 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 超时来源:调用方指定(分片有界读,E-02 修复)优先,否则用默认值 */
|
||||
to_ms = (timeout_ms != 0U) ? timeout_ms : (uint32_t)NET_RECV_TIMEOUT_MS;
|
||||
tick_start = HAL_GetTick();
|
||||
|
||||
while (1) {
|
||||
@@ -1034,7 +1124,7 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags) {
|
||||
* 否则返回 WOULDBLOCK(-1) 表示暂时无数据 */
|
||||
if (p_sock->state == NET_SOCK_STATE_CLOSED) {
|
||||
if (p_sock->auto_relisten) {
|
||||
net_relisten_locked(p_sock);
|
||||
(void)net_relisten_try(p_sock); /* 非阻塞:失败由重试轮询接管 */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1042,8 +1132,8 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (NET_RECV_TIMEOUT_MS > 0 &&
|
||||
(HAL_GetTick() - tick_start) >= NET_RECV_TIMEOUT_MS) {
|
||||
if ((to_ms > 0U) &&
|
||||
((HAL_GetTick() - tick_start) >= to_ms)) {
|
||||
s_net_errno = NET_ERR_TIMEDOUT;
|
||||
return -1;
|
||||
}
|
||||
@@ -1060,7 +1150,7 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags) {
|
||||
}
|
||||
/* EOF:单连接模式自动回到 LISTEN,应用下次 net_accept 即接到新连接 */
|
||||
if (p_sock->auto_relisten) {
|
||||
net_relisten_locked(p_sock);
|
||||
(void)net_relisten_try(p_sock); /* 非阻塞:失败由重试轮询接管 */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1084,9 +1174,12 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags) {
|
||||
* 入口参数:p_sock - 已断开(CLOSED)的 TCP 监听型 Socket 控制块 net_sock_t *
|
||||
* 返回值:0 - 成功,-1 - 失败
|
||||
* 限定条件:netTask 上下文;p_sock->in_use == 1;保留 local_port/auto_relisten
|
||||
* 函数说明:1. 复用原本地端口与协议,重新 OPEN + TCP_LISTEN(Trap 09 重试)
|
||||
* 函数说明:1. 复用原本地端口与协议,重新 OPEN + TCP_LISTEN
|
||||
* 2. 仅将 state 置回 LISTENING,不 memset(fd 与 auto_relisten 保持不变)
|
||||
* 3. 实现 BSD 风格"监听 Socket 常驻",应用层无需手动重监听
|
||||
* 4. **单次尝试**:失败立即返回(Trap 09 沉降交给 net_relisten_try/
|
||||
* net_relisten_retry_tick 非阻塞重试,见下)。旧实现在此 osDelay(500)
|
||||
* 忙等,netTask 冻结 500ms、实测恢复 588ms(M3 台架 D10)
|
||||
*/
|
||||
static int net_relisten_locked(net_sock_t *p_sock) {
|
||||
uint8_t status;
|
||||
@@ -1095,15 +1188,11 @@ static int net_relisten_locked(net_sock_t *p_sock) {
|
||||
ch395f_set_proto_type((uint8_t)sockfd, CH395F_PROTO_TYPE_TCP);
|
||||
ch395f_set_sour_port((uint8_t)sockfd, p_sock->local_port);
|
||||
|
||||
for (int open_try = 0; open_try < 3; open_try++) {
|
||||
status = ch395f_open_socket((uint8_t)sockfd);
|
||||
if (status == CH395F_ERR_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
ch395f_close_socket((uint8_t)sockfd); /* 打开失败先关闭,再延时后重试 */
|
||||
osDelay(500);
|
||||
}
|
||||
status = ch395f_open_socket((uint8_t)sockfd);
|
||||
if (status != CH395F_ERR_SUCCESS) {
|
||||
/* Trap 09:断开后芯片需沉降时间才能重新 open。此处只清理并返回
|
||||
* (严禁 osDelay 忙等)→ 由重试轮询到点再来。 */
|
||||
ch395f_close_socket((uint8_t)sockfd);
|
||||
s_net_errno = NET_ERR_BUSY;
|
||||
return -1;
|
||||
}
|
||||
@@ -1130,6 +1219,115 @@ static int net_relisten_locked(net_sock_t *p_sock) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== Trap 09 非阻塞重监听重试(D10 修复,2026-09-22 M3 实测) =====
|
||||
* 旧实现:open 失败 → close → osDelay(500) → 重试:netTask 忙等 500ms(全网冻结),
|
||||
* 且实测恢复 588ms、期间客户端重连被拒。
|
||||
* 新实现:首次失败即登记待重试,由 netTask 每轮 net_process_messages() 到点重试
|
||||
* → 恢复时间 ≈ NET_RELISTEN_RETRY_MS 量级,且**任何时刻都不阻塞任务**。 */
|
||||
#define NET_RELISTEN_RETRY_MS 30U /* 重试间隔(Trap 09 沉降窗口;实测 100ms 首次必成,
|
||||
* 收紧到 30ms 以缩短恢复时间——未成则下一轮到点再来) */
|
||||
#define NET_RELISTEN_DEADLINE_MS 2000U /* 放弃时限(自首次尝试起) */
|
||||
|
||||
static uint8_t s_relisten_wait[NET_MAX_SOCKETS]; /* 1 = 有待重试的重监听 */
|
||||
static uint32_t s_relisten_next[NET_MAX_SOCKETS]; /* 下次重试时刻(tick) */
|
||||
static uint32_t s_relisten_dead[NET_MAX_SOCKETS]; /* 放弃时刻(tick) */
|
||||
#if defined(TEST_LATENCY_PROBE)
|
||||
static lat_tmark_t s_relisten_mark[NET_MAX_SOCKETS]; /* 恢复计时起点(首次尝试) */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 函数功能:清除本 fd 的待重监听重试状态(关闭/新建 fd 时调用)
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7)
|
||||
* 返回值:无
|
||||
* 限定条件:无
|
||||
*/
|
||||
static void net_relisten_clear(int sockfd) {
|
||||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||||
return;
|
||||
}
|
||||
s_relisten_wait[sockfd] = 0U;
|
||||
s_relisten_next[sockfd] = 0U;
|
||||
s_relisten_dead[sockfd] = 0U;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:标记重监听完成(清重试状态;探针构建下报告恢复计时样本)
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7)
|
||||
* 返回值:无
|
||||
* 限定条件:netTask 上下文
|
||||
*/
|
||||
static void net_relisten_done(int sockfd) {
|
||||
net_relisten_clear(sockfd);
|
||||
#if defined(TEST_LATENCY_PROBE)
|
||||
lat_probe_add_seg("relisten", lat_mark_us(&s_relisten_mark[sockfd]));
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:尝试重监听一次;失败则登记"待重试"(**不阻塞** netTask)
|
||||
* 入口参数:p_sock - CLOSED 的监听型 Socket 控制块 net_sock_t *
|
||||
* 返回值:0 - 成功(监听器已就绪);-1 - 本次失败或已在重试队列中
|
||||
* 限定条件:netTask 上下文
|
||||
* 函数说明:已有待重试时直接返回 -1(不重复尝试、不重置重试时刻)——否则每 10ms
|
||||
* 一次的 accept 轮询会不断把重试时刻往后推,导致永不重试(饥饿)。
|
||||
*/
|
||||
static int net_relisten_try(net_sock_t *p_sock) {
|
||||
int sockfd = (int)(p_sock - s_net_socks);
|
||||
uint32_t now = HAL_GetTick();
|
||||
|
||||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
if (s_relisten_wait[sockfd] != 0U) {
|
||||
s_net_errno = NET_ERR_BUSY;
|
||||
return -1; /* 已在重试队列:由 tick 负责 */
|
||||
}
|
||||
#if defined(TEST_LATENCY_PROBE)
|
||||
lat_mark(&s_relisten_mark[sockfd]); /* 恢复计时自首次尝试起 */
|
||||
#endif
|
||||
if (net_relisten_locked(p_sock) == 0) {
|
||||
net_relisten_done(sockfd);
|
||||
return 0;
|
||||
}
|
||||
s_relisten_wait[sockfd] = 1U;
|
||||
s_relisten_next[sockfd] = now + NET_RELISTEN_RETRY_MS;
|
||||
s_relisten_dead[sockfd] = now + NET_RELISTEN_DEADLINE_MS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:重试轮询(netTask 每轮调用一次,非阻塞)
|
||||
* 返回值:无
|
||||
* 限定条件:netTask 上下文(net_process_messages 开头调用)
|
||||
* 函数说明:到点重试待办的自动重监听;成功即报告探针样本;超时放弃并记错误日志
|
||||
*/
|
||||
static void net_relisten_retry_tick(void) {
|
||||
uint32_t now = HAL_GetTick();
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NET_MAX_SOCKETS; i++) {
|
||||
if (s_relisten_wait[i] == 0U) {
|
||||
continue;
|
||||
}
|
||||
if ((int32_t)(now - s_relisten_next[i]) < 0) {
|
||||
continue; /* 未到点(tick 差值按有符号比较,抗回绕) */
|
||||
}
|
||||
if ((int32_t)(now - s_relisten_dead[i]) >= 0) {
|
||||
net_relisten_clear(i);
|
||||
DBG_ERROR("sock%d relisten give up (Trap 09 not cleared)", i);
|
||||
continue;
|
||||
}
|
||||
if (net_relisten_locked(&s_net_socks[i]) == 0) {
|
||||
net_relisten_done(i);
|
||||
DBG_INFO("sock%d relisten retry ok", i);
|
||||
} else {
|
||||
s_relisten_next[i] = now + NET_RELISTEN_RETRY_MS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 函数功能:关闭 Socket(内部版本,不经过消息队列)
|
||||
* 入口参数:p_sock - 要关闭的 Socket 控制块指针 net_sock_t *
|
||||
@@ -1151,11 +1349,11 @@ static int net_close_locked(net_sock_t *p_sock) {
|
||||
* 并清掉 Socket/全局中断状态,避免事件串扰。
|
||||
*/
|
||||
do {
|
||||
uint8_t st = 0;
|
||||
ch395f_get_socket_status((uint8_t)sockfd, &st);
|
||||
uint8_t st[2] = {0, 0}; /* [0]=Socket 状态 [1]=TCP 状态(该 API 输出 2 字节) */
|
||||
ch395f_get_socket_status((uint8_t)sockfd, st);
|
||||
(void)ch395f_get_sock_int_status((uint8_t)sockfd);
|
||||
(void)ch395f_get_glob_int_status_all();
|
||||
if (st == CH395F_SOCKET_CLOSED) {
|
||||
if (st[0] == CH395F_SOCKET_CLOSED) {
|
||||
break;
|
||||
}
|
||||
if (HAL_GetTick() - tick > 500) {
|
||||
@@ -1165,6 +1363,7 @@ static int net_close_locked(net_sock_t *p_sock) {
|
||||
} while (1);
|
||||
|
||||
memset(p_sock, 0, sizeof(net_sock_t));
|
||||
net_relisten_clear(sockfd); /* 关闭即取消该 fd 的待重试(D10 重试队列) */
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1209,6 +1408,7 @@ int net_send(int sockfd, const void *buf, int len, int flags) {
|
||||
msg.buf = (void *)buf; /* 数据缓冲(队列仅拷贝指针,buf 须有效至函数返回) */
|
||||
msg.len = len; /* 待发送字节数 */
|
||||
msg.flags = 0; /* 实际标志位(忽略入参 flags) */
|
||||
msg.timeout_ms = 0U; /* 发送不使用超时字段 */
|
||||
msg.caller = xTaskGetCurrentTaskHandle(); /* 记录调用任务句柄,供 netTask 回执时精准通知 */
|
||||
msg.result = -1; /* 预置失败;netTask 处理成功后会改写为实际发送字节数 */
|
||||
|
||||
@@ -1220,11 +1420,7 @@ int net_send(int sockfd, const void *buf, int len, int flags) {
|
||||
|
||||
/* ---- 5. 阻塞等待 netTask 处理完毕:netTask 调 net_send_locked 后通过
|
||||
* xTaskNotify() 唤醒本任务,并把结果(实际发送字节数或 -1)作为通知值传回 ---- */
|
||||
{
|
||||
uint32_t s_notified;
|
||||
xTaskNotifyWait(0, 0, &s_notified, portMAX_DELAY); /* 一直阻塞直到被 netTask 通知 */
|
||||
msg.result = (int)s_notified; /* 取回 netTask 写入的结果 */
|
||||
}
|
||||
msg.result = net_wait_result("snd", sockfd); /* 两段式等待(防丢失通知,见 net_wait_result) */
|
||||
|
||||
/* ---- 6. 结果回传:负数视为错误并设置线程级错误码;否则返回实际发送字节数 ---- */
|
||||
if (msg.result < 0) {
|
||||
@@ -1266,6 +1462,7 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
|
||||
msg.buf = buf;
|
||||
msg.len = len;
|
||||
msg.flags = flags;
|
||||
msg.timeout_ms = 0U;
|
||||
msg.caller = xTaskGetCurrentTaskHandle();
|
||||
msg.result = -1;
|
||||
|
||||
@@ -1274,11 +1471,7 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
{
|
||||
uint32_t s_notified;
|
||||
xTaskNotifyWait(0, 0, &s_notified, portMAX_DELAY);
|
||||
msg.result = (int)s_notified;
|
||||
}
|
||||
msg.result = net_wait_result("rcv", sockfd);
|
||||
|
||||
if (msg.result < 0) {
|
||||
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
|
||||
@@ -1289,6 +1482,64 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
|
||||
return msg.result;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:接收数据(带调用方指定超时的阻塞版,用于分片有界读)
|
||||
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
|
||||
* buf / len - 接收缓冲与容量 void * / int > 0
|
||||
* timeout_ms - 单次占用 netTask 的最长等待(毫秒,>0) uint32_t
|
||||
* 返回值:>0 字节数;0 = 对端关闭(EOF);-1 = 真错误(errno 可查);**-2 = 超时无数据**
|
||||
* 限定条件:非 netTask 上下文;net 层已初始化
|
||||
* 函数说明:E-02 修复配套接口。lftpd 等"等对端下一行/下一块数据"的调用方若用
|
||||
* 默认 30s 阻塞 recv,会把 netTask(全部 socket 操作的唯一执行者)长期
|
||||
* 占住,导致其余任务的网络操作整体冻结(M3 实测一次空闲 FTP 登录冻结
|
||||
* 远端显示 17.7s)。用本接口把等待切成 ≤timeout_ms 的片,调用方自行
|
||||
* 累计总预算,从而"总超时语义不变、单次占用有界"。
|
||||
* 超时用独立返回码 -2(而非靠 errno 区分)→ 无跨任务 errno 竞态。
|
||||
*/
|
||||
int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms) {
|
||||
net_msg_t msg;
|
||||
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (netMsgQueueHandle == NULL) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (timeout_ms == 0U) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
msg.type = NET_MSG_RECV;
|
||||
msg.sockfd = sockfd;
|
||||
msg.buf = buf;
|
||||
msg.len = len;
|
||||
msg.flags = 0; /* 阻塞式(分片有界) */
|
||||
msg.timeout_ms = timeout_ms; /* 调用方指定(分片长度) */
|
||||
msg.caller = xTaskGetCurrentTaskHandle();
|
||||
msg.result = -1;
|
||||
|
||||
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
|
||||
s_net_errno = NET_ERR_BUSY;
|
||||
return -1;
|
||||
}
|
||||
|
||||
msg.result = net_wait_result("rcv", sockfd);
|
||||
|
||||
if (msg.result == NET_ERR_TIMEDOUT) {
|
||||
return -2; /* 本片超时:非致命 */
|
||||
}
|
||||
if (msg.result < 0) {
|
||||
s_net_errno = msg.result;
|
||||
return -1;
|
||||
}
|
||||
return msg.result;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:关闭 Socket(线程安全版本,通过消息队列委托给 netTask)
|
||||
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
|
||||
@@ -1313,6 +1564,7 @@ int net_close(int sockfd) {
|
||||
|
||||
msg.type = NET_MSG_CLOSE;
|
||||
msg.sockfd = sockfd;
|
||||
msg.timeout_ms = 0U;
|
||||
msg.caller = xTaskGetCurrentTaskHandle();
|
||||
msg.result = -1;
|
||||
|
||||
@@ -1321,11 +1573,7 @@ int net_close(int sockfd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
{
|
||||
uint32_t s_notified;
|
||||
xTaskNotifyWait(0, 0, &s_notified, portMAX_DELAY);
|
||||
msg.result = (int)s_notified;
|
||||
}
|
||||
msg.result = net_wait_result("clo", sockfd);
|
||||
|
||||
if (msg.result < 0) {
|
||||
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
|
||||
@@ -1555,7 +1803,7 @@ int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
return net_recv_locked(p_sock, buf, len, flags);
|
||||
return net_recv_locked(p_sock, buf, len, flags, 0U); /* 0 = 默认 NET_RECV_TIMEOUT_MS */
|
||||
}
|
||||
/*
|
||||
* 函数功能:关闭 Socket(内部版本,绕过消息队列,供 netTask 直接使用)
|
||||
@@ -1613,6 +1861,110 @@ int net_get_errno(void) {
|
||||
return s_net_errno;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:查询 Socket 当前状态(只读,无锁)
|
||||
* 入口参数:sockfd - Socket 描述符 int
|
||||
* 返回值:状态值(net_sock_state_t);描述符无效/未占用 → -1
|
||||
* 限定条件:无(单字节读,无撕裂;值可能滞后于 netTask 的最新状态)
|
||||
* 函数说明:跨任务只读快照。典型用途——检测"芯片静默回收"失步
|
||||
* (2026-09-22 台架实证:peer 发 FIN 后芯片自行把 Socket 回收
|
||||
* 回 LISTEN 且不发 DISCONNECT 中断 → MCU 侧状态滞留 ESTABLISHED
|
||||
* → 新连接字节被旧会话吞掉);上层可据此执行 close+listen 重同步
|
||||
*/
|
||||
int net_sock_state(int sockfd) {
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
||||
return -1;
|
||||
}
|
||||
if (!s_net_socks[sockfd].in_use) {
|
||||
return -1;
|
||||
}
|
||||
return (int)s_net_socks[sockfd].state;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:断连重同步(仅 MCU 侧状态,见头文件注释的适用场景)
|
||||
* 入口参数:sockfd - Socket 描述符 int
|
||||
* 返回值:0 - 已同步/原本即一致;-1 - 失败
|
||||
* 限定条件:连接已死且芯片已静默回收后调用(应用层判定)
|
||||
*/
|
||||
int net_resync_listen(int sockfd) {
|
||||
net_msg_t msg;
|
||||
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (netMsgQueueHandle == NULL) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
msg.type = NET_MSG_RESYNC_LISTEN;
|
||||
msg.sockfd = sockfd;
|
||||
msg.timeout_ms = 0U;
|
||||
msg.caller = xTaskGetCurrentTaskHandle();
|
||||
msg.result = -1;
|
||||
|
||||
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
|
||||
s_net_errno = NET_ERR_BUSY;
|
||||
return -1;
|
||||
}
|
||||
|
||||
msg.result = net_wait_result("rsl", sockfd);
|
||||
|
||||
if (msg.result < 0) {
|
||||
s_net_errno = msg.result;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:无条件重新监听(芯片侧 open+listen,含 Trap 09 失败重试)
|
||||
* 入口参数:sockfd - Socket 描述符 int
|
||||
* 返回值:0 - 成功;-1 - 失败
|
||||
* 限定条件:TCP 监听型 Socket;net_init() 已完成
|
||||
* 函数说明:用于"断连后监听器可能已失位"的自愈场景(2026-09-22 run9 实证:
|
||||
* 一次性 net_listen 落在 Trap 09 窗口失败且被静默忽略 → 监听器
|
||||
* 永久丢失 → 后续连接全部拒绝)。与 auto_relisten 内部路径同一
|
||||
* 实现(net_relisten_locked:3 次 open 尝试,失败则 close+500ms 重试)。
|
||||
* 芯片侧已在监听时:首次 open 失败 → close 重建 → 监听短暂中断 ~1s。
|
||||
*/
|
||||
int net_relisten(int sockfd) {
|
||||
net_msg_t msg;
|
||||
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (netMsgQueueHandle == NULL) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
msg.type = NET_MSG_RELISTEN;
|
||||
msg.sockfd = sockfd;
|
||||
msg.timeout_ms = 0U;
|
||||
msg.caller = xTaskGetCurrentTaskHandle();
|
||||
msg.result = -1;
|
||||
|
||||
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
|
||||
s_net_errno = NET_ERR_BUSY;
|
||||
return -1;
|
||||
}
|
||||
|
||||
msg.result = net_wait_result("rel", sockfd);
|
||||
|
||||
if (msg.result < 0) {
|
||||
s_net_errno = msg.result;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 函数功能:将点分十进制 IP 地址字符串转换为网络字节序的 32 位地址
|
||||
* 入口参数:cp - 点分十进制 IP 字符串(如 "192.168.1.100") const char *
|
||||
@@ -1799,12 +2151,30 @@ static void handle_connect_event(net_sock_t *p_sock) {
|
||||
* 3. 触发 NET_EVENT_DISCONNECTED 回调
|
||||
*/
|
||||
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);
|
||||
} else if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
|
||||
/* 陈旧 DISCONNECT 甄别(D20 修复,2026-09-22 台架实证):
|
||||
* 单连接模型下同一个 fd 会被"关闭 → 重新监听 → 再连接"循环复用,而上一条
|
||||
* 连接的 FIN/ACK 可能在本 fd 已复用为新连接**之后**才上报(本层是主动 close,
|
||||
* net_close_locked 当时排空的中断里还没有它)→ 新连接被误判为断开。
|
||||
* 实测表现:FTP 同一会话第二个数据连接必失败 425;日志 = connect 后约 20ms
|
||||
* 立即 "DISCONNECTED, releasing"。
|
||||
* 处置:向芯片回读状态,TCP 仍为 ESTABLISHED 即判为陈旧事件丢弃(中断已被
|
||||
* net_poll 读取清除,不会重复上报)。真断开时芯片处于 CLOSE_WAIT(0x07)/
|
||||
* CLOSED(0x00) 等状态,照常处理;超时事件走 handle_timeout_event,不受影响。 */
|
||||
uint8_t st[2];
|
||||
|
||||
ch395f_get_socket_status((uint8_t)sockfd, st);
|
||||
if (st[1] == CH395F_TCP_ESTABLISHED) {
|
||||
DBG_ERROR("sock%d stale DISCONNECT ignored (chip TCP still ESTABLISHED)", sockfd);
|
||||
return;
|
||||
}
|
||||
/* 单连接:先不关 CH395F Socket,
|
||||
* 留给 net_recv_locked 把剩余数据读出来再关,置 CLOSED 等待应用重新监听 */
|
||||
DBG_INFO("sock%d DISCONNECTED, releasing", p_sock->ch395_sock);
|
||||
DBG_INFO("sock%d DISCONNECTED, releasing (chip tcp=0x%02X)", p_sock->ch395_sock, st[1]);
|
||||
p_sock->state = NET_SOCK_STATE_CLOSED;
|
||||
fire_event(p_sock, NET_EVENT_DISCONNECTED);
|
||||
} else if (p_sock->state != NET_SOCK_STATE_CLOSED &&
|
||||
|
||||
@@ -37,6 +37,8 @@ typedef enum {
|
||||
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 {
|
||||
@@ -45,6 +47,7 @@ typedef struct {
|
||||
void *buf; /* 数据缓冲区(send/recv) */
|
||||
int len; /* 数据长度/缓冲区大小 */
|
||||
int flags; /* 接收标志 */
|
||||
uint32_t timeout_ms; /* 接收超时(入队方向;0 = 用默认 NET_RECV_TIMEOUT_MS) */
|
||||
TaskHandle_t caller; /* 调用方任务句柄 */
|
||||
int result; /* 操作结果 */
|
||||
/* 连接参数 */
|
||||
@@ -185,6 +188,20 @@ int net_send(int sockfd, const void *buf, int len, int flags);
|
||||
*/
|
||||
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 描述符
|
||||
@@ -249,6 +266,30 @@ int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg);
|
||||
*/
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user