Files
STM32F4-Base/Drivers/BSP/NET/net_socket.c
T

2767 lines
120 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:Network Socket Core
* 模块功能:BSD Socket API 核心实现,包含 Socket 管理、状态机、消息队列(线程安全)、事件处理、数据收发等功能,底层调用 CH395F 驱动
* 适用平台:STM32F407ZGTx + CH395F 以太网芯片
* 作者:王建锋
* 创建日期:2026-07-18
* 修改记录:
* 2026-07-18 王建锋 创建初始版本
* 2026-07-19 王建锋 添加消息队列线程安全机制,替换 xQueueCreate 为 CubeMX osMessageQueue
*/
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
#include "cmsis_os.h"
#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"
/*
* 调试输出配置
*/
#define DBG_TAG "[NET]"
#include "dbg_log.h"
/*
* 私有宏定义区
*/
#define NET_PHY_CHECK_INTERVAL_MS 5000U /* PHY 状态定期检查间隔(ms) */
/*
* 全局变量定义区 - 尽量避免使用全局变量
*/
static net_sock_t s_net_socks[NET_MAX_SOCKETS]; /* Socket 控制块数组 */
static int s_net_errno = 0; /* 最后错误码 */
static uint8_t s_net_initialized = 0; /* 初始化标志 */
static uint8_t s_local_ip[4]; /* 本机 IP 缓存(网络序,net_getsockname 用) */
static uint16_t s_dynamic_port = 49152; /* 动态端口分配起始值 */
static volatile uint32_t s_net_generation = 0; /* 网络代次号:网络重建完成时递增(服务据此重建监听) */
/*
* 消息队列 - 线程安全核心
* 外部任务通过 net_send/net_recv/net_close 等 API 将请求放入队列,
* netTask 调用 net_process_messages() 串行处理所有 CH395F 操作
* 由 CubeMX 在 MX_FREERTOS_Init()(osKernelInitialize 之后)中创建
*/
extern osMessageQueueId_t netMsgQueueHandle; /* CubeMX 生成的消息队列句柄 */
/*
* 私有函数声明区
*/
static int alloc_socket(void);
static void handle_connect_event(net_sock_t *p_sock);
static void handle_disconnect_event(net_sock_t *p_sock);
static void handle_recv_event(net_sock_t *p_sock);
static void handle_send_event(net_sock_t *p_sock);
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,
uint32_t timeout_ms, TaskHandle_t caller);
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);
/* L3 pending 化(一期,E-02 结构演进):阻塞等待 = 挂起请求 + 主循环节拍完成。
* "无数据 + 阻塞等待"不再自旋占用 netTask:登记 pending 后立即返回,
* net_pending_tick(与 D10 重监听重试同节奏,net_process_messages 开头每轮调用)
* 按 ①数据 ②到点 ③断连 顺序检查完成条件并通知调用方(语义逐项对齐旧自旋循环);
* 关闭/网络重建/陈旧重同步走显式 settle/cancel 路径(取消必通知,调用方永不挂死)。
* 公开 API 返回契约不变:>0 字节数 / 0 EOF / -1 错误(错误码经通知值回传)。
*/
#define NET_PENDING_REGISTERED 0x70000000 /* 内部哨兵:pending 已登记(分派跳过即时通知;取值不与字节数空间冲突) */
#define NET_PENDING_WATCHDOG_MS 1000U /* 看门狗:到点后 1s 仍未了结 = 完成逻辑缺位 → 强制了结 + 错误日志 */
typedef struct {
uint8_t active; /* 1 = 本 fd 有挂起 recv */
void *buf; /* 调用方缓冲(仅指针,须保持有效至完成) */
int len; /* 缓冲容量 */
uint32_t deadline; /* 截止时刻(tick)= 登记时刻 + 本次预算 */
uint32_t registered; /* 登记时刻(tick;看门狗/日志) */
TaskHandle_t caller; /* 调用方任务(完成通知目标) */
} net_pending_t;
static net_pending_t s_net_pending[NET_MAX_SOCKETS]; /* 每 fd 一个挂起(禁止覆盖:覆盖丢失前调用方通知 → 挂死) */
static TaskHandle_t s_net_task_handle; /* 执行任务(net_init 捕获;防自通知死锁) */
static void net_pending_tick(void);
static void net_pending_complete(int sockfd, int result);
static int net_pending_register(int sockfd, void *buf, int len,
uint32_t to_ms, TaskHandle_t caller);
static void net_pending_clear(int sockfd);
static void net_pending_cancel(int sockfd, int err);
static void net_pending_cancel_all(int err);
static void net_pending_settle_close(net_sock_t *p_sock);
/*
* 函数功能:判断 6 字节 MAC 是否全零
* 入口参数:p_mac - MAC 指针(不可为 NULL) const uint8_t *
* 返回值:true - 全零(= 使用芯片内置 MAC);false - 非全零
* 限定条件:p_mac 非空
*/
static bool s_mac_is_zero(const uint8_t *p_mac) {
for (int i = 0; i < 6; i++) {
if (p_mac[i] != 0u) {
return false;
}
}
return true;
}
/*
* 函数功能:网络子系统初始化(配置 CH395F + 启用多连接模式)
* 入口参数:ip - IP 地址字符串 "192.168.1.100",NULL 使用 DHCP 自动获取 const char *
* mask - 子网掩码字符串 "255.255.255.0",NULL 使用默认值 const char *
* gateway - 网关地址字符串 "192.168.1.1",NULL 使用默认值 const char *
* mac - MAC 地址(6 字节,低字节在前),NULL/全零使用芯片内置 const uint8_t *
* 返回值:0 - 初始化成功 int
* -1 - 初始化失败(芯片检测/复位/协议栈初始化失败) int
* 限定条件:SPI2 已正确初始化;netTask 尚未启动
* 函数说明:1. 检查 CH395F 芯片是否存在并复位
* 2. 启用 TCP Server 多连接模式(bit1=1),设置 TCP MSS、重传参数、KeepAlive 参数
* 3. 配置 IP/网关/子网掩码,或启用 DHCP
* 4. 初始化 CH395F 协议栈并使能 PING 响应
* 5. mac 非空且非全零:初始化成功后设置 MAC(写芯片 EEPROM,
* 每上电重放同值幂等)
*/
int net_init(const char *ip, const char *mask, const char *gateway, const uint8_t *mac) {
ch395f_status_t ret;
/* L3:网络重建前取消全部挂起请求并通知调用方(调用方阻塞等回执,
* 取消不通知 = 永久挂死);必须早于控制块清零与芯片复位 */
net_pending_cancel_all(NET_ERR_CONNRESET);
/* 清零 Socket 控制块 */
memset(s_net_socks, 0, sizeof(s_net_socks));
s_net_errno = 0;
s_net_initialized = 0;
s_net_task_handle = xTaskGetCurrentTaskHandle(); /* 执行任务 = net_init 调用者(NET 层 = netTask / 硬件层 = testTask) */
/* 检查 CH395F 芯片 */
if (ch395f_check_exist() != CH395F_STATUS_OK) {
DBG_ERROR("net_init: ch395f_check_exist FAILED");
s_net_errno = NET_ERR;
return -1;
}
/* 复位芯片 */
ch395f_reset();
/* 多 Socket 模式 + TCP Server 单连接 + 手动关闭 Socket(bit0=1, bit1=0, bit3=1)
* bit0=1: 多 Socket 模式,启用 Socket 0~7(单 Socket 模式下 1~7 被禁用)
* bit1=0: TCP Server 单连接,监听 Socket 0 仅接受 1 个客户端(连接停留在 Socket 0 本身)
* bit3=1: DISCONNECT/TIMEOUT 时不自动清空缓冲区,由软件排空数据后手动关闭 */
ch395f_set_fun_para(0x09);
/* 设置 TCP MSS(2026-09-22 TC-NET-604:试 512 裕量方案被台架证伪,恢复 1024,见 Trap 21)
* 手册(§5.45):CH395F 内部缓冲共 48 块 × 512B = 24KB。下方为 8 个 Socket 各分配 6 块
* (接收 4 块=2KB + 发送 2 块=1KB),块布局固定(与 MSS 解耦,ch395f.h)。
* MSS=1024 为满足布局的最大值(接收=2×MSS);MSS 裕量不能消除芯片窗口冻结角
* (512 方案反使新连接突发也停摆),真值源为 ch395f.h 的 CH395F_TCP_MSS。 */
ch395f_set_tcp_mss(CH395F_TCP_MSS);
/* 设置 TCP 重传参数(必须在 INIT_CH395 之前调用;P0-a:周期 500→200ms。
* 台架验收 2026-09-22 完成:GET_CMD_STATUS 对 SET 类命令无判别力(500/2000ms
* 行为一致,均为 RESET_ALL 的陈旧 BUSY 残留),值的接受性以手册范围(≤1000ms)
* + 全套件无回归为准——验收探针已移除,结论见《链路延迟测试说明》§10.1 D1 */
ch395f_set_retrans_count(NET_RETRANS_COUNT);
ch395f_set_retrans_period(NET_RETRANS_PERIOD_MS);
/* 设置 TCP KeepAlive 参数(必须在 INIT_CH395 之前,单位 ms,必须为 500 的倍数,且 IDLE > INTVL)
* 值来自 net_config.h 的 NET_KEEPALIVE_*(60s/5s/3)*/
ch395f_set_keepalive_idle(NET_KEEPALIVE_IDLE_MS); /* 空闲 60 秒后开始探测*/
ch395f_set_keepalive_intvl(NET_KEEPALIVE_INTVL_MS); /* 5 秒探测一次*/
ch395f_set_keepalive_cnt(NET_KEEPALIVE_CNT); /* 最多探 3 次*/
/* 设置网络参数(必须在 INIT_CH395 之前调用 */
if (ip != NULL) {
uint8_t ip_arr[4];
uint8_t mask_arr[4];
uint8_t gw_arr[4];
parse_ip_string(ip, ip_arr);
parse_ip_string(mask, mask_arr);
parse_ip_string(gateway, gw_arr);
memcpy(s_local_ip, ip_arr, 4);
ch395f_set_ip_addr(ip_arr);
ch395f_set_gwip_addr(gw_arr);
ch395f_set_mask_addr(mask_arr);
} else {
/* 启用 DHCP */
ch395f_set_dhcp(1);
}
/* 初始化 CH395F 协议栈 */
ret = ch395f_init();
if (ret != CH395F_STATUS_OK) {
s_net_errno = NET_ERR;
return -1;
}
/* 设置 MAC(须在协议栈初始化之后,限定条件"芯片已初始化";写芯片 EEPROM,
* 每上电重放同值幂等);NULL 或全零 = 保持芯片内置 MAC */
if (mac != NULL && s_mac_is_zero(mac) == false) {
ch395f_set_mac_addr((uint8_t *)mac);
}
/* 多 Socket 模式:为 Socket 0~7 各分配独立收发缓冲(必须在 OPEN_SOCKET 之前)
* 手册(§5.45):CH395F 内部缓冲共 48 块 × 512B = 24KB;芯片上电默认仅给 Socket 0~3
* 分配(每 Socket 接收 8 块/发送 4 块),Socket 4~7 为 NULL,故用到 8 个 Socket 必须重分配。
* 单 Socket 块数为固定布局(ch395f.h,2026-09-22 起与 MSS 解耦):接收 CH395F_RECV_BLOCKS(=4 块=2KB)、
* 发送 CH395F_SEND_BLOCKS(=2 块=1KB),每 Socket 共 6 块,用满 48 块:
* blocks_per_socket = CH395F_RECV_BLOCKS + CH395F_SEND_BLOCKS
* recv_start = i * blocks_per_socket, send_start = recv_start + CH395F_RECV_BLOCKS
* Socket 7 占块 42~47,恰好用尽 0~47,互不重叠。
* 注:此布局以手册 §5.45(48×512B)为准;若实际芯片为其它缓冲规格需重新核算。
* Trap 01:先配数据 Socket 1~7,再配监听 Socket 0。 */
uint8_t blocks_per_socket = (uint8_t)(CH395F_RECV_BLOCKS + CH395F_SEND_BLOCKS);
for (int i = NET_MAX_SOCKETS - 1; i >= 0; i--) {
uint8_t recv_start = (uint8_t)(i * blocks_per_socket);
uint8_t send_start = (uint8_t)(recv_start + CH395F_RECV_BLOCKS);
ch395f_set_send_buf((uint8_t)i, send_start, (uint8_t)CH395F_SEND_BLOCKS);
ch395f_set_recv_buf((uint8_t)i, recv_start, (uint8_t)CH395F_RECV_BLOCKS);
}
/* 使能 PING 响应 */
ch395f_ping_enable(1);
s_net_initialized = 1;
return 0;
}
/*
* 函数功能:取网络代次号
* 入口参数:无
* 返回值:代次号(每次网络重建完成递增) uint32_t
* 限定条件:无
* 函数说明:见 net_socket.h 同名函数说明
*/
uint32_t net_get_generation(void) {
return s_net_generation;
}
/*
* 函数功能:递增网络代次号(网络重建完成、对外可用时调用)
* 入口参数:无
* 返回值:无
* 限定条件:net_init() 成功且 PHY/链路就绪后调用(当前仅 net_stack_bring_up)
* 函数说明:见 net_socket.h 同名函数说明
*/
void net_generation_inc(void) {
s_net_generation++;
}
/*
* 函数功能:轮询 CH395F 中断状态,更新 Socket 连接状态并处理事件
* 返回值:本次轮询中发生事件的 Socket 数量 int >= 0
* 限定条件:s_net_initialized == 1;必须在 netTask 上下文中调用
* 函数说明:1. GPIO 轮询 CH395F INT# 引脚电平(低电平有效),无需 EXTI 中断
* 2. 读取 GET_GLOB_INT_STATUS_ALL(2 字节版)获取所有 Socket 的中断状态
* 3. 处理连接/断开/数据接收/发送完成/超时等事件,更新 Socket 状态机
* 4. RECV 中断为电平触发,每次 net_poll() 只处理一批中断,避免主循环饿死
*/
#if defined(NET_BENCH_CMD_LOG)
/* Bench:芯片命令序列日志(2026-09-22 Trap 21 精确定位;netTask 上下文)。
* 每个真实 SPI 命令点一行:tag=命令类别(P=poll全局中断 R=recv S=send C=close
* A=accept L=listen N=connect U=relisten M=其他),fd=socket,t=FreeRTOS tick,
* d=距上一条命令的 tick 间隔,x=结果/中断状态。台架专用,实验后回退。 */
static uint32_t s_bench_prev_tick = 0U;
static uint8_t s_bench_prev_valid = 0U;
static void net_bench_cmd_log(char tag, int sock, int detail) {
uint32_t now = (uint32_t)xTaskGetTickCount();
uint32_t delta = (s_bench_prev_valid != 0U) ? (now - s_bench_prev_tick) : 0U;
s_bench_prev_tick = now;
s_bench_prev_valid = 1U;
DBG_INFO("netcmd: %c fd=%d t=%u d=%u x=%d", tag, sock, now, delta, detail);
}
#endif
int net_poll(void) {
/* 本次轮询累计发生的事件数(连接/断开/接收/发送/超时各计 1) */
int event_count = 0;
/* 全局中断状态寄存器值(CH395F GET_GLOB_INT_STATUS_ALL 为 16 位) */
/* 防御性检查:尚未初始化则不处理任何中断,直接返回 */
if (!s_net_initialized) {
return 0;
}
/*
* 采用 do-while(0) 实现“单次批处理”:
* 本函数每被调用一次只读取并处理一批中断,处理完立即返回,不循环等待,
* 避免 RECV 电平中断导致主循环(如 echo)饿死。
* 循环体内部用 break 提前退出(INT# 无效或无中断时)。
*/
do
{
uint16_t int_status;
/*
* GPIO 轮询 INT# 引脚:低电平有效(有中断待处理)。
* 采用 GPIO 轮询而非 EXTI 边沿中断,仅当 INT# 为低电平时才读取状态,减少不必要 SPI 事务。
* 若 INT# 为高(无效),直接退出本次轮询。
*/
if (HAL_GPIO_ReadPin(CH395_INT_GPIO_Port, CH395_INT_Pin) == GPIO_PIN_SET) {
break;
}
/* 读取全局中断状态(2 字节):读操作本身会清除 CH395F 中断并使其拉高 INT#,
* 若此后仍有新中断,CH395F 会再次拉低 INT#,由下一次 net_poll() 处理 */
int_status = ch395f_get_glob_int_status_all();
#if defined(NET_BENCH_CMD_LOG)
/* 仅状态跳变时记(电平型 RECV_OK 会每 poll 重复,全记会冲爆日志) */
static uint16_t s_bench_last_glob = 0xFFFF;
if (int_status != s_bench_last_glob) {
s_bench_last_glob = (uint16_t)int_status;
net_bench_cmd_log('P', -1, (int)int_status);
}
#endif
/* 无有效中断(读到的状态为 0),提前退出 */
if (int_status == 0) {
break;
}
/* 遍历所有 Socket,处理各自的中断 */
for (int i = 0; i < NET_MAX_SOCKETS; i++) {
/* 取本 Socket 控制块(直接用数组下标,无需 net_get_sock 的越界判断) */
net_sock_t *p_sock = &s_net_socks[i];
/* 全局中断状态(GET_GLOB_INT_STATUS_ALL 16 位):Socket n 的中断位即 bit(n+4)。
* Socket 0~3 在低位字节 bit4~bit7;Socket 4~7 在高位字节的 bit0~3
* (即 16 位值的 bit8~bit11)。对应位为 0 说明该 Socket 无中断,跳过以节省时间。 */
if (!(int_status & (1U << (i + 4U)))) {
continue;
}
/* 读取该 Socket 的中断状态寄存器(SINT) */
uint8_t sock_int = ch395f_get_sock_int_status(i);
#if defined(NET_BENCH_CMD_LOG)
static uint8_t s_bench_last_sint[NET_MAX_SOCKETS] = { 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF };
if (sock_int != s_bench_last_sint[i]) {
s_bench_last_sint[i] = sock_int;
net_bench_cmd_log('p', i, (int)sock_int);
}
#endif
/* 连接建立事件:交给状态机处理(监听态/客户端/数据 Socket 分别处理) */
if (sock_int & CH395F_SINT_STAT_CONNECT) {
handle_connect_event(p_sock);
event_count++;
}
/* 连接断开事件:更新状态机并触发断开回调 */
if (sock_int & CH395F_SINT_STAT_DISCONNECT) {
handle_disconnect_event(p_sock);
event_count++;
}
/* 接收数据就绪事件:仅通知上层,实际数据由 net_recv_locked 读出(避免重复读 recv_len) */
if (sock_int & CH395F_SINT_STAT_RECV_OK) {
handle_recv_event(p_sock);
event_count++;
}
/* 发送完成事件:通知上层发送缓冲已空,可继续发送 */
if (sock_int & CH395F_SINT_STAT_SEND_OK) {
handle_send_event(p_sock);
event_count++;
}
/* 发送缓冲区空闲:置 send_ready 标志,唤醒等待发送的逻辑 */
if (sock_int & CH395F_SINT_STAT_SENBUF_FREE) {
p_sock->send_ready = 1;
}
/* 超时事件:连接超时或收发超时,更新状态机 */
if (sock_int & CH395F_SINT_STAT_SOCK_TIMEOUT) {
handle_timeout_event(p_sock);
event_count++;
}
}
/*
* 处理完本批中断后退出 do-while(0):
* CH395F 的 RECV 中断为电平触发,只要接收缓冲区仍有数据就保持 INT# 低电平,
* 若此处继续循环会反复处理 RECV 中断,导致主循环中的应用代码(如 echo 回显)得不到执行。
* 因此每次 net_poll() 只处理“一批”,剩余数据由主循环/recv 逻辑在后续轮询中读取。
*/
} while (0);
/* 返回本次累计发生的事件数量(0 表示无事件) */
return event_count;
}
/*
* 函数功能:处理消息队列中的请求
* 限定条件:必须在 netTask 上下文中调用(独占 CH395F 和 s_net_socks 访问权)
* 函数说明:每次调用处理一条消息。处理 send/recv 时内部会调用 net_poll()
* 等待操作完成。处理完毕后通知调用方任务。
*/
void net_process_messages(void) {
net_msg_t msg;
if (netMsgQueueHandle == NULL) {
return;
}
/* D10:待重试的自动重监听(Trap 09 沉降后重开),非阻塞、每轮最多一次尝试。
* 放在消息处理之前:断连后即使没有任何应用消息,监听器也能自主恢复。
* L3:挂起 recv 的完成检查同位(两 tick 共享主循环节拍,均为 O(每 fd) 非阻塞)。 */
net_relisten_retry_tick();
net_pending_tick();
while (osMessageQueueGet(netMsgQueueHandle, &msg, NULL, 0) == osOK) {
int do_notify = 1;
switch (msg.type) {
case NET_MSG_SEND: {
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) {
s_net_errno = NET_ERR_NOTCONN;
msg.result = -1;
} else {
msg.result = net_send_locked(p_sock, msg.buf, msg.len);
}
break;
}
case NET_MSG_RECV: {
net_sock_t *p_sock = net_get_sock(msg.sockfd);
if (p_sock == NULL) {
s_net_errno = NET_ERR_BADF;
msg.result = -1;
} else {
/* L3:无数据 + 阻塞等待 = 登记挂起请求(netTask 零占用),
* 完成由 net_pending_tick 按主循环节拍通知调用方 */
msg.result = net_recv_locked(p_sock, msg.buf, msg.len, msg.flags,
msg.timeout_ms, msg.caller);
if (msg.result == NET_PENDING_REGISTERED) {
do_notify = 0; /* 挂起已登记:不发即时回执,完成时才通知 */
}
}
break;
}
case NET_MSG_CLOSE: {
net_sock_t *p_sock = net_get_sock(msg.sockfd);
if (p_sock == NULL) {
s_net_errno = NET_ERR_BADF;
msg.result = -1;
} else {
msg.result = net_close_locked(p_sock);
}
break;
}
case NET_MSG_CONNECT: {
msg.result = net_connect_locked(msg.sockfd,
(const struct net_sockaddr *)&msg.addr,
msg.addrlen);
break;
}
case NET_MSG_LISTEN: {
msg.result = net_listen_locked(msg.sockfd);
break;
}
case NET_MSG_ACCEPT: {
struct net_sockaddr *p_addr = (struct net_sockaddr *)msg.buf;
int addrlen = msg.len;
int *p_addrlen = (p_addr != NULL && addrlen > 0) ? &addrlen : NULL;
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;
net_pending_cancel(msg.sockfd, NET_ERR_CONNRESET); /* L3:死连接上的挂起 recv 立即取消(必通知) */
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;
}
#if defined(NET_BENCH_CMD_LOG)
{
char bench_tag = (msg.type == NET_MSG_RECV) ? 'R' :
(msg.type == NET_MSG_SEND) ? 'S' :
(msg.type == NET_MSG_CLOSE) ? 'C' :
(msg.type == NET_MSG_ACCEPT) ? 'A' :
(msg.type == NET_MSG_LISTEN) ? 'L' :
(msg.type == NET_MSG_CONNECT) ? 'N' : 'M';
net_bench_cmd_log(bench_tag, msg.sockfd, (int)msg.result);
}
#endif
/* 结果回传:成功=字节数/描述符;失败=具体错误码(如 NET_ERR_WOULDBLOCK)。
* 注意消息体按值拷入队列——调用方看不到 netTask 对 msg 的修改,结果只能搭
* 通知值这趟车带回;**整个 32 位都是结果**(若压入序列号等高 16 位标签,
* 单次结果上限会被压到 32767,而 TEST_BUF_SIZE=32768 等调用点会溢出为负)。
* L3:挂起 recv(do_notify=0)不发即时回执,由 net_pending_tick 完成时通知。 */
if (do_notify != 0) {
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);
}
xTaskNotify(msg.caller, notify_val, eSetValueWithOverwrite);
}
}
}
/*
* 函数功能:结果等待(两段式,防丢失通知;结果按 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
* type - SOCK_STREAM(TCP) 或 SOCK_DGRAM(UDP) int
* 返回值:Socket 描述符 (0~7),失败返回 -1 int
* 限定条件:net_init() 已调用成功;s_net_initialized == 1
* 函数说明:自动分配一个空闲的 Socket 控制块(in_use=0),初始化为 CREATED 状态
*/
int net_socket(int domain, int type) {
int sockfd;
net_sock_t *p_sock;
/* 参数检查*/
if (domain != NET_AF_INET) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
/* 分配空闲 Socket */
sockfd = alloc_socket();
if (sockfd < 0) {
s_net_errno = NET_ERR_NOMEM;
return -1;
}
p_sock = &s_net_socks[sockfd];
p_sock->ch395_sock = (uint8_t)sockfd;
p_sock->type = (uint8_t)type;
p_sock->state = NET_SOCK_STATE_CREATED;
p_sock->in_use = 1;
p_sock->send_ready = 1;
p_sock->readable = 0;
net_relisten_clear(sockfd); /* 新 fd 不得继承上一次的待重试状态 */
net_pending_clear(sockfd); /* 新 fd 不得继承上一次挂起的 recv 请求(L3) */
return sockfd;
}
/*
* 函数功能:绑定 Socket 到本地 IP 地址和端口号
* 入口参数:sockfd - Socket 描述符 (0~7) int
* addr - 本地地址结构体指针 const struct net_sockaddr *
* addrlen - 地址结构体长度 int >= sizeof(struct net_sockaddr_in)
* 返回值:0 - 绑定成功 int
* -1 - 绑定失败(参数无效、Socket 未使用等) int
* 限定条件:Socket 已通过 net_socket() 创建且未被其他操作占用;net_init() 已调用
* 函数说明:1. TCP Socket 仅记录本地端口,进入 BOUND 状态等待 listen
* 2. UDP Socket(SOCK_DGRAM)立即打开 CH395F Socket 并配置为 Server 模式
* - Socket 4~7 需显式分配收发缓冲区
*/
int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) {
net_sock_t *p_sock;
const struct net_sockaddr_in *p_addr_in;
/* 参数检查 */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
}
p_sock = &s_net_socks[sockfd];
if (!p_sock->in_use) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (addr == NULL || addrlen < (int)sizeof(struct net_sockaddr_in)) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
p_addr_in = (const struct net_sockaddr_in *)addr;
/* 设置本地端口(转换为主机序) */
p_sock->local_port = net_ntohs(p_addr_in->sin_port);
/* port=0 时自动分配动态端口(49152~65535) */
if (p_sock->local_port == 0) {
p_sock->local_port = s_dynamic_port;
s_dynamic_port++;
if (s_dynamic_port == 0) {
s_dynamic_port = 49152;
}
}
/* UDP (SOCK_DGRAM): 直接在此打开 CH395F Socket */
if (p_sock->type == NET_SOCK_DGRAM) {
uint8_t status;
/* net_init 已为全部 8 个 Socket 分配缓冲;此处仅对 Socket 4~7 兜底重设,
* 使用与 net_init 相同的布局(由 MSS 推导,见 net_init) */
if (sockfd >= 4) {
uint8_t blocks_per_socket = (uint8_t)(CH395F_RECV_BLOCKS + CH395F_SEND_BLOCKS);
uint8_t recv_start = (uint8_t)(sockfd * blocks_per_socket);
uint8_t send_start = (uint8_t)(recv_start + CH395F_RECV_BLOCKS);
ch395f_set_send_buf((uint8_t)sockfd, send_start, (uint8_t)CH395F_SEND_BLOCKS);
ch395f_set_recv_buf((uint8_t)sockfd, recv_start, (uint8_t)CH395F_RECV_BLOCKS);
}
ch395f_set_proto_type((uint8_t)sockfd, CH395F_PROTO_TYPE_UDP);
ch395f_set_sour_port((uint8_t)sockfd, p_sock->local_port);
/* DesIP=0xFFFFFFFF UDP Server 模式(接受任意来源) */
{
uint8_t broadcast[4] = {0xFF, 0xFF, 0xFF, 0xFF};
ch395f_set_des_ip((uint8_t)sockfd, broadcast);
}
ch395f_set_des_port((uint8_t)sockfd, 0);
status = ch395f_open_socket((uint8_t)sockfd);
if (status != CH395F_ERR_SUCCESS) {
DBG_ERROR("sock%d open_socket FAIL: status=0x%02X (expected 0x00)", sockfd, status);
s_net_errno = NET_ERR_BUSY;
return -1;
}
p_sock->state = NET_SOCK_STATE_UDP_OPEN;
} else {
p_sock->state = NET_SOCK_STATE_BOUND;
}
return 0;
}
/*
* 函数功能:启动 TCP Server 监听(内部版本,必须在 netTask 上下文调用)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 返回值:0 - 成功,-1 - 失败 int
* 限定条件:sockfd 已绑定且为 TCP;netTask 上下文
*/
int net_listen_locked(int sockfd) {
/* 取监听 Socket 控制块(直接下标访问,sockfd 已在上层 net_listen 校验过范围) */
net_sock_t *p_sock = &s_net_socks[sockfd];
/* CH395F 打开 Socket 的返回状态 */
uint8_t status;
/* 合法性检查:Socket 必须已创建且为 TCP 流类型 */
if (!p_sock->in_use || p_sock->type != NET_SOCK_STREAM) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
/* 状态检查:仅允许在 BOUND(已 bind)或 CREATED(未 bind 直接用随机端口)状态下启动监听 */
if (p_sock->state != NET_SOCK_STATE_BOUND &&
p_sock->state != NET_SOCK_STATE_CREATED) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
/*
* 本实现仅支持单连接:监听 Socket 自身即为数据通道,复用 CH395F 默认收发缓冲
* (Socket 0~3 默认缓冲互不重叠),不再为数据 Socket 额外分配。
*/
ch395f_set_proto_type(sockfd, CH395F_PROTO_TYPE_TCP);
ch395f_set_sour_port(sockfd, p_sock->local_port);
/* CH395F 关闭 Socket 后需延时才能重新打开(Trap 09),故失败重试最多 3 次 */
for (int open_try = 0; open_try < 3; open_try++) {
status = ch395f_open_socket(sockfd);
if (status == CH395F_ERR_SUCCESS) {
break; /* 打开成功,退出重试 */
}
ch395f_close_socket(sockfd); /* 打开失败先关闭,再延时后重试 */
osDelay(500);
}
/* 3 次仍失败,返回错误 */
if (status != CH395F_ERR_SUCCESS) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
/* 下发 TCP LISTEN 命令,进入监听状态 */
status = ch395f_tcp_listen(sockfd);
if (status != CH395F_ERR_SUCCESS) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
/* 监听 Socket 进入 LISTENING 态 */
p_sock->state = NET_SOCK_STATE_LISTENING;
/* 单连接模式:连接断开后由 net 层自动回到 LISTEN(模拟 BSD 监听常驻) */
p_sock->auto_relisten = 1;
/* 单连接:CONNECT 中断直接作用于本 Socket 自身 */
/*
* 启动监听前清空各 Socket 及全局中断状态寄存器(读即清除),
* 避免历史残留/伪中断在监听刚开始时误触发连接或断开事件。
*/
for (int j = 0; j < NET_MAX_SOCKETS; j++) {
(void)ch395f_get_sock_int_status((uint8_t)j);
}
(void)ch395f_get_glob_int_status_all();
return 0;
}
/*
* 函数功能:启动 TCP Server 监听(线程安全版本,通过消息队列委托给 netTask)
* 入口参数:sockfd - Socket 描述符 int
* 返回值:0 - 成功,-1 - 失败 int
* 限定条件:net_init() + MX_FREERTOS_Init() 已执行;必须在任务上下文中调用
* 函数说明:通过消息队列委托给 netTask,确保 SPI 串行化
*/
int net_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_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("lis", sockfd);
if (msg.result < 0) {
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
* errno;对外仍保持 BSD 语义——失败一律返回 -1 */
s_net_errno = msg.result;
return -1;
}
return msg.result;
}
/*
* 函数功能:TCP Server 接受新连接(内部版本,必须在 netTask 上下文调用)
* 入口参数:sockfd - 监听 Socket 描述符 int
* addr - 输出:客户端地址结构体指针 struct net_sockaddr *(可为 NULL)
* addrlen - 输入输出:地址结构体长度 int *(可为 NULL)
* 返回值:新连接的 Socket 描述符,失败返回 -1 int
* 限定条件:Socket 处于 LISTENING 状态
* 函数说明:单连接:监听 Socket 自身在 CONNECT 后已转为 ESTABLISHED
*/
static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen) {
net_sock_t *p_listen_sock;
p_listen_sock = &s_net_socks[sockfd];
if (!p_listen_sock->in_use) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
net_poll();
/* 单连接:本 Socket 自身在 CONNECT 后已转为 ESTABLISHED,
* 只有处于 ESTABLISHED 才表示有可接受的连接;否则尚无连接 */
if (p_listen_sock->state != NET_SOCK_STATE_ESTABLISHED) {
/* 连接已断开(CLOSED)且启用自动重监听:先把 Socket 重新置回 LISTEN,
* 下一次 accept 即可等待新连接(BSD 风格监听常驻) */
if (p_listen_sock->state == NET_SOCK_STATE_CLOSED && p_listen_sock->auto_relisten) {
(void)net_relisten_try(p_listen_sock); /* 非阻塞:失败由重试轮询接管 */
}
s_net_errno = NET_ERR_WOULDBLOCK;
return -1;
}
/* 填充对端地址(若调用方需要) */
if (addr != NULL && addrlen != NULL) {
struct net_sockaddr_in *p_addr_in = (struct net_sockaddr_in *)addr;
p_addr_in->sin_family = NET_AF_INET;
p_addr_in->sin_port = net_htons(p_listen_sock->remote_port);
p_addr_in->sin_addr.s_addr = p_listen_sock->remote_ip;
*addrlen = sizeof(struct net_sockaddr_in);
}
/* 单连接:返回的就是监听 Socket 自身(已转为数据通道) */
return sockfd;
}
/*
* 函数功能:TCP Server 接受新连接(线程安全版本,通过消息队列委托给 netTask)
* 入口参数:sockfd - 监听 Socket 描述符 int
* addr - 输出:客户端地址结构体指针 struct net_sockaddr *(可为 NULL)
* addrlen - 输入输出:地址结构体长度 int *(可为 NULL)
* 返回值:新连接的 Socket 描述符,失败返回 -1 int
* 限定条件:Socket 处于 LISTENING 状态;net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文
* 函数说明:将接受请求放入消息队列,阻塞等待 netTask 处理完毕
*/
int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
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_ACCEPT;
msg.sockfd = sockfd;
msg.buf = (void *)addr;
msg.len = (addrlen != NULL) ? *addrlen : 0;
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("acc", sockfd);
if (addrlen != NULL &&addr != NULL) {
*addrlen = sizeof(struct net_sockaddr_in);
}
if (msg.result < 0) {
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
* errno;对外仍保持 BSD 语义——失败一律返回 -1 */
s_net_errno = msg.result;
return -1;
}
return msg.result;
}
/*
* 函数功能:TCP Client 发起与服务器的连接(线程安全版本,通过消息队列委托给 netTask)
* 入口参数:sockfd - Socket 描述符 (0~7),必须为已创建的 TCP Socket int
* addr - 服务器地址结构体指针 const struct net_sockaddr *
* addrlen - 地址结构体长度 int >= sizeof(struct net_sockaddr_in)
* 返回值:0 - 连接请求发送成功(异步,需等待 ESTABLISHED) int
* -1 - 连接失败(参数无效、状态错误等) int
* 限定条件:Socket 已通过 net_socket() 创建且 type == NET_SOCK_STREAM;尚未连接
* 函数说明:将连接请求放入消息队列,阻塞等待 netTask 处理完毕。实际的 SPI 操作在 netTask
* 上下文中执行,避免与 net_poll() 的 SPI 访问产生竞争(否则会破坏 CH395F 寄存器)。
* 连接建立需等待 net_poll() 检测到 CONNECT 事件并将状态更新为 ESTABLISHED
*/
int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) {
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 (addr == NULL || addrlen < (int)sizeof(struct net_sockaddr_in)) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
msg.type = NET_MSG_CONNECT;
msg.sockfd = sockfd;
msg.buf = NULL;
msg.len = 0;
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;
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
msg.result = net_wait_result("con", sockfd);
if (msg.result < 0) {
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
* errno;对外仍保持 BSD 语义——失败一律返回 -1 */
s_net_errno = msg.result;
return -1;
}
return msg.result;
}
/*
* 函数功能:TCP Client 发起连接(netTask 内部锁定版本,由消息队列委托执行)
* 限定条件:net_init() 已调用;必须在 netTask 上下文中调用
*/
static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int addrlen) {
net_sock_t *p_sock;
const struct net_sockaddr_in *p_addr_in;
uint8_t status;
/* 参数检查 */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
}
p_sock = &s_net_socks[sockfd];
if (!p_sock->in_use || p_sock->type != NET_SOCK_STREAM) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
if (addr == NULL || addrlen < (int)sizeof(struct net_sockaddr_in)) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
p_addr_in = (const struct net_sockaddr_in *)addr;
/* 保存远端地址 */
p_sock->remote_ip = p_addr_in->sin_addr.s_addr;
p_sock->remote_port = net_ntohs(p_addr_in->sin_port);
/* 转换 IP 为数组格式(大端/网络字节序,与 ch395f set_des_ip 期望一致,
* 同 Phase2 phase2_tc201 的 test_parse_ip 结果 [192,168,1,2]) */
p_sock->remote_ip_arr[0] = (uint8_t)(p_sock->remote_ip >> 24);
p_sock->remote_ip_arr[1] = (uint8_t)(p_sock->remote_ip >> 16);
p_sock->remote_ip_arr[2] = (uint8_t)(p_sock->remote_ip >> 8);
p_sock->remote_ip_arr[3] = (uint8_t)(p_sock->remote_ip);
/* 配置 CH395F Socket:先 OPEN 再写源/目的端口(避免 open 复位寄存器) */
ch395f_set_proto_type(sockfd, CH395F_PROTO_TYPE_TCP);
/* CH395F 关闭 Socket 后需延时才能重新打开(Trap 09),故失败重试最多 3 次 */
for (int open_try = 0; open_try < 3; open_try++) {
status = ch395f_open_socket(sockfd);
if (status == CH395F_ERR_SUCCESS) {
break; /* 打开成功,退出重试 */
}
ch395f_close_socket(sockfd); /* 打开失败先关闭,再延时后重试 */
osDelay(500);
}
if (status != CH395F_ERR_SUCCESS) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
/* 清掉本 Socket 可能残留的中断状态,丢弃上一条连接遗留的 DISCONNECT */
(void)ch395f_get_sock_int_status((uint8_t)sockfd);
(void)ch395f_get_glob_int_status_all();
ch395f_set_sour_port(sockfd, p_sock->local_port);
ch395f_set_des_ip(sockfd, p_sock->remote_ip_arr);
ch395f_set_des_port(sockfd, p_sock->remote_port);
/* 诊断:回读目的 IP/端口与 Socket 状态(GET_REMOT_IPP 为小端,故需按小端解析)
* 注意 ch395f_get_socket_status 输出**2 字节**:[0]=Socket 开关状态、[1]=TCP 状态,
* 必须给 2 字节缓冲(曾只给 1 字节 → 栈越界 1 字节) */
{
uint8_t rip[6];
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);
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 tcp=0x%02X",
sockfd,
(dip >> 24) & 0xFF, (dip >> 16) & 0xFF,
(dip >> 8) & 0xFF, dip & 0xFF, dp, st[0], st[1]);
}
/* 发起 TCP 连接 */
ch395f_tcp_connect(sockfd);
p_sock->state = NET_SOCK_STATE_CONNECTING;
return 0;
}
/*
* SPI DMA 单次事务最大有效载荷(取 CH395F 驱动公开常量)
*/
#define NET_DMA_MAX_PAYLOAD ((int)CH395F_SPI_DMA_MAX_PAYLOAD)
/* 单次写入 CH395F 发送 FIFO 的最大字节数:受硬件发送缓冲(CH395F_SEND_BUF_SIZE_BYTES=1KB)
* 与 SPI DMA 载荷(NET_DMA_MAX_PAYLOAD=2KB) 双重限制,以硬件发送缓冲为硬上限(见 Trap 20),
* 否则单次写 2KB/4KB 会写穿 1KB 发送 FIFO。 */
#define NET_SEND_CHUNK_MAX ((int)CH395F_SEND_BUF_SIZE_BYTES < NET_DMA_MAX_PAYLOAD ? \
(int)CH395F_SEND_BUF_SIZE_BYTES : NET_DMA_MAX_PAYLOAD)
/*
* 函数功能:发送数据(内部锁定版本,必须在 netTask 上下文调用)
*/
/*
* 函数功能:发送数据(内部锁定版本,必须在 netTask 上下文调用)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* buf - 待发送数据缓冲区指针 const void *
* len - 数据长度 int > 0
* 出口参数:无
* 返回值:实际发送字节数(>= 0);-1 表示参数无效或 Socket 未连接 int
* 限定条件:p_sock 非空;Socket 处于 ESTABLISHED 状态;netTask 上下文
* 函数说明:1. 按 NET_SEND_CHUNK_MAX(硬件发送缓冲上限,见 Trap 20)分块写入 CH395F 发送 FIFO
* 2. 发送前等待 send_ready 置位,超时返回已发送字节数
* 3. 每写完一块等待 SEND_OK 中断(send_ready 清零后重新置位)
*/
static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
int sent = 0;
uint32_t tick_start;
int sockfd = (int)(p_sock - s_net_socks);
if (buf == NULL || len <= 0) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
if (p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
s_net_errno = NET_ERR_NOTCONN;
return -1;
}
tick_start = HAL_GetTick();
while (sent < len) {
int chunk = len - sent;
if (chunk > NET_SEND_CHUNK_MAX) {
chunk = NET_SEND_CHUNK_MAX;
}
if (!p_sock->send_ready) {
if (NET_SEND_TIMEOUT_MS > 0 &&
(HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) {
DBG_ERROR("sock%d send stall: send_ready not set (%d/%d)", sockfd, sent, len);
s_net_errno = NET_ERR_TIMEDOUT;
break;
}
net_poll();
continue;
}
ch395f_write_send_buf(sockfd, (uint8_t *)buf + sent, (uint16_t)chunk);
p_sock->send_ready = 0;
sent += chunk;
p_sock->send_bytes += (uint32_t)chunk;
#if defined(TEST_LATENCY_PROBE)
/* L3 探针起点:发送缓冲写入刚完成
* (样本 = 写缓冲 → SENBUF_FREE 等待,见 链路延迟测试说明 §3 L3) */
uint32_t c_l3 = lat_cycles_now();
#endif
tick_start = HAL_GetTick();
while (!p_sock->send_ready) {
net_poll();
if (NET_SEND_TIMEOUT_MS > 0 &&
(HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) {
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) {
/* 短写告警:调用方必须检查返回值。静默截断会让对端永久等待剩余字节
* (2026-09-21 实证:UI 推流应答被截断 → 客户端反复判定"全程无字节"失联)。 */
DBG_ERROR("sock%d short send %d/%d (errno=%d)", sockfd, sent, len, s_net_errno);
}
return sent;
}
/*
* 函数功能:接收数据(内部锁定版本,必须在 netTask 上下文调用)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位,NET_MSG_DONTWAIT 表示非阻塞 int
* timeout_ms - 本次等待预算(0 = 默认 NET_RECV_TIMEOUT_MS) uint32_t
* caller - 调用方任务句柄(挂起请求的完成通知目标) TaskHandle_t
* 出口参数:buf - 填充接收到的数据
* 返回值:实际接收字节数(> 0);0 表示对端关闭;-1 表示出错/非阻塞无数据 int
* NET_PENDING_REGISTERED 表示已登记挂起请求(分派据此跳过即时通知)
* 限定条件:p_sock 非空;netTask 上下文
* 函数说明:1. 数据已在(快速路径)→ 立即读出返回,行为与旧实现一致
* 2. 非阻塞模式无数据立即返回 -1(NET_ERR_WOULDBLOCK)
* 3. 无数据 + 阻塞等待 → 登记挂起请求(L3 pending 化,E-02 结构演进):
* netTask 零占用立即返回;完成条件(数据/到点/断连)由
* net_pending_tick 按主循环节拍检查并通知 caller;
* 关闭/网络重建/陈旧重同步由对应路径显式了结(取消必通知)。
* CLOSED 状态语义(排空 + EOF + 自动重监听)逐项保留在 tick 内
*/
static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
uint32_t timeout_ms, TaskHandle_t caller) {
int sockfd = (int)(p_sock - s_net_socks);
if (buf == NULL || len <= 0) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
/* 超时来源:调用方指定(单次预算)优先,否则用默认值(均作为挂起截止) */
uint32_t to_ms = (timeout_ms != 0U) ? timeout_ms : (uint32_t)NET_RECV_TIMEOUT_MS;
net_poll();
int recv_len = ch395f_get_recv_len(sockfd);
if (recv_len > 0) {
int read_len = (recv_len > len) ? len : recv_len;
ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len);
p_sock->recv_bytes += (uint32_t)read_len;
p_sock->readable = 0; /* 已消费数据(注:部分读受 Trap 13 限制,下一次 RECV_OK 会重新置位) */
return read_len;
}
if (flags & NET_MSG_DONTWAIT) {
/* 非阻塞且当前无数据:若连接已断开则视为 EOF(0) 并自动重监听,
* 否则返回 WOULDBLOCK(-1) 表示暂时无数据 */
if (p_sock->state == NET_SOCK_STATE_CLOSED) {
if (p_sock->auto_relisten) {
(void)net_relisten_try(p_sock); /* 非阻塞:失败由重试轮询接管 */
}
return 0;
}
s_net_errno = NET_ERR_WOULDBLOCK;
return -1;
}
/* 无数据 + 阻塞等待:登记挂起请求,立即返回(netTask 任何时刻都不被占用)。
* 调用方在自己上下文睡任务通知(LwIP netconn 模型);等待期间 netTask
* 继续主循环(事件泵 + 队列),其他消费者零头阻塞。 */
return net_pending_register(sockfd, buf, len, to_ms, caller);
}
/*
* 函数功能:关闭 socket(内部锁定版本,必须在 netTask 上下文调用)
*/
/*
* 函数功能:关闭 Socket(内部锁定版本,必须在 netTask 上下文调用)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:0 - 关闭成功 int
* 限定条件:p_sock 非空;netTask 上下文
* 函数说明:1. 调用 ch395f_close_socket 关闭 CH395F 硬件 Socket
* 2. 清空整个控制块(memset 0),释放该 Socket 资源
*/
/* Trap 23(2026-09-23 生产路径实测):异常释放(芯片 TIMEOUT,例如客户端中止上传)
* 后的**隔离期**。实测时序:中止时对端仍有在途数据 → 芯片 TIMEOUT(此时 MCU 侧 recv=0)
* → 在途段随后落进芯片 RX 缓冲(约 2KB 残渣)→ 若立即 open+listen,残渣被下一条连接
* 当作自己的数据读走:新上传只收 2KB 就被判定结束、回报成功(静默截断)。
* 处置:异常释放的 socket 先隔离 NET_RELISTEN_QUARANTINE_MS:期间持续排空 RX、主动
* close 并排空中断、等芯片回到 CLOSED/TCP_CLOSED;隔离期满才 open+listen。上限
* NET_RELISTEN_SETTLE_MS 后强制放行,保证监听器不会永久失效。
* 正常 FIN 释放(handle_disconnect_event)不走此路径——既有行为零改动。
* 声明须早于 net_relisten_locked(其余重试状态见其后区块)。 */
#define NET_RELISTEN_QUARANTINE_MS 400U /* 隔离时长(等在途数据落尽 + 排空残渣) */
#define NET_RELISTEN_SETTLE_MS 800U /* 隔离上限(须 < NET_RELISTEN_DEADLINE_MS,
* 否则隔离耗尽重试预算 → 监听被放弃,2026-09-23 实测) */
static uint8_t s_relisten_quarantine[NET_MAX_SOCKETS]; /* 1 = 本次释放为异常,需隔离 */
static uint32_t s_relisten_settle_start[NET_MAX_SOCKETS]; /* 隔离起点(0 = 未开始) */
static uint32_t s_relisten_settle_dead[NET_MAX_SOCKETS]; /* 隔离上限到期时刻 */
/*
* 函数功能:清空芯片该 socket 的 RX 残留(Trap 23)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 返回值:无
* 限定条件:netTask 上下文;socket 即将 close/open(数据已无意义,丢弃)
* 函数说明:中止/异常断开时芯片 RX 缓冲可能残留上一条连接的数据(实测残留约一个
* 缓冲 = 2048B);残渣带入下一条连接会使其在约 2KB 处被芯片拆链(上传被
* 静默截断)。关闭前主动读空即清除。分块读避免新增静态缓冲。
*/
static void net_drain_chip_rx(int sockfd) {
uint8_t scratch[256];
for (int guard = 0; guard < 16; guard++) { /* 上限 16×256 = 4KB(缓冲仅 2KB),防死循环 */
int n = (int)ch395f_get_recv_len((uint8_t)sockfd);
if (n <= 0) {
break;
}
if (n > (int)sizeof(scratch)) {
n = (int)sizeof(scratch);
}
ch395f_read_recv_buf((uint8_t)sockfd, scratch, (uint16_t)n);
}
}
/*
* 函数功能:单连接模式断连后自动重新进入 LISTEN(不清空控制块,保留 fd)
* 入口参数:p_sock - 已断开(CLOSED)的 TCP 监听型 Socket 控制块 net_sock_t *
* 返回值:0 - 成功,-1 - 失败
* 限定条件:netTask 上下文;p_sock->in_use == 1;保留 local_port/auto_relisten
* 函数说明: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;
int sockfd = (int)(p_sock - s_net_socks);
if (s_relisten_quarantine[sockfd] != 0U) {
uint8_t st[2] = {0, 0};
uint32_t now = HAL_GetTick();
if (s_relisten_settle_start[sockfd] == 0U) {
s_relisten_settle_start[sockfd] = now;
s_relisten_settle_dead[sockfd] = now + NET_RELISTEN_SETTLE_MS;
}
net_drain_chip_rx(sockfd); /* 隔离期内持续排空 RX 残渣 */
ch395f_close_socket((uint8_t)sockfd); /* 主动关:清旧连接收尾状态 */
(void)ch395f_get_sock_int_status((uint8_t)sockfd);
(void)ch395f_get_glob_int_status_all();
ch395f_get_socket_status((uint8_t)sockfd, st);
if (((st[0] != CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_CLOSED) ||
((uint32_t)(now - s_relisten_settle_start[sockfd]) < NET_RELISTEN_QUARANTINE_MS))) {
if ((int32_t)(now - s_relisten_settle_dead[sockfd]) < 0) {
DBG_INFO("sock%d relisten quarantine (sock=0x%02X tcp=0x%02X)",
sockfd, st[0], st[1]);
s_net_errno = NET_ERR_BUSY;
return -1; /* 交重试轮询(30ms 后复检) */
}
DBG_INFO("sock%d relisten quarantine timeout, force open (sock=0x%02X tcp=0x%02X)",
sockfd, st[0], st[1]);
}
s_relisten_quarantine[sockfd] = 0U; /* 隔离完成(或强制放行) */
s_relisten_settle_start[sockfd] = 0U;
s_relisten_settle_dead[sockfd] = 0U;
}
ch395f_set_proto_type((uint8_t)sockfd, CH395F_PROTO_TYPE_TCP);
ch395f_set_sour_port((uint8_t)sockfd, p_sock->local_port);
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;
}
status = ch395f_tcp_listen((uint8_t)sockfd);
if (status != CH395F_ERR_SUCCESS) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
p_sock->state = NET_SOCK_STATE_LISTENING;
p_sock->remote_port = 0;
p_sock->remote_ip = 0;
memset(p_sock->remote_ip_arr, 0, sizeof(p_sock->remote_ip_arr));
p_sock->send_ready = 0;
p_sock->recv_bytes = 0;
/* auto_relisten 保持为 1,下次断连仍自动重监听 */
for (int j = 0; j < NET_MAX_SOCKETS; j++) {
(void)ch395f_get_sock_int_status((uint8_t)j);
}
(void)ch395f_get_glob_int_status_all();
DBG_INFO("sock%d auto-relisten ok", sockfd);
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;
s_relisten_settle_dead[sockfd] = 0U; /* Trap 23:一并清沉降/隔离状态 */
s_relisten_settle_start[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 (s_net_pending[i].active != 0U) {
continue; /* 本 fd 有在途挂起 recv:重开 Socket 会摧毁在途连接(L3 保险) */
}
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;
}
}
}
/* ===== L3 挂起 recv 请求的实现(一期,2026-09-22) =====
* 完成条件按 ①数据 ②到点 ③断连 顺序检查(逐项对齐旧自旋循环语义):
* ① 数据通道(ESTABLISHED/UDP_OPEN)内 readable 门控 SPI 读——常见无数据路径
* 零额外 SPI 事务(INT# 电平触发链保证缓冲有数据时每轮重读 SINT 重位置位)
* ② 到点(与状态无关,旧自旋亦在任何状态下检查超时)→ NET_ERR_TIMEDOUT
* ③ CLOSED:先排空(Trap 13:FIN 可能先于数据)→ 有数据按数据完成,
* 否则 EOF(0) + 自动重监听
* 其他状态(CONNECTING/LISTENING/…)保持等待状态迁移(CONNECT→ESTABLISHED
* 是合法路径;陈旧 resync 由显式取消处理)——不做状态推断取消,避免误杀
* 正在建链的等待。看门狗兜底:到点后仍未了结 = 完成逻辑缺位 → 强制了结留痕。 */
/*
* 函数功能:通知调用方并清除本 fd 的挂起请求(所有完成/取消路径的公共出口)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* result - 32 位结果原值(>0 字节数 / 0 EOF / 负错误码) int
* 返回值:无
* 限定条件:netTask 上下文;sockfd 合法且挂起在位(否则静默返回,防重复通知)
* 函数说明:通知值 = 结果本身(与消息分派同一回传通道,调用方 net_recv/net_recv_to
* 的映射逻辑不变);失败结果同步 errno 保持 netTask 上下文一致性
*/
static void net_pending_complete(int sockfd, int result) {
net_pending_t *p;
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return;
}
p = &s_net_pending[sockfd];
if (p->active == 0U) {
return;
}
if (result < 0) {
s_net_errno = result;
}
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 - 本次等待预算(截止 = 登记时刻 + to_ms) uint32_t
* caller - 调用方任务句柄(完成通知目标) TaskHandle_t
* 返回值:NET_PENDING_REGISTERED - 登记成功(分派跳过即时通知)
* -1 - 拒绝(同 fd 已有挂起 / 执行任务自身发起)
* 限定条件:netTask 上下文
* 函数说明:每 fd 至多一个挂起(禁止覆盖——覆盖丢失前调用方通知 = 该任务永久挂死);
* 执行任务自身发起的阻塞等待无他人可完成(tick 跑在执行任务内)→ 自等自
* 通知 = 死锁,必须拒绝(netTask 上下文调用方须用 DONTWAIT)
*/
static int net_pending_register(int sockfd, void *buf, int len,
uint32_t to_ms, TaskHandle_t caller) {
net_pending_t *p;
uint32_t now;
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
s_net_errno = NET_ERR_BADF;
return -1;
}
p = &s_net_pending[sockfd];
if (p->active != 0U) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
if ((caller == NULL) || (caller == s_net_task_handle)) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
now = HAL_GetTick();
p->active = 1U;
p->buf = buf;
p->len = len;
p->deadline = now + to_ms;
p->registered = now;
p->caller = caller;
return NET_PENDING_REGISTERED;
}
/*
* 函数功能:挂起 recv 完成检查(netTask 每轮 net_process_messages 调用一次,非阻塞)
* 返回值:无
* 限定条件:netTask 上下文
*/
static void net_pending_tick(void) {
uint32_t now = HAL_GetTick();
int i;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
net_pending_t *p = &s_net_pending[i];
net_sock_t *p_sock = &s_net_socks[i];
if (p->active == 0U) {
continue;
}
if (p_sock->in_use == 0U) {
/* fd 已关闭(正常路径在 close 内先了结;此处兜底,防任何路径漏结) */
net_pending_complete(i, NET_ERR_CONNRESET);
continue;
}
/* ① 数据到达(数据优先,与旧自旋一致) */
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) {
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; /* 虚假标志:缓冲无数据,继续等待 */
}
}
/* ② 到点(tick 差值按有符号比较,抗回绕) */
if ((int32_t)(now - p->deadline) >= 0) {
s_net_errno = NET_ERR_TIMEDOUT;
net_pending_complete(i, NET_ERR_TIMEDOUT);
continue;
}
/* ③ 断连:排空 → 数据或 EOF(0) + 自动重监听 */
if (p_sock->state == NET_SOCK_STATE_CLOSED) {
int rem = ch395f_get_recv_len((uint8_t)i);
if (rem > 0) {
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 {
if (p_sock->auto_relisten != 0U) {
(void)net_relisten_try(p_sock); /* 非阻塞:失败由重试轮询接管 */
}
net_pending_complete(i, 0);
}
continue;
}
/* 其他状态:保持等待(见本区块头注释) */
/* ④ 看门狗:到点 + 裕量仍未了结 = 完成逻辑缺位 → 强制了结并留痕 */
if ((int32_t)(now - p->deadline) >= (int32_t)NET_PENDING_WATCHDOG_MS) {
DBG_ERROR("sock%d pending recv stuck %ums past deadline (force settle)",
i, (unsigned)((int32_t)now - (int32_t)p->deadline));
s_net_errno = NET_ERR_TIMEDOUT;
net_pending_complete(i, NET_ERR_TIMEDOUT);
}
}
}
/*
* 函数功能:清除本 fd 的挂起请求(不通知;用于"新 fd 不得继承"与 close 后的幂等清理)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 返回值:无
* 限定条件:无
*/
static 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 上下文
*/
static void net_pending_cancel(int sockfd, int err) {
net_pending_complete(sockfd, err); /* complete 内部校验范围与在位 */
}
/*
* 函数功能:取消全部挂起请求并逐一通知(网络重建路径)
* 入口参数:err - 通知的错误码 int
* 返回值:无
* 限定条件:netTask 上下文;须在控制块清零与芯片复位之前调用
*/
static void net_pending_cancel_all(int err) {
for (int i = 0; i < NET_MAX_SOCKETS; i++) {
net_pending_complete(i, err);
}
}
/*
* 函数功能:关闭前了结本 fd 的挂起 recv(per-fd 串行:先完成、再关闭)
* 入口参数:p_sock - 将被关闭的 Socket 控制块指针 net_sock_t *
* 返回值:无(无在位挂起时静默返回)
* 限定条件:netTask 上下文;须在 ch395f_close_socket 之前调用
* 函数说明:先排空尝试——关闭不丢已到达数据(芯片缓冲在 close 前可读);
* 有数据按字节数通知,否则取消(NET_ERR_CONNRESET)
*/
static void net_pending_settle_close(net_sock_t *p_sock) {
int sockfd = (int)(p_sock - s_net_socks);
int val;
int avail;
if (s_net_pending[sockfd].active == 0U) {
return;
}
val = NET_ERR_CONNRESET;
avail = ch395f_get_recv_len((uint8_t)sockfd);
if (avail > 0) {
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);
}
/*
* 函数功能:关闭 Socket(内部版本,不经过消息队列)
* 入口参数:p_sock - 要关闭的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:0 - 关闭成功 int
* 限定条件:必须在 netTask 上下文中调用;p_sock 有效
* 函数说明:1. 关闭 CH395F Socket,排空上一条连接残留的 DISCONNECT 中断
* 2. 等待芯片回到 CLOSED 状态(超时 500ms),再清零控制块
*/
static int net_close_locked(net_sock_t *p_sock) {
int sockfd = (int)(p_sock - s_net_socks);
uint32_t tick = HAL_GetTick();
/* L3:先了结本 fd 的挂起 recv(排空/取消 + 必通知),再关芯片 Socket
* ——关闭不得让等待中的调用方永久悬挂 */
net_pending_settle_close(p_sock);
ch395f_close_socket(sockfd);
/* 排空上一条连接残留的 DISCONNECT 中断(单连接模式仅有 Socket 0,
* 复用同一 Socket 时,旧连接的 DISCONNECT 在 FIN/ACK 完成后才上报,
* 若不清除会被下一连接误判为"刚建立就断开")。等待芯片回到 CLOSED
* 并清掉 Socket/全局中断状态,避免事件串扰。
*/
do {
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[0] == CH395F_SOCKET_CLOSED) {
break;
}
if (HAL_GetTick() - tick > 500) {
break;
}
osDelay(5);
} while (1);
memset(p_sock, 0, sizeof(net_sock_t));
net_relisten_clear(sockfd); /* 关闭即取消该 fd 的待重试(D10 重试队列) */
net_pending_clear(sockfd); /* 关闭即清除该 fd 的挂起状态(L3;幂等) */
return 0;
}
/*
* 函数功能:发送数据(线程安全版本,通过消息队列委托给 netTask)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 待发送数据缓冲区指针(调用返回前须保持有效,本函数不拷贝数据) const void *
* len - 数据长度(字节),> 0 int
* flags - 标志位,当前实现忽略(保留 BSD send 接口兼容) int
* 返回值:实际发送字节数 int >= 0 || -1
* -1 - 发送失败(参数非法 / 消息队列入队失败 / netTask 侧发送错误) int
* 限定条件:1. 必须在【非 netTask】上下文中调用(应用/测试任务);若在 netTask 内调用会自己等自己
* (netTask 阻塞在 xTaskNotifyWait 而无人处理消息)→ 死锁。netTask 内请直接用 net_send_locked()。
* 2. Socket 须处于 ESTABLISHED 状态;net_init() + MX_FREERTOS_Init() 已执行;
* netMsgQueueHandle 已由 net_init 创建。
* 函数说明:所有 CH395F SPI 访问只允许在 netTask 内串行执行(见 Trap 16)。本函数作为应用侧 API,
* 自身不直接触碰 SPI,而是把发送请求打包成 net_msg_t 投递到 netMsgQueueHandle,由 netTask
* 的 net_process_messages() 取出并在 netTask 上下文调用 net_send_locked() 真正写 CH395F。
* 调用方通过 FreeRTOS TaskNotify 阻塞等待处理结果,从而对外呈现"同步发送"语义:
* 函数返回即代表本次发送已被 netTask 处理完毕(成功或失败)。
*/
int net_send(int sockfd, const void *buf, int len, int flags) {
(void)flags; /* flags 当前未使用,显式 void 以消除未用参数告警 */
net_msg_t msg; /* 一次性消息结构体;入队后由 netTask 取出处理(注意只传指针不拷贝数据) */
/* ---- 1. 参数合法性检查 ---- */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF; /* 描述符越界 */
return -1;
}
/* ---- 2. 运行环境检查:消息队列须已创建(网络层已初始化) ---- */
if (netMsgQueueHandle == NULL) {
s_net_errno = NET_ERR; /* 网络层未初始化 */
return -1;
}
/* ---- 3. 组装消息:把本次 send 的全部上下文填入 msg ---- */
msg.type = NET_MSG_SEND; /* 消息类型,netTask 据此分派到 net_send_locked */
msg.sockfd = sockfd; /* 目标 Socket */
msg.buf = (void *)buf; /* 数据缓冲(队列仅拷贝指针,buf 须有效至函数返回) */
msg.len = len; /* 待发送字节数 */
msg.flags = 0; /* 实际标志位(忽略入参 flags) */
msg.timeout_ms = 0U; /* 发送不使用超时字段 */
msg.caller = xTaskGetCurrentTaskHandle(); /* 记录调用任务句柄,供 netTask 回执时精准通知 */
msg.result = -1; /* 预置失败;netTask 处理成功后会改写为实际发送字节数 */
/* ---- 4. 入队:将消息投递给 netTask,osWaitForever 表示队列满时阻塞调用方 ---- */
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
s_net_errno = NET_ERR_BUSY; /* 入队失败(队列异常等极端情况) */
return -1;
}
/* ---- 5. 阻塞等待 netTask 处理完毕:netTask 调 net_send_locked 后通过
* xTaskNotify() 唤醒本任务,并把结果(实际发送字节数或 -1)作为通知值传回 ---- */
msg.result = net_wait_result("snd", sockfd); /* 两段式等待(防丢失通知,见 net_wait_result) */
/* ---- 6. 结果回传:负数视为错误并设置线程级错误码;否则返回实际发送字节数 ---- */
if (msg.result < 0) {
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
* errno;对外仍保持 BSD 语义——失败一律返回 -1 */
s_net_errno = msg.result;
return -1;
}
return msg.result;
}
/*
* 函数功能:接收数据(线程安全版本,通过消息队列委托给 netTask)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位,0=阻塞,NET_MSG_DONTWAIT=非阻塞 int
* 返回值:实际接收字节数 int >= 0 || 0 || -1
* 0 - 对端关闭连接 int
* -1 - 接收失败(参数无效、队列满等) int
* 限定条件:Socket 处于 ESTABLISHED 状态;net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文
* 函数说明:将接收请求放入消息队列,阻塞等待 netTask 处理完毕并返回结果。netTask 内请使用 net_recv_sock()
*/
int net_recv(int sockfd, void *buf, int len, int flags) {
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_RECV;
msg.sockfd = sockfd;
msg.buf = buf;
msg.len = len;
msg.flags = flags;
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("rcv", sockfd);
if (msg.result < 0) {
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
* errno;对外仍保持 BSD 语义——失败一律返回 -1 */
s_net_errno = msg.result;
return -1;
}
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; /* 一次性消息结构体:队列按值存结构副本(仅指针、不拷数据),入队后由 netTask 取出处理 */
/* ---- 1. 参数合法性检查 ---- */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF; /* 描述符越界 */
return -1;
}
/* ---- 2. 运行环境检查:消息队列须已创建(网络层已初始化) ---- */
if (netMsgQueueHandle == NULL) {
s_net_errno = NET_ERR; /* 网络层未初始化 */
return -1;
}
/* ---- 3. 分片长度检查:timeout_ms = 0 在 netTask 侧意味着"改用默认
* NET_RECV_TIMEOUT_MS(30 秒)",恰是本接口要规避的长阻塞,直接拒绝 ---- */
if (timeout_ms == 0U) {
s_net_errno = NET_ERR_INVAL; /* 分片长度必须 > 0 */
return -1;
}
/* ---- 4. 组装消息:把本次分片读的全部上下文填入 msg ---- */
msg.type = NET_MSG_RECV; /* 消息类型,netTask 据此分派到 net_recv_locked */
msg.sockfd = sockfd; /* 目标 Socket */
msg.buf = buf; /* 接收缓冲(队列仅拷贝指针,buf 须有效至本函数返回) */
msg.len = len; /* 缓冲容量:单次读取字节数上限(允许部分读) */
msg.flags = 0; /* 阻塞式:netTask 侧轮询至"有数据"或"本片超时"(勿置 NET_MSG_DONTWAIT) */
msg.timeout_ms = timeout_ms; /* 本片预算:netTask 最多等待 timeout_ms,保证对 netTask 的占用有界(E-02) */
msg.caller = xTaskGetCurrentTaskHandle(); /* 记录调用任务句柄,netTask 处理后经 xTaskNotify() 精准回执 */
msg.result = -1; /* 预置失败(仅防御值;真实结果经任务通知值回传,见步骤 6) */
/* ---- 5. 入队:将消息投递给 netTask,osWaitForever 表示队列满时阻塞调用方 ---- */
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
s_net_errno = NET_ERR_BUSY; /* 入队失败(队列异常等极端情况) */
return -1;
}
/* ---- 6. 阻塞等待 netTask 处理完毕:netTask 执行 net_recv_locked 后经
* xTaskNotify() 把结果(字节数 / 0 / 负错误码)以 32 位通知值回传本任务;
* 注意 net_msg_t 按值拷入队列,队列里的 result 字段不会回来——
* 通知值是唯一的真实回传通道(勿给 net_msg_t 加"回传用"字段) ---- */
msg.result = net_wait_result("rcv", sockfd); /* 两段式等待:零等待拾取 + 阻塞,防丢失通知(见 net_wait_result) */
/* ---- 7. 回传结果(三分支:分片超时 / 真错误 / 数据或 EOF) ---- */
if (msg.result == NET_ERR_TIMEDOUT) {
/* 本片超时:timeout_ms 内未等到数据,非致命。
* 超时只凭返回码 -2 判定、不得依赖 errno:s_net_errno 是跨任务共享的
* 全局量,netTask 处理本片时写入的 NET_ERR_TIMEDOUT 随时可能被其他任务
* 的 net 调用覆盖;而返回码是 netTask 经任务通知值专为本任务回传的,
* 天然无竞态(本分支也不再改写 s_net_errno)。
* 调用方见到 -2 后累计自身总超时预算,决定是否发起下一片。
* (本判断必须先于下方通用 <0 分支:NET_ERR_TIMEDOUT = -9 同样满足 <0) */
return -2;
}
if (msg.result < 0) {
/* 真错误:netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值回传,
* 此处恢复为线程级错误码(可经 net_get_errno() 读取);
* 对外保持 BSD 语义——失败一律返回 -1 */
s_net_errno = msg.result;
return -1;
}
/* >= 0:0 = 对端关闭(EOF,net 层尽力自动重监听,失败由重试轮询接管);
* > 0 = 实际接收字节数(可能小于 len,属正常部分读) */
return msg.result;
}
/*
* 函数功能:关闭 Socket(线程安全版本,通过消息队列委托给 netTask)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* 返回值:0 - 关闭成功 int
* -1 - 关闭失败 int
* 限定条件:Socket 已创建且处于非 CLOSED 状态;net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文
* 函数说明:将关闭请求放入消息队列,阻塞等待 netTask 处理完毕。TCP Socket 先断开连接再释放控制块。
* netTask 内请使用 net_close_sock()
*/
int net_close(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_CLOSE;
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("clo", sockfd);
if (msg.result < 0) {
/* netTask 把具体错误码(如 NET_ERR_WOULDBLOCK)以负值搭通知值回传,此处恢复为
* errno;对外仍保持 BSD 语义——失败一律返回 -1 */
s_net_errno = msg.result;
return -1;
}
return msg.result;
}
/*
* 函数功能:发送 UDP 数据报(含目标地址)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 待发送数据缓冲区指针 const void *
* len - 数据长度 int > 0
* flags - 标志位,通常为 0 int
* dest_addr - 目标地址结构体指针 const struct net_sockaddr *
* addrlen - 目标地址结构体长度 int >= sizeof(struct net_sockaddr_in)
* 返回值:实际发送字节数 int > 0 || -1
* -1 - 发送失败 int
* 限定条件:Socket 已创建且为 UDP 类型(SOCK_DGRAM);net_init() 已调用
*/
int net_sendto(int sockfd, const void *buf, int len, int flags,
const struct net_sockaddr *dest_addr, int addrlen) {
net_sock_t *p_sock;
const struct net_sockaddr_in *p_addr_in;
int sent = 0;
uint32_t tick_start;
/* 参数检查 */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
}
p_sock = &s_net_socks[sockfd];
if (!p_sock->in_use) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (buf == NULL || len <= 0) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
/* UDP 模式:设置目标地址 */
if (dest_addr != NULL && addrlen >= (int)sizeof(struct net_sockaddr_in)) {
p_addr_in = (const struct net_sockaddr_in *)dest_addr;
/* 转换 IP 为大端数组(同 TCP 客户端,set_des_ip 期望大端) */
uint8_t ip_arr[4];
uint32_t ip = p_addr_in->sin_addr.s_addr;
ip_arr[0] = (uint8_t)(ip >> 24);
ip_arr[1] = (uint8_t)(ip >> 16);
ip_arr[2] = (uint8_t)(ip >> 8);
ip_arr[3] = (uint8_t)(ip);
ch395f_set_des_ip(sockfd, ip_arr);
ch395f_set_des_port(sockfd, net_ntohs(p_addr_in->sin_port));
}
tick_start = HAL_GetTick();
/* 发送数据 */
while (sent < len) {
int chunk = len - sent;
if (chunk > NET_SEND_CHUNK_MAX) {
chunk = NET_SEND_CHUNK_MAX;
}
/* 检查发送缓冲区 */
if (!p_sock->send_ready) {
if (NET_SEND_TIMEOUT_MS > 0 &&
(HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) {
DBG_ERROR("sock%d send stall: send_ready not set (%d/%d)", sockfd, sent, len);
s_net_errno = NET_ERR_TIMEDOUT;
break;
}
net_poll();
continue;
}
ch395f_write_send_buf(sockfd, (uint8_t *)buf + sent, (uint16_t)chunk);
p_sock->send_ready = 0;
sent += chunk;
p_sock->send_bytes += (uint32_t)chunk;
/* 等待发送完成 */
tick_start = HAL_GetTick();
while (!p_sock->send_ready) {
net_poll();
if (NET_SEND_TIMEOUT_MS > 0 &&
(HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) {
break;
}
}
}
if (sent < len) {
/* 短写告警:调用方必须检查返回值。静默截断会让对端永久等待剩余字节
* (2026-09-21 实证:UI 推流应答被截断 → 客户端反复判定"全程无字节"失联)。 */
DBG_ERROR("sock%d short send %d/%d (errno=%d)", sockfd, sent, len, s_net_errno);
}
return sent;
}
/*
* 函数功能:接收 UDP 数据报(含源地址)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位,通常为 0 int
* src_addr - 输出:发送方地址结构体指针 struct net_sockaddr *(可为 NULL)
* addrlen - 输入输出:源地址长度 int *(可为 NULL)
* 返回值:实际接收字节数 int >= 0 || -1
* -1 - 接收失败 int
* 限定条件:Socket 已创建且为 UDP 类型(SOCK_DGRAM);net_init() 已调用;有数据可用
*/
int net_recvfrom(int sockfd, void *buf, int len, int flags,
struct net_sockaddr *src_addr, int *addrlen) {
net_sock_t *p_sock;
uint32_t tick_start;
/* 参数检查 */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
}
p_sock = &s_net_socks[sockfd];
if (!p_sock->in_use) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (buf == NULL || len <= 0) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
tick_start = HAL_GetTick();
while (1) {
net_poll();
int recv_len = ch395f_get_recv_len(sockfd);
if (recv_len > 0) {
/* UDP Server 模式:前 8 字节为固定格式(IP + 端口 + 长度)*/
if (p_sock->type == NET_SOCK_DGRAM && recv_len >= 8) {
uint8_t header[8];
ch395f_read_recv_buf(sockfd, header, 8);
recv_len -= 8;
/* 填充来源地址 */
if (src_addr != NULL &&addrlen != NULL &&
*addrlen >= (int)sizeof(struct net_sockaddr_in)) {
struct net_sockaddr_in *p_addr_in = (struct net_sockaddr_in *)src_addr;
p_addr_in->sin_family = NET_AF_INET;
/* CH395F UDP 头 [0-1] reserved [2-3] src_port(LE) [4-7] src_ip */
p_addr_in->sin_port = net_htons((uint16_t)(header[2] | (header[3] << 8)));
p_addr_in->sin_addr.s_addr = ((uint32_t)header[4]) |
((uint32_t)header[5] << 8) |
((uint32_t)header[6] << 16) |
((uint32_t)header[7] << 24);
*addrlen = sizeof(struct net_sockaddr_in);
}
}
/* 读取数据 */
if (recv_len > 0) {
int read_len = (recv_len > len) ? len : recv_len;
ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len);
p_sock->recv_bytes += (uint32_t)read_len;
return read_len;
}
}
/* 非阻塞模式检查 */
if (flags & NET_MSG_DONTWAIT) {
s_net_errno = NET_ERR_WOULDBLOCK;
return -1;
}
/* 超时检查 */
if (NET_RECV_TIMEOUT_MS > 0 &&
(HAL_GetTick() - tick_start) >= NET_RECV_TIMEOUT_MS) {
s_net_errno = NET_ERR_TIMEDOUT;
return -1;
}
}
}
/*
* 函数功能:发送数据(内部版本,绕过消息队列,供 netTask 直接使用)
*/
/*
* 函数功能:发送数据(内部版本,绕过消息队列,供 netTask 直接使用)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* buf - 待发送数据缓冲区指针 const void *
* len - 数据长度 int > 0
* 出口参数:无
* 返回值:实际发送字节数(>= 0);-1 表示参数无效 int
* 限定条件:p_sock 非空;netTask 上下文(避免向自身发送消息造成死锁)
* 函数说明:直接调用 net_send_locked 完成发送
*/
int net_send_sock(net_sock_t *p_sock, const void *buf, int len) {
if (p_sock == NULL) {
s_net_errno = NET_ERR_BADF;
return -1;
}
return net_send_locked(p_sock, buf, len);
}
/*
* 函数功能:接收数据(内部版本,绕过消息队列,供 netTask 直接使用)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位,NET_MSG_DONTWAIT=非阻塞 int
* 返回值:实际接收字节数 int >= 0 || 0 || -1
* 0 - 对端关闭连接 int
* -1 - 接收失败 int
* 限定条件:必须在 netTask 上下文中调用;p_sock->in_use == 1
* 函数说明:直接操作 Socket 控制块,不经过消息队列。避免 netTask 向自身发送消息造成死锁
* (L3 后:netTask 上下文的阻塞等待会被挂起登记拒绝——自通知死锁,
* 本函数阻塞用法仅对 DONTWAIT 有效)
*/
int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags) {
if (p_sock == NULL) {
s_net_errno = NET_ERR_BADF;
return -1;
}
return net_recv_locked(p_sock, buf, len, flags, 0U, /* 0 = 默认 NET_RECV_TIMEOUT_MS */
xTaskGetCurrentTaskHandle());
}
/*
* 函数功能:关闭 Socket(内部版本,绕过消息队列,供 netTask 直接使用)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* 返回值:0 - 关闭成功 int
* -1 - 关闭失败 int
* 限定条件:必须在 netTask 上下文中调用;p_sock->in_use == 1
* 函数说明:直接操作 Socket 控制块,不经过消息队列。TCP Socket 先断开连接再释放控制块
*/
int net_close_sock(net_sock_t *p_sock) {
if (p_sock == NULL) {
s_net_errno = NET_ERR_BADF;
return -1;
}
return net_close_locked(p_sock);
}
/*
* 函数功能:注册 Socket 事件回调函数
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* cb - 回调函数指针 net_event_cb_t(可为 NULL 取消注册)
* arg - 用户自定义参数指针 void *
* 返回值:0 - 注册成功 int
* -1 - 注册失败 int
* 限定条件:Socket 已创建且 in_use == 1;net_init() 已调用
*/
int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) {
net_sock_t *p_sock;
/* 参数检查 */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
}
p_sock = &s_net_socks[sockfd];
if (!p_sock->in_use) {
s_net_errno = NET_ERR_BADF;
return -1;
}
p_sock->event_cb = cb;
p_sock->event_arg = arg;
return 0;
}
/*
* 函数功能:获取 Socket 最后一次操作返回的错误码
* 返回值:当前错误码值(NET_OK=0, NET_ERR_xxx=-N) int
* 限定条件:无
* 函数说明:每次 net_*() API 调用失败后,错误码被设置。调用成功时不自动清零
*/
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 *
* 出口参数:无
* 返回值:网络字节序的 32 位 IP 地址(0 表示参数无效) uint32_t
* 限定条件:无
* 函数说明:1. 逐段解析十进制数与并小端组装为 32 位值
* 2. 转换结果经 NET_HTONL 转为网络字节序
*/
uint32_t net_inet_addr(const char *cp) {
uint32_t addr = 0;
int i;
if (cp == NULL) {
return 0;
}
for (i = 0; i < 4; i++) {
uint32_t val = 0;
while (*cp >= '0' && *cp <= '9') {
val = val * 10 + (*cp - '0');
cp++;
}
addr |= (val << (i * 8));
if (*cp == '.') {
cp++;
}
}
return NET_HTONL(addr);
}
/*
* 函数功能:将网络字节序的 32 位地址转换为点分十进制字符串
* 入口参数:addr - 网络字节序的 32 位 IP 地址 uint32_t
* buf - 输出缓冲区(至少 16 字节) char *
* 出口参数:buf - 填充点分十进制字符串
* 返回值:成功返回 buf 指针;buf 为 NULL 时返回 NULL char *
* 限定条件:buf 非空且长度足够(>= 16 字节)
* 函数说明:1. 先经 NET_NTOHL 转为主机字节序再格式化输出
*/
char *net_inet_ntoa(uint32_t addr, char *buf) {
uint32_t naddr = NET_NTOHL(addr);
if (buf == NULL) {
return NULL;
}
snprintf(buf, 16, "%lu.%lu.%lu.%lu",
(unsigned long)(naddr & 0xFF),
(unsigned long)((naddr >> 8) & 0xFF),
(unsigned long)((naddr >> 16) & 0xFF),
(unsigned long)((naddr >> 24) & 0xFF));
return buf;
}
/*
* 函数功能:端口字节序转换(主机序 -> 网络序)
* 入口参数:hostshort - 主机字节序的 16 位端口 uint16_t
* 出口参数:无
* 返回值:网络字节序的 16 位端口 uint16_t
* 限定条件:无
* 函数说明:直接调用 NET_HTONS 完成转换
*/
uint16_t net_htons(uint16_t hostshort) {
return NET_HTONS(hostshort);
}
/*
* 函数功能:端口字节序转换(网络序 -> 主机序)
* 入口参数:netshort - 网络字节序的 16 位端口 uint16_t
* 出口参数:无
* 返回值:主机字节序的 16 位端口 uint16_t
* 限定条件:无
* 函数说明:直接调用 NET_NTOHS 完成转换
*/
uint16_t net_ntohs(uint16_t netshort) {
return NET_NTOHS(netshort);
}
/*
* 函数功能:IP 地址字节序转换(主机序 -> 网络序)
* 入口参数:hostlong - 主机字节序的 32 位 IP 地址 uint32_t
* 出口参数:无
* 返回值:网络字节序的 32 位 IP 地址 uint32_t
* 限定条件:无
* 函数说明:直接调用 NET_HTONL 完成转换
*/
uint32_t net_htonl(uint32_t hostlong) {
return NET_HTONL(hostlong);
}
/*
* 函数功能:IP 地址字节序转换(网络序 -> 主机序)
* 入口参数:netlong - 网络字节序的 32 位 IP 地址 uint32_t
* 出口参数:无
* 返回值:主机字节序的 32 位 IP 地址 uint32_t
* 限定条件:无
* 函数说明:直接调用 NET_NTOHL 完成转换
*/
uint32_t net_ntohl(uint32_t netlong) {
return NET_NTOHL(netlong);
}
/*
* 函数功能:分配一个空闲的 Socket 控制块
* 入口参数:无
* 出口参数:无
* 返回值:空闲 Socket 索引(0~NET_MAX_SOCKETS-1);-1 表示无空闲 Socket int
* 限定条件:s_net_socks 已定义
* 函数说明:1. 线性扫描 s_net_socks,返回首个 in_use == 0 的控制块索引
* 2. 调用方负责初始化该控制块
*/
static int alloc_socket(void) {
int i;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
if (!s_net_socks[i].in_use) {
return i;
}
}
return -1;
}
/*
* 函数功能:处理 CH395F CONNECT 中断事件(单连接到达 / Client 连接成功)
* 入口参数:p_sock - 发生连接事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 CONNECT 中断后调用;netTask 上下文
* 函数说明:1. 单连接:自身转 ESTABLISHED 并读取对端地址
* 2. TCP Client(CONNECTING):连接成功,读取对端地址并置 ESTABLISHED
* 3. 连接成功后使能 KeepAlive 并触发 NET_EVENT_CONNECTED 回调
*/
static void handle_connect_event(net_sock_t *p_sock) {
#if defined(NET_BENCH_CMD_LOG)
net_bench_cmd_log('a', (int)p_sock->ch395_sock, 0);
#endif
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
/* 单连接:收到 CONNECT 中断 = 自身有连接到达,转为 ESTABLISHED */
DBG_INFO("sock%d standalone accept", p_sock->ch395_sock);
uint8_t remote[6];
ch395f_get_remot_ipp(p_sock->ch395_sock, remote);
memcpy(p_sock->remote_ip_arr, remote, 4);
p_sock->remote_ip = ((uint32_t)remote[0]) |
((uint32_t)remote[1] << 8) |
((uint32_t)remote[2] << 16) |
((uint32_t)remote[3] << 24);
p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8));
p_sock->state = NET_SOCK_STATE_ESTABLISHED;
p_sock->send_ready = 1;
/* 清除残留的 TIMEOUT 中断标志 */
(void)ch395f_get_sock_int_status(p_sock->ch395_sock);
fire_event(p_sock, NET_EVENT_CONNECTED);
} else if (p_sock->state == NET_SOCK_STATE_CONNECTING) {
/* TCP Client 连接成功 */
uint8_t remote[6];
ch395f_get_remot_ipp(p_sock->ch395_sock, remote);
memcpy(p_sock->remote_ip_arr, remote, 4);
p_sock->remote_ip = ((uint32_t)remote[0]) |
((uint32_t)remote[1] << 8) |
((uint32_t)remote[2] << 16) |
((uint32_t)remote[3] << 24);
p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8));
p_sock->state = NET_SOCK_STATE_ESTABLISHED;
ch395f_set_keepalive_enable(p_sock->ch395_sock, 1);
fire_event(p_sock, NET_EVENT_CONNECTED);
}
}
/*
* 函数功能:处理 Socket 断开(DISCONNECT)事件,更新 Socket 状态机
* 入口参数:p_sock - 发生断开事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 DISCONNECT 中断后调用;netTask 上下文
* 函数说明:1. CH395F 在 DISCONNECT 后自动关闭 Socket(FUN_PARA 默认),需重新 OPEN + LISTEN
* 2. 单连接:置 CLOSED,交由 net_recv_locked 排空剩余数据后再关
* 3. 触发 NET_EVENT_DISCONNECTED 回调
*/
static void handle_disconnect_event(net_sock_t *p_sock) {
#if defined(NET_BENCH_CMD_LOG)
net_bench_cmd_log('d', (int)p_sock->ch395_sock, 0);
#endif
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 (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 &&
p_sock->state != NET_SOCK_STATE_BOUND &&
p_sock->state != NET_SOCK_STATE_CREATED) {
/* 未预期的断开 */
DBG_ERROR("sock%d disconnect in state %d", p_sock->ch395_sock, p_sock->state);
fire_event(p_sock, NET_EVENT_DISCONNECTED);
}
}
/*
* 函数功能:处理数据接收事件
* 函数功能:处理 Socket 数据接收(RECV_OK)事件
* 入口参数:p_sock - 发生接收事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 RECV_OK 中断后调用;netTask 上下文
* 函数说明:1. 仅触发 NET_EVENT_DATA_RECEIVED 回调通知上层
* 2. 不在此读取 recv_len,避免与 net_recv_locked 中的读取冲突(Trap 13)
*/
static void handle_recv_event(net_sock_t *p_sock) {
/* RECV_OK 中断已触发,直接通知上层。不在此读 recv_len,
* 避免和 net_recv_locked 里面的 ch395f_get_recv_len 冲突(Trap 13) */
p_sock->readable = 1; /* 置可读标志,供 net_select 在无 SPI 访问下判读 */
fire_event(p_sock, NET_EVENT_DATA_RECEIVED);
}
/*
* 函数功能:处理 Socket 发送完成(SEND_OK)事件
* 入口参数:p_sock - 发生发送完成事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 SEND_OK 中断后调用;netTask 上下文
* 函数说明:触发 NET_EVENT_SEND_COMPLETE 回调通知上层
*/
static void handle_send_event(net_sock_t *p_sock) {
fire_event(p_sock, NET_EVENT_SEND_COMPLETE);
}
/*
* 函数功能:处理 Socket 超时(SOCK_TIMEOUT)事件,更新 Socket 状态机
* 入口参数:p_sock - 发生超时事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 SOCK_TIMEOUT 中断后调用;netTask 上下文
* 函数说明:1. TCP Client(CONNECTING):连接超时,回到 CREATED 状态
* 2. 已建立连接:回到 CLOSED(单连接)
* 3. 监听态:异常超时,记录错误日志
* 4. 触发 NET_EVENT_TIMEOUT 回调
*/
static void handle_timeout_event(net_sock_t *p_sock) {
#if defined(NET_BENCH_CMD_LOG)
net_bench_cmd_log('x', (int)p_sock->ch395_sock, 0);
#endif
if (p_sock->state == NET_SOCK_STATE_CONNECTING) {
/* TCP Client 连接超时 */
DBG_ERROR("sock%d CONNECT TIMEOUT", p_sock->ch395_sock);
p_sock->state = NET_SOCK_STATE_CREATED;
} else if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
DBG_ERROR("sock%d TIMEOUT", p_sock->ch395_sock);
/* Trap 23:异常释放(TIMEOUT,例如客户端中止上传)→ 复监听前隔离,
* 等对端在途数据落尽并排空 RX 残渣(详见 net_relisten_locked 区块注释) */
{
int qfd = (int)(p_sock - s_net_socks);
if ((qfd >= 0) && (qfd < NET_MAX_SOCKETS)) {
s_relisten_quarantine[qfd] = 1U;
}
}
p_sock->state = NET_SOCK_STATE_CLOSED;
} else if (p_sock->state == NET_SOCK_STATE_LISTENING) {
/* 监听超时(异常情况) */
DBG_ERROR("sock%d LISTEN TIMEOUT", p_sock->ch395_sock);
}
fire_event(p_sock, NET_EVENT_TIMEOUT);
}
/*
* 函数功能:触发 Socket 事件回调(若已注册)
* 入口参数:p_sock - 触发事件的 Socket 控制块指针 net_sock_t *
* event - 事件类型 net_event_t
* 出口参数:无
* 返回值:无
* 限定条件:net_init() 已调用
* 函数说明:1. 仅当 p_sock->event_cb 非空时调用回调
* 2. 回调参数为 ch395_sock、event、event_arg
*/
static void fire_event(net_sock_t *p_sock, net_event_t event) {
if (p_sock->event_cb != NULL) {
p_sock->event_cb(p_sock->ch395_sock, event, p_sock->event_arg);
}
}
/*
* 函数功能:将点分十进制 IP 地址字符串解析为 4 字节数组
* 入口参数:str - 点分十进制 IP 字符串(如 "192.168.1.100") const char *
* ip_arr - 输出:4 字节 IP 数组(网络序,低字节在前) uint8_t *
* 出口参数:ip_arr - 填充解析后的 IP 地址
* 返回值:无
* 限定条件:str 与 ip_arr 均非空
* 函数说明:1. 逐段解析十进制数写入 ip_arr[0..3]
* 2. 参数非法(NULL)时直接返回,不做解析
*/
static void parse_ip_string(const char *str, uint8_t *ip_arr) {
int i;
if (str == NULL ||ip_arr == NULL) {
return;
}
for (i = 0; i < 4; i++) {
uint32_t val = 0;
while (*str >= '0' && *str <= '9') {
val = val * 10 + (*str - '0');
str++;
}
ip_arr[i] = (uint8_t)val;
if (*str == '.') {
str++;
}
}
}
/*
* 函数功能:根据描述符获取 Socket 控制块指针(内部使用)
* 入口参数:sockfd - Socket 描述符 int 0 - NET_MAX_SOCKETS-1
* 出口参数:无
* 返回值:Socket 控制块指针;描述符越界或未分配时返回 NULL net_sock_t *
* 限定条件:无
* 函数说明:1. 描述符越界(<0 或 >= NET_MAX_SOCKETS)返回 NULL
* 2. 未使用(in_use == 0)的 Socket 返回 NULL
*/
net_sock_t *net_get_sock(int sockfd) {
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
return NULL;
}
if (!s_net_socks[sockfd].in_use) {
return NULL;
}
return &s_net_socks[sockfd];
}
/*
* 函数功能:获取 Socket 本地地址(IP + Port)
* 入口参数:sockfd - socket 描述符 int
* addr - 输出:本地地址 struct net_sockaddr *
* addrlen - 输入输出:地址结构体长度 int *
* 返回值:0 - 成功,-1 - 失败 int
* 限定条件:socket 已创建或已绑定
* 函数说明:返回 sin_family + sin_port(网络序)+ sin_addr(网络序,从 net_init 缓存的 s_local_ip)
*/
int net_getsockname(int sockfd, struct net_sockaddr *addr, int *addrlen) {
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
}
const net_sock_t *p_sock = &s_net_socks[sockfd];
if (!p_sock->in_use && p_sock->state == NET_SOCK_STATE_CLOSED) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (addr == NULL ||addrlen == NULL || *addrlen < (int)sizeof(struct net_sockaddr_in)) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
struct net_sockaddr_in *p_addr_in = (struct net_sockaddr_in *)addr;
memset(p_addr_in, 0, sizeof(struct net_sockaddr_in));
p_addr_in->sin_family = NET_AF_INET;
p_addr_in->sin_port = net_htons(p_sock->local_port);
p_addr_in->sin_addr.s_addr = ((uint32_t)s_local_ip[0]) |
((uint32_t)s_local_ip[1] << 8) |
((uint32_t)s_local_ip[2] << 16) |
((uint32_t)s_local_ip[3] << 24);
*addrlen = sizeof(struct net_sockaddr_in);
return 0;
}