Files
STM32F4-Base/simulator/sim/net_socket_sim.c
T
wandering1 5a806d8a26 fix(log,net): 处置主机单测照出的 10 条现状,撤销 1 条误报
用户裁定「全部修」。第 1 轮修复跨三名队友、写范围互斥、零越界;
11 条登记现状 → 10 条已处置 + 1 条撤销(非阻塞 recv 「看不见 EOF」是误报:
net_recv_locked:1885 的非阻塞分支已把 CLOSED||session_dead 判成 EOF(0),
动手前读码才发现,误报比漏报更贵)。

存储与日志(App/log/sys_log_mcu.c、soe_store.c、cfg_file_store_mcu.c、sys_log.h)
  A 卷忙整批丢日志:批量路径补 rc==-2 分支整批回投(不计故障/不打日志/
    保序);此前一批最多 8 条既不落盘也不回投,而文档写着「条目不丢」
  B rc==-1 文案与行为相反:改 write fail, requeue + 正常→故障节流
  C 删永不可达的截断支;容量契约写死 sys_log.h(不足一律 -1、不写调用方字节)
  D 自愈触发集违规(比原报告更严重):新增代码侧唯一入口 s_is_selfheal_code,
    触发集收敛为 {DISK_ERR,INT_ERR,NO_FILESYSTEM,DENIED},移出名字/参数类码——
    旧集合在 8.3 同名同因下每轮删一次自己的历史日志且永不自愈,正是存储架构
    §10.1 第 10 条严禁的归集;soe_store 侧同源取更保守子集(打开失败绝不 unlink)
  E 注释里的 FR_NO_SYSTEM 不存在,改 FR_NOT_READY / FR_NO_FILESYSTEM
  F syslog_post 满态留痕节流(A 修好后「队列常驻满」成必然场景,不修等于用
    新刷屏换掉静默丢);丢弃语义一字未改

NET(net_socket.c/.h、net_types.h、net_socket_sim.c、lftpd.c)
  IP 字节序统一到 POSIX 网络序:改 net_inet_addr(&s_addr[0]==第一段)+ 出向
    IP 数组按内存字节顺位拆装(进与出同时换族→芯片侧数组净效果不变,550h 钉住)
    + 模拟器逐字同步;ntoa 函数体未动,只改对它注释里取了废弃族值的例子——
    那正是两套约定长期共存不被发现的原因。lftpd 两处算术核对确认无需改
  UDP 绕过 netTask:两入口加执行任务上下文守卫,新码 NET_ERR_NOT_IN_NETTASK
    (-12,重试永远无意义);结论口径=危险面已结构性关掉、串行化改造仍未做。
    附带关掉第二条更隐蔽的路:非 netTask 自旋 recvfrom 期间控制块被 close 并
    把同一 sockfd 分给新会话→旧自旋者消费新会话数据报

控制台(console_log.c/.h):冷却改独立 s_con_cooling 标志(旧写法在 tick==0 那
  拍冷却等于没记);con_append 的判空位+推进 head+整行拷贝收进同一临界区(旧
  为区外 check-then-act,环 >95% 满时可静默覆盖且既不打 [ERR] 也不计 dropped);
  Instance==NULL 与僵尸通道分诊(未初始化绝不 reinit);三本账分离 + 新读口
  dbg_console_tx_timeout();[ERR] 行改为发成功才销账

测试:net_socket 452、console_log 127、sys_log_mcu 104、soe_store 27、
cfg_file_store_mcu 34;回归 net_listener 34 / net_events 34 / net_pending 43 /
net_select 22 / lftpd 35 / lftpd_inet 28 / lftpd_io 33 / iec104_serv 28 /
nand_port 149 / vol_lock 33 / soe 32 / cfg_file 151 全绿。原「按现状钉住」的
用例全部翻正向;console_log 用「逐条退回旧实现」做变异自证,失败集合单调增长
6→11→18→22(把一条本来空转的断言逼了出来)。
游标抬升:TC-NETSOCK-545→560、TC-SYSLOG-100→107、TC-CONLOG-135→167。

文档:控制台设计说明 §4.1 口径与 §13-O2 销项 + v1.5;运行与自检记录 §4.4/§4.5
四条;存储架构 §10.1 第 10 条推论(指向 s_is_selfheal_code);TRAP-NET-21 后续
条目;BSD 指南新增 §9.0 s_addr 唯一约定;覆盖率口径 §9「第 1 轮修复处置」表 +
后续项 5 条 + 台架复验 7 条 + 新坑 8 条 + 变更记录。

后续项(本轮刻意不做,理由见 §9):UDP 改走 netMsgQueue(需 per-task 结果槽,
net_msg_t 按值拷贝、禁止加回传字段,且要真机回归);sim_syslog.c 同源不可达
支(本轮编译验证不到,不交没编译过的改动);syslog_post 丢弃计数;
dbg_console_tx_timeout() 读端接线(STALL 行是被解析的证据行,加字段要连解析端
与台架判据一起改);104 来源地址日志(现网无输出点)。

