Files
STM32F4-Base/simulator/sim/net_socket_sim.c
T
wandering1 b7c0889cc7 refactor(编号·批次二): 代码与测试侧 215 处保护功能号改成功能名,零行为改动
用户裁定「不要用字母加数字的编号,语义标识是最好的」后的批次二代码半(道 ui-param 执行,逐文件站点数与
自证见其交付)。唯一口径 = 映射表 ci-artifacts/lprot_map.tsv 的功能名列原文,**零自造同义词**;L2.5 与
L-TEST 经实测各 0 处(不是本族),TC 族一字未动。

固件 22 文件 128 处(adc_task.c 21、phasor.c 22、phasor.h 13…),主机测试 13 文件 87 处(pha
sor_test.c
57…);设计文档指针新增 19 行,另 16 只文件本来就写着规划文档 ⇒ 不重复加。

**两条只有真改过 215 处才会踩到的坑,都写进注释规范实践**:
· 指针**不能接在原句尾部**——那会替原文一个未闭合的半括号封口,实测咬坏 App/algo/sample_ring.h 这类
  跨行句 ⇒ 指针一律**单独成行**;
· 号删掉会留下**叠词**(「自检失败闭锁 闭锁判定」「定值校验和 定值巡检」)⇒ 复扫并收口 4 处,措辞只用
  映射名本身;具体对象需要留住时写成「自检失败闭锁(采样故障闭锁)」。

自证(不靠嘴说):改前把 35 只文件原文备份到仓外 temp,逐文件比 `/*`、`*/` 计数、三类括号计数、行数增量
恰等于指针行数、全角括号增删相等、残留号 0 ⇒ **35/35 通过**;Lead 复核后跑权威 @build **0 错 0 警**,
Program Size `Code=233900 RO=26104 RW=1536 ZI=136808` —— **与三条道各自改前改后测的四数逐
字节同数**,
这就是「只动注释与文本」的机器证据;八套主机单测 exit 0,cppcheck 按钩子参数零发现,coding-standard 0。

文件头「修改记录」里的号**一并换名、不还原**(Lead 裁定):这些号是全仓退役的,留一个查无此号的引用比
留一条改过名的条目更坏——「冻结历史不回改」保护的是**别替历史改口**,不是保护一个再也查不到的字符串;
id-check 对变更记录行本就豁免,两种选择都不影响门禁。
2026-10-27 08:59:26 +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;
}
/* accept 门控迁移(docs/网络/网络自愈架构设计.md §4.2):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——头阻塞收口)
* 入口参数: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;
}