/* * 模块名称:Network Select Implementation * 模块功能:I/O 多路复用实现,包括 select 和 poll 两种模式,用于同时监控多个 Socket 的读写事件 * 适用平台:STM32F407ZGTx + CH395F 以太网芯片 * 作者:王建锋 * 创建日期:2026-07-18 * 修改记录: * 2026-07-18 王建锋 创建初始版本 */ #include #include #include "cmsis_os.h" /* CMSIS-RTOS2 API: osDelay() */ #include "FreeRTOS.h" #include "task.h" #include "net_select.h" #include "net_socket.h" #include "ch395f.h" #include "main.h" /* * 私有函数声明区 */ static int check_socket_readable(int sockfd); static int check_socket_writable(int sockfd); static int check_socket_error(int sockfd); /* * 公共函数实现区 */ /* * 函数功能:I/O 多路复用(select 模式),等待多个 Socket 的读写事件就绪 * 入口参数:nfds - 最大文件描述符数 +1 int > 0 * readfds - 读事件集合指针 net_fd_set_t *(可为 NULL) * writefds - 写事件集合指针 net_fd_set_t *(可为 NULL) * exceptfds- 异常事件集合指针(表项不存在的 fd 计入异常并计入返回计数; * timeout 传 {0,0} 表示"只查一轮立即返回") net_fd_set_t *(可为 NULL) * timeout - 超时时间结构体指针 net_timeval_t *(NULL=无限等待) * 出口参数:readfds/writefds/exceptfds - 仅保留就绪的 Socket 位 net_fd_set_t * * 返回值:就绪的 Socket 数量(>= 0);-1 表示参数无效 int * 限定条件:net_init() + MX_FREERTOS_Init() 已执行;可在任意任务上下文调用 * 函数说明:1. 轮询 Socket 控制块(RAM)中的读/写/异常状态,判断事件是否就绪 * 2. 不在此直接访问 CH395F SPI——所有 ch395f 事务统一由 netTask 的 net_poll() * 每 10ms 串行执行并刷新控制块状态(send_ready / readable),本函数仅做 * RAM 读取,避免与应用任务发起的 SPI 访问竞争(见 TRAP-NET-14) * 3. 阻塞等待直到有事件就绪或超时 * 4. 通过 osDelay(1) 让出 CPU,平衡实时性与 CPU 利用率(状态刷新延迟 <=10ms) */ int net_select(int nfds, net_fd_set_t *readfds, net_fd_set_t *writefds, net_fd_set_t *exceptfds, net_timeval_t *timeout) { uint32_t start_tick; uint32_t timeout_ms = 0; net_fd_set_t result_read = {0}; net_fd_set_t result_write = {0}; net_fd_set_t result_except = {0}; int ready_count = 0; int i; /* 检查输入参数合法性 */ if ((nfds <= 0) || (nfds > NET_MAX_SOCKETS)) { return -1; } /* 计算超时时间(毫秒) */ if (timeout != NULL) { timeout_ms = (uint32_t)(timeout->tv_sec * 1000) + (uint32_t)(timeout->tv_usec / 1000); } start_tick = HAL_GetTick(); while (1) { /* 仅读取 netTask 的 net_poll() 已刷新的控制块状态(无 CH395F SPI 访问) */ ready_count = 0; /* 检查哪个 Socket 有事件 */ for (i = 0; i < nfds && i < NET_MAX_SOCKETS; i++) { const net_sock_t *p_sock = net_get_sock(i); if (p_sock == NULL) { /* 表项不存在就是异常,必须在 continue **之前**判:原实现先跳过,而 * check_socket_error() 只有"表项为空 / fd 越界"才返回 1,越界的 i * 又不在本循环范围内 → exceptfds 永远是空的死参数(调用方按 POSIX * 直觉用它,就永远什么都收不到)。2026-10-23 修掉。 */ if ((exceptfds != NULL) && NET_FD_ISSET(i, exceptfds)) { NET_FD_SET(i, &result_except); ready_count++; } continue; } /* 检查可读事件 */ if ((readfds != NULL) && NET_FD_ISSET(i, readfds)) { if (check_socket_readable(i)) { NET_FD_SET(i, &result_read); ready_count++; } } /* 检查可写事件 */ if ((writefds != NULL) && NET_FD_ISSET(i, writefds)) { if (check_socket_writable(i)) { NET_FD_SET(i, &result_write); ready_count++; } } /* 检查异常事件 */ if ((exceptfds != NULL) && NET_FD_ISSET(i, exceptfds)) { if (check_socket_error(i)) { NET_FD_SET(i, &result_except); ready_count++; } } } /* 有事件就绪,返回 */ if (ready_count > 0) { break; } /* 超时检测 */ if (timeout != NULL) { /* 不能再加 (timeout_ms > 0) 前置:timeout = {0, 0} 换算出 0,那是 * POSIX 的"轮询一次立即返回";加上它 = 超时永不生效,调用方(netTask * 在 IWDG required_mask 里)再也回不到主循环。就绪检查在本判断之前, * 所以有就绪时依旧立刻带计数返回,2026-10-23 由主机单测暴露并修掉。 */ if ((HAL_GetTick() - start_tick) >= timeout_ms) { break; /* 超时返回 0(timeout_ms == 0 即"只查一轮") */ } } /* 让出 CPU:FreeRTOS osDelay() 会正确进入休眠,允许其他任务运行 */ osDelay(1); /* 1ms 周期轮询,平衡实时性和 CPU 利用率 */ } /* 复制结果集合 */ if (readfds != NULL) { readfds->fd_bits = result_read.fd_bits; } if (writefds != NULL) { writefds->fd_bits = result_write.fd_bits; } if (exceptfds != NULL) { exceptfds->fd_bits = result_except.fd_bits; } return ready_count; } /* * 函数功能:I/O 多路复用(poll 模式),等待多个 Socket 的读写事件就绪 * 入口参数:fds - pollfd 数组指针 net_pollfd_t * * nfds - 数组元素数量 int > 0 * timeout - 超时时间(毫秒) int >= 0(-1=无限等待,但本实现按 >=0 处理) * 出口参数:fds[i].revents - 填充实际发生的事件掩码 short * 返回值:就绪的 fd 数量(>= 0);-1 表示参数无效 int * 限定条件:net_init() + MX_FREERTOS_Init() 已执行;可在任意任务上下文调用 * 函数说明:1. 轮询 Socket 控制块(RAM)中的读/写/错误/挂起状态,判断事件是否就绪 * 2. 不在此直接访问 CH395F SPI——状态由 netTask 的 net_poll() 每 10ms 刷新 * 3. 阻塞等待直到有事件就绪或超时 * 4. 通过 osDelay(1) 让出 CPU,平衡实时性与 CPU 利用率(状态刷新延迟 <=10ms) */ int net_poll_events(net_pollfd_t *fds, int nfds, int timeout) { uint32_t start_tick; int ready = 0; int i; if ((fds == NULL) || (nfds <= 0)) { return -1; } start_tick = HAL_GetTick(); while (1) { /* 仅读取 netTask 的 net_poll() 已刷新的控制块状态(无 CH395F SPI 访问) */ ready = 0; for (i = 0; i < nfds; i++) { fds[i].revents = 0; /* 检查 fd 有效性 */ if ((fds[i].fd < 0) || (fds[i].fd >= NET_MAX_SOCKETS)) { fds[i].revents |= NET_POLLNVAL; ready++; continue; } const net_sock_t *p_sock = net_get_sock(fds[i].fd); if (p_sock == NULL) { fds[i].revents |= NET_POLLNVAL; ready++; continue; } /* 检查可读 */ if (fds[i].events & NET_POLLIN) { if (check_socket_readable(fds[i].fd)) { fds[i].revents |= NET_POLLIN; ready++; } } /* 检查可写 */ if (fds[i].events & NET_POLLOUT) { if (check_socket_writable(fds[i].fd)) { fds[i].revents |= NET_POLLOUT; ready++; } } /* 检查错误 */ if (check_socket_error(fds[i].fd)) { fds[i].revents |= NET_POLLERR; ready++; } /* 检查挂起(连接断开) */ if (NET_SOCK_STATE_CLOSED == p_sock->state) { fds[i].revents |= NET_POLLHUP; ready++; } } if (ready > 0) { break; } /* 超时检测 */ if (timeout >= 0) { if ((HAL_GetTick() - start_tick) >= (uint32_t)timeout) { break; } } /* 让出 CPU:FreeRTOS osDelay() 会正确进入休眠,允许其他任务运行 */ osDelay(1); /* 1ms 周期轮询,平衡实时性和 CPU 利用率 */ } return ready; } /* * 私有函数实现区 */ /* * 函数功能:检查指定 Socket 当前是否可读 * 入口参数:sockfd - 待检查的 Socket 描述符 int 0 - NET_MAX_SOCKETS-1 * 出口参数:无 * 返回值:1 - 可读(接收缓冲非空);0 - 不可读或描述符无效 int * 限定条件:net_init() 已调用 * 函数说明:1.(2026-10-04):LISTENING 态**不再**恒判可读。本网络层无 * "待 accept 的挂起连接队列"——新连接到达时 CONNECT 事件把该 socket * 自身直接转为 ESTABLISHED(handle_connect_event:"监听自身转数据通道"), * 故监听态恒无数据可读。原实现对 LISTENING 恒返回 1,会使 * select()/poll() 在监听 socket 上永久立即就绪(忙轮询地雷;当前无 * 调用方,属潜在地雷)。连接到达经 NET_EVENT_CONNECTED 回调通知。 * 2. 其余态:readable 由 net_poll 在 RECV_OK 时置位、net_recv_locked * 读空后清零。避免应用任务直接访问 CH395F SPI(见 TRAP-NET-14) */ static int check_socket_readable(int sockfd) { const net_sock_t *p_sock; p_sock = net_get_sock(sockfd); if (p_sock == NULL) { return 0; } /* 监听态恒不可读(见上方说明) */ if (NET_SOCK_STATE_LISTENING == p_sock->state) { return 0; } /* 有数据可读 */ if (p_sock->readable) { return 1; } return 0; } /* * 函数功能:检查指定 Socket 当前是否可写 * 入口参数:sockfd - 待检查的 Socket 描述符 int 0 - NET_MAX_SOCKETS-1 * 出口参数:无 * 返回值:1 - 可写(已连接且发送缓冲空闲);0 - 不可写或描述符无效 int * 限定条件:net_init() 已调用 * 函数说明:1. 仅已建立连接且 send_ready 标志置位的 Socket 视为可写 */ static int check_socket_writable(int sockfd) { const net_sock_t *p_sock; p_sock = net_get_sock(sockfd); if (p_sock == NULL) { return 0; } /* 已建立连接的 Socket 发送缓冲区空闲时可写 */ if (NET_SOCK_STATE_ESTABLISHED == p_sock->state) { if (p_sock->send_ready) { return 1; } } return 0; } /* * 函数功能:检查指定 Socket 是否存在错误状态 * 入口参数:sockfd - 待检查的 Socket 描述符 int 0 - NET_MAX_SOCKETS-1 * 出口参数:无 * 返回值:1 - 有错误(含描述符越界或 Socket 未分配);0 - 无错误 int * 限定条件:无 * 函数说明:1. 描述符越界视为错误 * 2. 未分配(net_get_sock 返回 NULL)的 Socket 视为错误 */ static int check_socket_error(int sockfd) { const net_sock_t *p_sock; /* 无效 fd 视为错误 */ if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { return 1; } p_sock = net_get_sock(sockfd); if (p_sock == NULL) { return 1; /* 未使用的 Socket 视为错误 */ } return 0; }