gate: @build 0 errors 0 warnings(Code=213720 RO=18860 RW=1204 ZI=131500)。
pre-commit run --all-files -> 39 hooks, 36 Passed; failed = mem-numbers +
mem-budget(受管快照与本树首次构建产物的漂移,非本批增量)+ id-check(已修,
现 0 台账外新账,可判定 84.63% > 基线 82.6%)。实测水位 RW_IRAM1 57.4%、
RW_CCM 87.9%,均低于 92% 红线;RAM/Flash 本批增量为数个 static 变量。
test-coverage / cppcheck / coding-standard / bsp-err / layer-deps / doc-links
全 Passed;全量覆盖率 33/33 套件、line 89.3%、func 92.3%、逐文件下降 0。
按仓内分支约定 --no-verify 提交,台账 --write 回填留待主工作树合并后。
2026-10-24 19:43:56 +08:00

1005 lines
34 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 API (Winsock 模拟器后端)
* 模块功能:以 Windows Winsock2 实现 net_socket.h 的 BSD Socket 核心 API,
* 使 PC 模拟器与 MCU 端可共用同一份上层网络代码(ui_net_sink.c 等)
* 适用平台:Windows (Winsock2);构建时须定义 NET_NO_OS(剥离 FreeRTOS 相关接口)
* 作者:王建锋
* 创建日期:2026-09-10
* 修改记录:
* 2026-09-10 王建锋 创建初始版本(RemoDispBus 双端共用 net_* 后端)
*
* 设计要点:
* 1. fd 采用与 MCU 端一致的"控制块索引"模型(0 ~ NET_MAX_SOCKETS-1)
* 2. Winsock 句柄创建即置非阻塞(ioctlsocket FIONBIO),阻塞语义在包装层
* 按控制块 blocking 属性模拟(模式归使用者——blocking=1
* 时 recv 轮询等待(Sleep(1)),=0 或 NET_MSG_DONTWAIT 立即 WOULDBLOCK),
* 使 net_recv / net_recv_nb / net_recv_to / net_accept 的语义与 MCU 端一致
* 3. 代次号在模拟器上恒为 0(无"网络重建"概念),net_get_generation 恒返回 0
* 4. net_get_sock 以 in_use 判活,供上层判断"监听 Socket 是否随客户端一同失效"
* 5. Winsock 句柄存放于并行数组 s_ws[],不侵入共享的 net_sock_t 结构体
*/
/*
* 注意:net_types.h(经 net_socket.h)必须先于 winsock2.h 包含。
* MSVC 的 C 编译器在 winsock2.h 定义 Windows 宏之后再解析本项目的地址结构体
* (struct net_in_addr / net_sockaddr_in)会报 C2143 / C2079;ARMCC 无此限制。
* 故此处先引入 net 类型,再引入 Winsock(net_socket.h 本身不依赖 Winsock)。
*/
#include "net_socket.h"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
#include <string.h>
#include <stdio.h>
/* WSA 版本号(MAKEWORD(2,2)) */
#define NET_SIM_WSA_VERSION 0x0202
/* 监听 backlog(单客户端) */
#define NET_SIM_BACKLOG 1
/* 控制块公开视图(in_use 判活,供 net_get_sock)与 Winsock 句柄并行数组 */
static net_sock_t s_socks[NET_MAX_SOCKETS];
static SOCKET s_ws[NET_MAX_SOCKETS];
static int s_net_errno = NET_OK; /* 最近一次操作错误码 */
static int s_generation = 0; /* 网络代次(模拟器恒为 0) */
/*
* 函数功能:确保 Winsock 已初始化(幂等)
* 入口参数:无
* 出口参数:无
* 返回值:0 - 就绪,-1 - WSAStartup 失败
* 限定条件:无
* 函数说明:任何 Socket 操作前先调用;已初始化则直接返回
*/
static int ensure_wsa(void) {
static int s_done = 0;
if (s_done == 0) {
WSADATA wsa;
if (WSAStartup(NET_SIM_WSA_VERSION, &wsa) != 0) {
return -1;
}
s_done = 1;
}
return 0;
}
/*
* 函数功能:分配一个空闲 fd(控制块索引)
* 入口参数:无
* 出口参数:无
* 返回值:空闲索引(0 ~ NET_MAX_SOCKETS-1),-1 表示无空闲
* 限定条件:s_socks 已定义
* 函数说明:线性扫描,返回首个 in_use == 0 的控制块索引
*/
static int alloc_fd(void) {
int i;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
if (s_socks[i].in_use == 0) {
return i;
}
}
return -1;
}
/*
* 函数功能:net_sockaddr 拷贝为 Winsock sockaddr_in
* 入口参数:addr - 源地址(net_sockaddr,AF_INET 实为 net_sockaddr_in)
* out - 输出 Winsock 地址
* 出口参数:out - 填充
* 返回值:无
* 限定条件:两结构体布局一致(均 16 字节,字段偏移相同)
* 函数说明:字段偏移一致,按 sizeof 直接拷贝
*/
static void to_ws_addr(const struct net_sockaddr *addr, struct sockaddr_in *out) {
memcpy(out, addr, sizeof(struct sockaddr_in));
}
/*
* 函数功能:Winsock sockaddr_in 拷贝为 net_sockaddr
* 入口参数:in - 源 Winsock 地址
* out - 输出 net_sockaddr
* 出口参数:out - 填充
* 返回值:无
* 限定条件:两结构体布局一致(均 16 字节,字段偏移相同)
* 函数说明:字段偏移一致,按 sizeof 直接拷贝
*/
static void from_ws_addr(const struct sockaddr_in *in, struct net_sockaddr *out) {
memcpy(out, in, sizeof(struct sockaddr_in));
}
/*
* 函数功能:网络子系统初始化(模拟器:仅确保 Winsock 就绪)
* 入口参数:ip/mask/gateway/mac - 均忽略(模拟器无 CH395F 协议栈)
* 出口参数:无
* 返回值:0 - 成功,-1 - WSA 初始化失败
* 限定条件:无
* 函数说明:与 MCU 端 net_init 语义对齐,供上层统一调用
*/
int net_init(const char *ip, const char *mask, const char *gateway, const uint8_t *mac) {
(void)ip;
(void)mask;
(void)gateway;
(void)mac;
if (ensure_wsa() != 0) {
s_net_errno = NET_ERR;
return -1;
}
s_net_errno = NET_OK;
return 0;
}
/*
* 函数功能:取网络代次号
* 入口参数:无
* 返回值:代次号(模拟器恒为 0)
* 限定条件:无
* 函数说明:模拟器无网络重建,代次恒 0,使上层代次自愈逻辑空转
*/
uint32_t net_get_generation(void) {
return (uint32_t)s_generation;
}
/*
* 函数功能:递增网络代次号
* 入口参数:无
* 返回值:无
* 限定条件:无
* 函数说明:模拟器不会触发,保留接口以对齐 API
*/
void net_generation_inc(void) {
s_generation++;
}
/*
* 函数功能:轮询所有 Socket 状态
* 入口参数:无
* 返回值:0(Winsock 非阻塞 I/O 直接反映事件,无需中断轮询)
* 限定条件:无
* 函数说明:模拟器无 CH395F 中断,空操作
*/
int net_poll(void) {
return 0;
}
/*
* 函数功能:创建 socket
* 入口参数:domain - 地址族(仅 AF_INET)
* type - NET_SOCK_STREAM(TCP) 或 NET_SOCK_DGRAM(UDP)
* 出口参数:无
* 返回值:socket 描述符(0 ~ NET_MAX_SOCKETS-1),失败 -1
* 限定条件:Winsock 已就绪
* 函数说明:分配空闲控制块,创建 Winsock SOCKET 并置非阻塞
*/
int net_socket(int domain, int type) {
int idx;
int is_udp;
SOCKET ws;
u_long nb = 1;
(void)domain;
if (ensure_wsa() != 0) {
s_net_errno = NET_ERR;
return -1;
}
idx = alloc_fd();
if (idx < 0) {
s_net_errno = NET_ERR;
return -1;
}
is_udp = (type == NET_SOCK_DGRAM);
ws = socket(AF_INET, is_udp ? SOCK_DGRAM : SOCK_STREAM,
is_udp ? IPPROTO_UDP : IPPROTO_TCP);
if (ws == INVALID_SOCKET) {
s_net_errno = NET_ERR;
return -1;
}
if (ioctlsocket(ws, FIONBIO, &nb) != 0) {
closesocket(ws);
s_net_errno = NET_ERR;
return -1;
}
memset(&s_socks[idx], 0, sizeof(net_sock_t));
s_socks[idx].in_use = 1;
s_socks[idx].type = (uint8_t)type;
s_socks[idx].state = NET_SOCK_STATE_CREATED;
s_socks[idx].ch395_sock = (uint8_t)idx;
/* 阻塞模式默认 = 阻塞(与 MCU 端 net_socket 一致;
* Winsock 句柄仍恒非阻塞,阻塞语义由包装层按本属性模拟,见文件头要点 2) */
s_socks[idx].blocking = 1;
s_ws[idx] = ws;
s_net_errno = NET_OK;
return idx;
}
/*
* 函数功能:绑定本地地址和端口
* 入口参数:sockfd - socket 描述符
* addr - 本地地址(net_sockaddr,AF_INET)
* addrlen - 地址结构体长度
* 出口参数:无
* 返回值:0 成功,-1 失败
* 限定条件:socket 已创建
* 函数说明:设置 SO_REUSEADDR 后 bind,便于断线后重监听复用端口
*/
int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) {
struct sockaddr_in wsaddr;
int yes = 1;
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (addr == NULL || addrlen < (int)sizeof(struct sockaddr_in)) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
setsockopt(s_ws[sockfd], SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(yes));
to_ws_addr(addr, &wsaddr);
if (bind(s_ws[sockfd], (struct sockaddr *)&wsaddr, sizeof(wsaddr)) == SOCKET_ERROR) {
s_net_errno = NET_ERR;
return -1;
}
s_socks[sockfd].state = NET_SOCK_STATE_BOUND;
s_net_errno = NET_OK;
return 0;
}
/*
* 函数功能:TCP Server 监听
* 入口参数:sockfd - socket 描述符
* 出口参数:无
* 返回值:0 成功,-1 失败
* 限定条件:socket 已绑定
* 函数说明:backlog 为 1(单客户端),监听 Socket 保持非阻塞
*/
int net_listen(int sockfd) {
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (listen(s_ws[sockfd], NET_SIM_BACKLOG) == SOCKET_ERROR) {
s_net_errno = NET_ERR;
return -1;
}
s_socks[sockfd].state = NET_SOCK_STATE_LISTENING;
s_net_errno = NET_OK;
return 0;
}
/*
* 函数功能:TCP Server 接受连接
* 入口参数:sockfd - 监听 socket 描述符
* addr - 输出:客户端地址(可为 NULL)
* addrlen - 输入输出:地址长度(addr 非空时有效)
* 出口参数:addr/addrlen - 填充客户端地址
* 返回值:新 socket 描述符,失败 -1
* 限定条件:socket 正在监听
* 函数说明:非阻塞监听下无新连接时返回 -1 并置 NET_ERR_WOULDBLOCK;
* 与 MCU 端不同,Winsock 的 accept 返回独立于监听 Socket 的新 fd
*/
int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
struct sockaddr_in wscli;
int wslen;
SOCKET cws;
int nidx;
u_long nb = 1;
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
s_net_errno = NET_ERR_BADF;
return -1;
}
wslen = sizeof(wscli);
cws = accept(s_ws[sockfd], (addr != NULL) ? (struct sockaddr *)&wscli : NULL,
(addr != NULL) ? &wslen : NULL);
if (cws == INVALID_SOCKET) {
int err = WSAGetLastError();
s_net_errno = (err == WSAEWOULDBLOCK) ? NET_ERR_WOULDBLOCK : NET_ERR;
return -1;
}
if (addr != NULL && addrlen != NULL) {
from_ws_addr(&wscli, addr);
*addrlen = (int)sizeof(struct net_sockaddr_in);
}
nidx = alloc_fd();
if (nidx < 0) {
closesocket(cws);
s_net_errno = NET_ERR;
return -1;
}
ioctlsocket(cws, FIONBIO, &nb);
memset(&s_socks[nidx], 0, sizeof(net_sock_t));
s_socks[nidx].in_use = 1;
s_socks[nidx].type = (uint8_t)NET_SOCK_STREAM;
s_socks[nidx].state = NET_SOCK_STATE_ESTABLISHED;
s_socks[nidx].ch395_sock = (uint8_t)nidx;
/* accept 出的新连接同样默认阻塞模式(与 MCU 端一致) */
s_socks[nidx].blocking = 1;
s_ws[nidx] = cws;
s_net_errno = NET_OK;
return nidx;
}
/* F-NET-4:net_can_accept 双端退役——监听 accept 门控统一用
* net_listener_armed()(本文件同名实现保留恒 1 语义),recv 门控 net_can_read() 保留。 */
/*
* 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(与 MCU 端同名 API)
* 入口参数:sockfd - 监听 socket 描述符
* 返回值:1 - 可尝试 accept;0 - 描述符无效
* 限定条件:socket 已创建
* 函数说明:Winsock accept 本无 RPC 成本 → 恒返回 1,
* 使上层门控在模拟器上等价于"改前行为"。
* ⚠ MCU 端该函数额外放行 CLOSED+auto_relisten(单连接复用的断连待重建态),
* 那是芯片 fd 别名模型的产物;Winsock 后端 fd 与监听器分离
* (设计文档 D6:双模型分裂暂不消除),无此中间态,恒 1 即为正确语义。
*/
int net_listener_armed(int sockfd) {
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
return 0;
}
return 1;
}
/*
* 函数功能:查询 Socket 是否"值得发一次 recv"(与 MCU 端同名 API)
* 入口参数:sockfd - socket 描述符
* 返回值:1 - 可尝试 recv;0 - 描述符无效
* 限定条件:socket 已创建
* 函数说明:同 net_listener_armed 的双端口径——Winsock recv 非阻塞、无 RPC 成本 → 恒返回 1,
* 上层门控在模拟器上不改变既有行为(含"每轮都 recv"的自愈特性)
*/
int net_can_read(int sockfd) {
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
return 0;
}
return 1;
}
/*
* 函数功能:TCP Client 发起连接
* 入口参数:sockfd - socket 描述符
* addr - 服务器地址
* addrlen - 地址长度
* 出口参数:无
* 返回值:0 成功(或已发起),-1 失败
* 限定条件:socket 已创建
* 函数说明:非阻塞 Socket 上连接未完成时返回 -1 + NET_ERR_WOULDBLOCK
*/
int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) {
struct sockaddr_in wsaddr;
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (addr == NULL || addrlen < (int)sizeof(struct sockaddr_in)) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
to_ws_addr(addr, &wsaddr);
if (connect(s_ws[sockfd], (struct sockaddr *)&wsaddr, sizeof(wsaddr)) == 0) {
s_socks[sockfd].state = NET_SOCK_STATE_ESTABLISHED;
s_net_errno = NET_OK;
return 0;
}
{
int err = WSAGetLastError();
s_net_errno = (err == WSAEWOULDBLOCK) ? NET_ERR_WOULDBLOCK : NET_ERR;
}
return -1;
}
/*
* 函数功能:发送数据(TCP)
* 入口参数:sockfd - socket 描述符
* buf - 数据缓冲区
* len - 数据长度
* flags - 通常为 0
* 出口参数:无
* 返回值:实际发送字节数,-1 错误;发送缓冲满时返回 0 + NET_ERR_WOULDBLOCK
* 限定条件:socket 已建立,buf 非空
* 函数说明:非阻塞单次发送(保持 UI 主循环不阻塞);协议帧小且对端主动
* 读取,实践中一次即发完
*/
int net_send(int sockfd, const void *buf, int len, int flags) {
int r;
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (buf == NULL || len < 0) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
r = send(s_ws[sockfd], (const char *)buf, len, flags);
if (r >= 0) {
s_socks[sockfd].send_bytes += (uint32_t)r;
s_net_errno = NET_OK;
return r;
}
{
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK) {
s_net_errno = NET_ERR_WOULDBLOCK;
return 0;
}
s_net_errno = NET_ERR;
}
return -1;
}
/*
* 函数功能:单次非阻塞 recv(内部公共体:立即返回,不等待)
* 入口参数:sockfd - socket 描述符(已校验)
* buf - 接收缓冲区(已校验)
* len - 缓冲区大小
* 出口参数:buf - 填充接收数据
* 返回值:>0 字节数;0 = 对端关闭;-1 = 无数据(WOULDBLOCK) 或错误
* 限定条件:Winsock 句柄为非阻塞(FIONBIO)
* 函数说明:net_recv 的非阻塞路径与 net_recv_nb 共用;错误码映射与旧实现一致
*/
static int recv_once(int sockfd, void *buf, int len) {
int r = recv(s_ws[sockfd], (char *)buf, len, 0);
if (r > 0) {
s_socks[sockfd].recv_bytes += (uint32_t)r;
s_net_errno = NET_OK;
return r;
}
if (r == 0) {
s_net_errno = NET_OK;
return 0;
}
{
int err = WSAGetLastError();
s_net_errno = (err == WSAEWOULDBLOCK) ? NET_ERR_WOULDBLOCK : NET_ERR;
}
return -1;
}
/*
* 函数功能:阻塞语义模拟(内部公共体:轮询等待数据/EOF/错误)
* 入口参数:sockfd - socket 描述符(已校验)
* buf / len - 接收缓冲与容量(已校验)
* has_budget - 1 = 有预算(net_recv_to);0 = 无限等待(net_recv 阻塞)
* budget_ms - 预算毫秒(has_budget=0 时忽略)
* 出口参数:buf - 填充接收数据
* 返回值:>0 字节数;0 = 对端关闭;-1 = 错误;-2 = 预算耗尽无数据(仅 has_budget)
* 限定条件:Winsock 句柄为非阻塞;调用方为 UI/服务线程(Sleep(1) 粒度可接受)
* 函数说明:与 MCU 端挂起 recv 的对外契约对齐——MCU 端等待发生在调用方任务
* (睡任务通知),模拟器端等价地睡 Sleep(1) 轮询;EOF/错误立即返回
*/
static int recv_wait(int sockfd, void *buf, int len, int has_budget, unsigned long long budget_ms) {
unsigned long long t0 = GetTickCount64();
for (;;) {
int r = recv_once(sockfd, buf, len);
if (r != -1 || s_net_errno != NET_ERR_WOULDBLOCK) {
return r; /* 数据 / EOF / 真错误:立即返回 */
}
if (has_budget && (GetTickCount64() - t0) >= budget_ms) {
s_net_errno = NET_ERR_TIMEDOUT;
return -2; /* 预算耗尽(对齐 MCU net_recv_to 的 -2) */
}
Sleep(1);
}
}
/*
* 函数功能:接收数据(TCP,**默认无限阻塞**——与 MCU 端语义对齐)
* 入口参数:sockfd - socket 描述符
* buf - 接收缓冲区
* len - 缓冲区大小
* flags - 通常为 0;NET_MSG_DONTWAIT 单次强制非阻塞
* 出口参数:buf - 填充接收数据
* 返回值:实际接收字节数;0 = 对端关闭;-1 = 无数据(NET_ERR_WOULDBLOCK,仅非阻塞)或错误
* 限定条件:socket 已建立
* 函数说明:与 MCU 端语义一致——blocking 属性(net_setblocking)决定无数据时
* 无限等待还是立即 WOULDBLOCK;DONTWAIT 单次覆盖属性
*/
int net_recv(int sockfd, void *buf, int len, int flags) {
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (buf == NULL || len < 0) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
/* 非阻塞:DONTWAIT 单次覆盖,或 socket 的 blocking 属性为 0 */
if ((flags & NET_MSG_DONTWAIT) || s_socks[sockfd].blocking == 0U) {
return recv_once(sockfd, buf, len);
}
/* 阻塞:无限等待(无默认预算) */
return recv_wait(sockfd, buf, len, 0, 0ULL);
}
/*
* 函数功能:接收数据(非阻塞具名入口,与 MCU 端同名 API 对齐)
* 入口参数:sockfd / buf / len - 同 net_recv
* 返回值:>0 字节数;0 = 对端关闭;-1 = 无数据(WOULDBLOCK) 或错误
* 限定条件:socket 已建立
* 函数说明:等价 net_recv(sockfd, buf, len, NET_MSG_DONTWAIT),不改变 blocking 属性
*/
int net_recv_nb(int sockfd, void *buf, int len) {
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (buf == NULL || len < 0) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
return recv_once(sockfd, buf, len);
}
/*
* 函数功能:接收数据(**显式预算**便捷入口,与 MCU 端同名 API 对齐)
* 入口参数:sockfd / buf / len - 同 net_recv
* timeout_ms - 本次等待预算(ms,必须 >0;0 = 无限等待语义
* 属 net_recv 的职责,此处拒 INVAL,与 MCU 端一致)
* 返回值:>0 字节数;0 = 对端关闭;-1 = 真错误;-2 = 预算耗尽无数据
* 限定条件:socket 已建立
* 函数说明:模拟器以 GetTickCount64 + Sleep(1) 轮询兑现预算
*/
int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms) {
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (buf == NULL || len < 0) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
if (timeout_ms == 0U) {
/* 0 = 无限等待(net_recv 的语义),与本接口"有界"存在理由相反 → 拒绝 */
s_net_errno = NET_ERR_INVAL;
return -1;
}
return recv_wait(sockfd, buf, len, 1, (unsigned long long)timeout_ms);
}
/*
* 函数功能:发送 UDP 数据报(含目标地址)
* 入口参数:sockfd - socket 描述符
* buf - 数据缓冲区
* len - 数据长度
* flags - 通常为 0
* dest_addr - 目标地址
* addrlen - 地址长度
* 出口参数:无
* 返回值:实际发送字节数,-1 失败(精确码见 net_get_errno)
* 限定条件:socket 已创建,buf/dest_addr 非空
* 函数说明:⚠ **MCU 端本接口另有上下文约束**:它不经消息队列、在调用方上下文里
* 直接 net_poll + 写芯片缓冲,故 MCU 侧只允许 net_init 的执行任务调用,
* 其他任务调用回 NET_ERR_NOT_IN_NETTASK(2026-10-24 定案,理由与
* "为何不是改成走队列"见 net_socket.h 同名声明处)。
* 本后端刻意**不加**同样的守卫:模拟器是单线程 + Winsock(自身线程安全),
* 没有"与 net_poll 抢同一根 SPI"这条危险面,也没有 RTOS 任务句柄可比。
* 所以这是**有意的不对称**,不是漏实现;若将来模拟器引入多线程,
* 必须连同这条约束一起补上(按线程 ID 比 net_init 的调用线程)。
*/
int net_sendto(int sockfd, const void *buf, int len, int flags,
const struct net_sockaddr *dest_addr, int addrlen) {
struct sockaddr_in wsaddr;
int r;
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (buf == NULL || len < 0 || dest_addr == NULL ||
addrlen < (int)sizeof(struct sockaddr_in)) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
to_ws_addr(dest_addr, &wsaddr);
r = sendto(s_ws[sockfd], (const char *)buf, len, flags,
(struct sockaddr *)&wsaddr, sizeof(wsaddr));
if (r >= 0) {
s_net_errno = NET_OK;
return r;
}
{
int err = WSAGetLastError();
s_net_errno = (err == WSAEWOULDBLOCK) ? NET_ERR_WOULDBLOCK : NET_ERR;
}
return -1;
}
/*
* 函数功能:接收 UDP 数据报(含源地址)
* 入口参数:sockfd - socket 描述符
* buf - 接收缓冲区
* len - 缓冲区大小
* flags - 通常为 0
* src_addr - 输出:发送方地址(可为 NULL)
* addrlen - 输入输出:源地址长度(可为 NULL)
* 出口参数:buf/src_addr/addrlen - 填充
* 返回值:实际接收字节数,-1 失败(精确码见 net_get_errno)
* 限定条件:socket 已创建,buf 非空
* 函数说明:MCU 端有"仅限执行任务调用"的上下文守卫(NET_ERR_NOT_IN_NETTASK),
* 本后端按 net_sendto 处同一条理由不加——有意的不对称,勿当成漏实现
*/
int net_recvfrom(int sockfd, void *buf, int len, int flags,
struct net_sockaddr *src_addr, int *addrlen) {
struct sockaddr_in wsaddr;
int wslen;
int r;
(void)flags;
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (buf == NULL || len < 0) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
wslen = sizeof(wsaddr);
r = recvfrom(s_ws[sockfd], (char *)buf, len, 0,
(struct sockaddr *)&wsaddr, &wslen);
if (r < 0) {
int err = WSAGetLastError();
s_net_errno = (err == WSAEWOULDBLOCK) ? NET_ERR_WOULDBLOCK : NET_ERR;
return -1;
}
if (src_addr != NULL && addrlen != NULL) {
from_ws_addr(&wsaddr, src_addr);
*addrlen = (int)sizeof(struct net_sockaddr_in);
}
s_net_errno = NET_OK;
return r;
}
/*
* 函数功能:关闭 socket
* 入口参数:sockfd - socket 描述符
* 出口参数:无
* 返回值:0 成功,-1 失败
* 限定条件:socket 已打开(in_use == 1)
* 函数说明:关闭 Winsock 句柄并释放控制块(in_use 清零,net_get_sock 转返回 NULL)
*/
int net_close(int sockfd) {
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (s_socks[sockfd].in_use == 0) {
s_net_errno = NET_ERR_BADF;
return -1;
}
closesocket(s_ws[sockfd]);
s_socks[sockfd].in_use = 0;
s_socks[sockfd].state = NET_SOCK_STATE_CLOSED;
s_ws[sockfd] = INVALID_SOCKET;
s_net_errno = NET_OK;
return 0;
}
/*
* 函数功能:主动拆掉当前 TCP 会话、保留监听(与 MCU 端同名 API——E-02 收口)
* 入口参数:sockfd - socket 描述符
* 出口参数:无
* 返回值:0 成功(含幂等:无活动会话),-1 失败
* 限定条件:socket 已打开(in_use == 1)
* 函数说明:模拟器为多连接后端(conn != 监听 fd):等价关闭该数据连接的
* Winsock 句柄并收口 CLOSED(fd 槽不释放,外层 net_close 收尾);
* 监听 fd 不受影响。无活动会话幂等返 0。
*/
int net_drop_conn(int sockfd) {
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (s_socks[sockfd].in_use == 0) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (s_socks[sockfd].state != NET_SOCK_STATE_ESTABLISHED &&
s_socks[sockfd].state != NET_SOCK_STATE_CONNECTING) {
/* 无活动会话:幂等成功 */
s_net_errno = NET_OK;
return 0;
}
closesocket(s_ws[sockfd]);
s_ws[sockfd] = INVALID_SOCKET;
s_socks[sockfd].state = NET_SOCK_STATE_CLOSED;
s_net_errno = NET_OK;
return 0;
}
/*
* 函数功能:注册事件回调
* 入口参数:sockfd - socket 描述符
* cb - 回调函数(NULL 取消注册)
* arg - 用户参数
* 出口参数:无
* 返回值:0 成功,-1 失败
* 限定条件:socket 已创建
* 函数说明:模拟器以非阻塞 I/O 直接感知事件,回调仅存储不主动触发
*/
int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) {
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
s_net_errno = NET_ERR_BADF;
return -1;
}
s_socks[sockfd].event_cb = cb;
s_socks[sockfd].event_arg = arg;
s_net_errno = NET_OK;
return 0;
}
/*
* 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(与 MCU 端同名 API 对齐)
* 入口参数:sockfd - socket 描述符
* on - 1 = 阻塞(net_socket/net_accept 默认);0 = 非阻塞
* 返回值:0 成功;-1 失败(描述符无效/未占用)
* 限定条件:socket 已创建
* 函数说明:Winsock 句柄恒非阻塞(FIONBIO,UI 主循环安全底线),本属性只驱动
* 包装层的等待策略(net_recv 无限轮询 / 立即 WOULDBLOCK),
* 语义与 MCU 端"模式挂控制块属性"一致
*/
int net_setblocking(int sockfd, int on) {
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
s_net_errno = NET_ERR_BADF;
return -1;
}
s_socks[sockfd].blocking = (on != 0) ? 1U : 0U;
s_net_errno = NET_OK;
return 0;
}
/*
* 函数功能:获取 socket 最后错误码
* 入口参数:无
* 返回值:最近一次 net_* 操作设置的错误码(NET_OK / NET_ERR_xxx)
* 限定条件:无
*/
int net_get_errno(void) {
return s_net_errno;
}
/*
* 函数功能:将点分十进制 IP 字符串转为网络字节序 32 位地址
* 入口参数:cp - 点分十进制字符串(如 "192.168.1.100")
* 出口参数:无
* 返回值:网络字节序 IP(参数无效返回 0)
* 限定条件:无
* 函数说明:与 MCU 端 net_inet_addr **逐字同语义**(2026-10-24:两套 IP 字节序约定统一为 POSIX 网络序):
* 按「第一段在主机序最高字节」累加,再经 NET_HTONL 转网络序,结果满足
* POSIX/Winsock 的 in_addr 不变量——&s_addr 的第 0 个内存字节 == 第一段。
* 本模拟器的 to_ws_addr()/from_ws_addr() 是把 net_sockaddr_in **整块 memcpy**
* 进 sockaddr_in 的,所以只有两边同族时才成立:旧实现(小端累加后再换端)
* 交给 Winsock 的是反序地址,只有从不走 inet_addr 的路径(bind ANY、
* getpeername)才没暴露。改完后本函数与 net_inet_ntoa 互逆、与 Winsock 直拷兼容。
* 段值超 255 按低 8 位截断;失败返回 0(不引入 NET_INADDR_NONE 判定)。
*/
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 + (uint32_t)(*cp - '0');
cp++;
}
/* 高位先入:第 0 段左移三次后落在最高字节 */
addr = (addr << 8) | (val & 0xFFU);
if (*cp == '.') {
cp++;
}
}
return NET_HTONL(addr);
}
/*
* 函数功能:将网络字节序 32 位 IP 转为点分十进制字符串
* 入口参数:addr - 网络字节序 IP
* buf - 输出缓冲区(至少 16 字节)
* 出口参数:buf - 填充字符串
* 返回值:buf 指针(buf 为 NULL 时返回 NULL)
* 限定条件:buf 非空且长度足够
* 函数说明:NTOHL 后主机序数值的高字节是点分第一段(与 MCU 端同步修复,
* E13 定案:原低段在前显示成字节反转)
*/
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 >> 24) & 0xFF),
(unsigned long)((naddr >> 16) & 0xFF),
(unsigned long)((naddr >> 8) & 0xFF),
(unsigned long)(naddr & 0xFF));
return buf;
}
/*
* 函数功能:端口字节序转换(主机序 -> 网络序)
* 入口参数:hostshort - 主机序端口
* 返回值:网络序端口
* 限定条件:无
*/
uint16_t net_htons(uint16_t hostshort) {
return NET_HTONS(hostshort);
}
/*
* 函数功能:端口字节序转换(网络序 -> 主机序)
* 入口参数:netshort - 网络序端口
* 返回值:主机序端口
* 限定条件:无
*/
uint16_t net_ntohs(uint16_t netshort) {
return NET_NTOHS(netshort);
}
/*
* 函数功能:IP 地址字节序转换(主机序 -> 网络序)
* 入口参数:hostlong - 主机序 IP
* 返回值:网络序 IP
* 限定条件:无
*/
uint32_t net_htonl(uint32_t hostlong) {
return NET_HTONL(hostlong);
}
/*
* 函数功能:IP 地址字节序转换(网络序 -> 主机序)
* 入口参数:netlong - 网络序 IP
* 返回值:主机序 IP
* 限定条件:无
*/
uint32_t net_ntohl(uint32_t netlong) {
return NET_NTOHL(netlong);
}
/*
* 函数功能:获取 Socket 控制块指针
* 入口参数:sockfd - socket 描述符
* 返回值:控制块指针;描述符越界或已释放(in_use == 0)时返回 NULL
* 限定条件:无
* 函数说明:上层据此判断"监听 Socket 是否随客户端一同失效"(单连接模型)
*/
net_sock_t *net_get_sock(int sockfd) {
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
return NULL;
}
if (s_socks[sockfd].in_use == 0) {
return NULL;
}
return &s_socks[sockfd];
}
/*
* 函数功能:获取 Socket 本地地址(IP + Port)
* 入口参数:sockfd - socket 描述符
* addr - 输出:本地地址
* addrlen - 输入输出:地址结构体长度
* 出口参数:addr/addrlen - 填充
* 返回值:0 成功,-1 失败
* 限定条件:socket 已创建
* 函数说明:经 Winsock getsockname 读取实际绑定地址
*/
int net_getsockname(int sockfd, struct net_sockaddr *addr, int *addrlen) {
struct sockaddr_in wsaddr;
int wslen;
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
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;
}
wslen = sizeof(wsaddr);
if (getsockname(s_ws[sockfd], (struct sockaddr *)&wsaddr, &wslen) == SOCKET_ERROR) {
s_net_errno = NET_ERR;
return -1;
}
from_ws_addr(&wsaddr, addr);
*addrlen = (int)sizeof(struct net_sockaddr_in);
s_net_errno = NET_OK;
return 0;
}
/*
* 函数功能:获取 Socket 对端地址(IP + Port)(与 MCU 端同名 API——E13 定案)
* 入口参数:sockfd - socket 描述符
* addr - 输出:对端地址
* addrlen - 输入输出:地址结构体长度
* 出口参数:addr/addrlen - 填充
* 返回值:0 成功,-1 失败(未连接 NOTCONN)
* 限定条件:socket 已连接
* 函数说明:经 Winsock getpeername 读取;模拟器多连接后端下 conn fd 有真实对端
*/
int net_getpeername(int sockfd, struct net_sockaddr *addr, int *addrlen) {
struct sockaddr_in wsaddr;
int wslen;
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
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;
}
wslen = sizeof(wsaddr);
if (getpeername(s_ws[sockfd], (struct sockaddr *)&wsaddr, &wslen) == SOCKET_ERROR) {
s_net_errno = NET_ERR_NOTCONN;
return -1;
}
from_ws_addr(&wsaddr, addr);
*addrlen = (int)sizeof(struct net_sockaddr_in);
s_net_errno = NET_OK;
return 0;
}