/* * Windows网络后端:所有Winsock类型、错误码和系统计时依赖集中在本文件。 * 实现IEC104_Net.h约定的同步非阻塞操作;上层无需包含Windows系统头文件。 * 不保存连接、不执行协议回调、不实现收发循环:这些调度职责由TcpServer承担。 * 本文件只在Windows目标上编译,最终链接时需加入Winsock库ws2_32。 */ #ifndef _WIN32 #error "Select a network backend for this platform; IEC104_Net_Windows requires Windows" #endif #ifndef _WIN32_WINNT /* 为InetPtonA/InetNtopA和GetTickCount64启用至少Windows Vista级别的系统接口声明。 */ #define _WIN32_WINNT 0x0600 #endif #ifndef WIN32_LEAN_AND_MEAN /* 缩减windows.h引入的内容,配合先包含winsock2.h,避免旧版Winsock头文件冲突。 */ #define WIN32_LEAN_AND_MEAN #endif #include "IEC104_Net.h" #include #include #include #include #include /* Windows SOCKET是指针宽度的数值句柄;编译时检查跨层转换不会丢失句柄位。 */ _Static_assert(sizeof(IEC104_NetSocket_t) >= sizeof(SOCKET), "Network handle must hold a SOCKET"); /** * 功能:返回通用状态,并按需写出已经取得的平台错误码。 * 参数:result为调度状态,error为诊断码,native_error可为NULL。 * 说明:本函数不调用WSAGetLastError;调用方应先保存原始错误,再执行资源清理。 */ static IEC104_NetResult_t Result_With_Error( IEC104_NetResult_t result, int error, int *native_error) { if (native_error != NULL) { *native_error = error; } return result; } /** * 功能:将常见Winsock失败原因转换为通用操作状态。 * 返回值:暂不可读写映射为WOULD_BLOCK,操作被中断映射为RETRY,其余映射为ERROR。 * 说明:只转换状态,不丢弃原始错误码;Accept特有的客户端失效及资源暂缺另行处理。 */ static IEC104_NetResult_t Socket_Error_Result(int error) { if (error == WSAEWOULDBLOCK) { return IEC104_NET_WOULD_BLOCK; } return error == WSAEINTR ? IEC104_NET_RETRY : IEC104_NET_ERROR; } /* 只用于接入路径;收发及监听初始化仍保留各自原有的错误分类。 */ static int Accept_Resource_Error(int error) { return error == WSAENOBUFS || error == WSAEMFILE; } /* 构造一次收发结果;这里只打包参数,字节数与状态的一致性由调用位置保证。 */ static IEC104_NetIoResult_t Io_Result(IEC104_NetResult_t result, size_t transferred, int error) { IEC104_NetIoResult_t output; output.result = result; output.transferred = transferred; output.native_error = error; return output; } /** * 功能:把已经创建的Windows socket切换到非阻塞模式。 * 返回值:非0表示成功,0表示失败;失败后调用方须立即取得WSAGetLastError。 * 说明:FIONBIO取1只改变I/O模式,本函数不会关闭句柄,也不会创建后台任务。 */ static int Set_Nonblocking(SOCKET socket_handle) { u_long enabled = 1UL; return ioctlsocket(socket_handle, FIONBIO, &enabled) == 0; } /** * 功能:请求Winsock 2.2初始化,成功后取得一次需配对释放的引用。 * 返回值:将WSAStartup直接返回的错误码转为OK/ERROR,并按需写入native_error。 * 说明:WSAStartup的错误不能改用WSAGetLastError读取;本层不缓存“已初始化”标志。 */ IEC104_NetResult_t IEC104_Net_Init(int *native_error) { WSADATA startup; int error = WSAStartup(MAKEWORD(2, 2), &startup); return Result_With_Error(error == 0 ? IEC104_NET_OK : IEC104_NET_ERROR, error, native_error); } /* 释放一次成功Init取得的Winsock引用;调用方应先关闭自己持有的全部socket。 * 与Init一一配对,本函数不检查重复释放,接口也不对外返回WSACleanup的结果。 */ void IEC104_Net_Cleanup(void) { (void)WSACleanup(); } /* 向上层提供Windows参数错误码,使WSAEINVAL不出现在通用调度代码中。 */ int IEC104_Net_Invalid_Argument_Error(void) { return WSAEINVAL; } /** * 功能:完成数字IPv4地址解析、socket创建、非阻塞设置、绑定及监听。 * 输出:成功才交付新句柄和实际端口;失败关闭本次创建的socket,保留原始错误码。 * 说明:本函数只负责监听socket;失败不释放调用方由Net_Init取得的平台引用。 * 参数及资源所有权的完整约定见IEC104_Net.h。 */ IEC104_NetResult_t IEC104_Net_Listen( const char *bind_address, uint16_t port, IEC104_NetSocket_t *listener, uint16_t *bound_port, int *native_error) { struct sockaddr_in address; int address_length = (int)sizeof(address); int enabled = 1; int error; SOCKET socket_handle; /* 先统一失败输出;只清空输出值,不会替调用方关闭原来保存在其中的句柄。 */ if (listener != NULL) { *listener = IEC104_NET_INVALID_SOCKET; } if (bound_port != NULL) { *bound_port = 0U; } if (bind_address == NULL || listener == NULL || bound_port == NULL) { return Result_With_Error(IEC104_NET_ERROR, WSAEINVAL, native_error); } /* 只解析数字IPv4,不做可能阻塞的域名查询;系统sockaddr中的端口使用网络字节序。 */ memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; address.sin_port = htons(port); if (InetPtonA(AF_INET, bind_address, &address.sin_addr) != 1) { return Result_With_Error(IEC104_NET_ERROR, WSAEINVAL, native_error); } socket_handle = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (socket_handle == INVALID_SOCKET) { return Result_With_Error(IEC104_NET_ERROR, WSAGetLastError(), native_error); } /* 独占地址选项须在bind前设置,保留原有监听地址占用策略。 * 各步骤按短路顺序执行,任一步骤失败即停止;getsockname读取实际绑定端口, * 因而port=0时也能将系统分配的端口交回上层。 */ if (setsockopt(socket_handle, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char *)&enabled, sizeof(enabled)) == SOCKET_ERROR || !Set_Nonblocking(socket_handle) || bind(socket_handle, (const struct sockaddr *)&address, sizeof(address)) == SOCKET_ERROR || listen(socket_handle, SOMAXCONN) == SOCKET_ERROR || getsockname(socket_handle, (struct sockaddr *)&address, &address_length) == SOCKET_ERROR) { /* closesocket可能改变线程错误状态,必须先保存最初失败的错误码。 */ error = WSAGetLastError(); (void)closesocket(socket_handle); return Result_With_Error(IEC104_NET_ERROR, error, native_error); } /* 到此才转移句柄所有权;公共接口交付的端口恢复为主机字节序。 */ *listener = (IEC104_NetSocket_t)socket_handle; *bound_port = ntohs(address.sin_port); return Result_With_Error(IEC104_NET_OK, 0, native_error); } /** * 功能:尝试接入一个客户端,设置非阻塞和TCP_NODELAY并转换对端地址后一起交付。 * 返回值:没有待接入客户端时返回WOULD_BLOCK;本次可恢复失败返回RETRY,资源暂缺返回RETRY_LATER。 * 说明:不在后端循环accept,额外连接及失败重试均受TcpServer本轮接入预算约束。 * 任何失败都不会在本函数内关闭listener或修改上层连接状态。 */ IEC104_NetResult_t IEC104_Net_Accept( IEC104_NetSocket_t listener, IEC104_NetSocket_t *client, IEC104_NetPeer_t *peer, int *native_error) { SOCKET accepted; struct sockaddr_in address; int address_length = (int)sizeof(address); int enabled = 1; IEC104_NetPeer_t prepared; int error; IEC104_NetResult_t result; if (client != NULL) { *client = IEC104_NET_INVALID_SOCKET; } if (client == NULL || peer == NULL || listener == IEC104_NET_INVALID_SOCKET) { return Result_With_Error(IEC104_NET_ERROR, WSAEINVAL, native_error); } /* 接入时取得不可变的TCP对端信息;上层后续只读缓存,不再查询可能已关闭或复用的socket。 */ memset(&address, 0, sizeof(address)); accepted = accept((SOCKET)listener, (struct sockaddr *)&address, &address_length); if (accepted == INVALID_SOCKET) { error = WSAGetLastError(); /* 资源暂缺交给上层定时重试;单个客户端复位或中止仍可在本轮预算内重试。 */ if (Accept_Resource_Error(error)) { result = IEC104_NET_RETRY_LATER; } else { result = error == WSAECONNRESET || error == WSAECONNABORTED ? IEC104_NET_RETRY : Socket_Error_Result(error); } return Result_With_Error(result, error, native_error); } /* 显式设置非阻塞并关闭Nagle,减少104短报文的合并等待;任一步骤失败均回收新客户端。 */ if (!Set_Nonblocking(accepted) || setsockopt(accepted, IPPROTO_TCP, TCP_NODELAY, (const char *)&enabled, sizeof(enabled)) == SOCKET_ERROR) { error = WSAGetLastError(); (void)closesocket(accepted); return Result_With_Error(Accept_Resource_Error(error) ? IEC104_NET_RETRY_LATER : IEC104_NET_RETRY, error, native_error); } if (address_length < (int)sizeof(address) || address.sin_family != AF_INET) { (void)closesocket(accepted); return Result_With_Error(IEC104_NET_RETRY, WSAEAFNOSUPPORT, native_error); } memset(&prepared, 0, sizeof(prepared)); if (InetNtopA(AF_INET, &address.sin_addr, prepared.address, sizeof(prepared.address)) == NULL) { error = WSAGetLastError(); (void)closesocket(accepted); return Result_With_Error(Accept_Resource_Error(error) ? IEC104_NET_RETRY_LATER : IEC104_NET_RETRY, error, native_error); } prepared.port = ntohs(address.sin_port); /* 所有准备均成功才发布地址及句柄;失败不泄漏新socket,也不覆盖调用方原地址。 */ *peer = prepared; *client = (IEC104_NetSocket_t)accepted; return Result_With_Error(IEC104_NET_OK, 0, native_error); } /** * 功能:执行一次同步非阻塞接收,将字节数、EOF和错误分别交付上层。 * 说明:不循环读取、不识别104帧边界,也不因EOF主动关闭客户端。 * recv返回0表示对端发送方向关闭,上层仍可决定排空本地待发送数据。 */ IEC104_NetIoResult_t IEC104_Net_Recv( IEC104_NetSocket_t socket_handle, void *data, size_t capacity) { int received; int error; /* Winsock长度参数为int,先校验再转换,避免大size_t被截断成负数。 * 排除零容量请求,使recv返回0可以明确表示EOF。 */ if (socket_handle == IEC104_NET_INVALID_SOCKET || data == NULL || capacity == 0U || capacity > INT_MAX) { return Io_Result(IEC104_NET_ERROR, 0U, WSAEINVAL); } received = recv((SOCKET)socket_handle, (char *)data, (int)capacity, 0); if (received > 0) { return Io_Result(IEC104_NET_OK, (size_t)received, 0); } if (received == 0) { return Io_Result(IEC104_NET_EOF, 0U, 0); } /* 只在recv失败后读取错误;WOULD_BLOCK和RETRY都没有新增接收字节。 */ error = WSAGetLastError(); return Io_Result(Socket_Error_Result(error), 0U, error); } IEC104_NetResult_t IEC104_Net_Pending_Input( IEC104_NetSocket_t socket_handle, uint32_t *available, int *native_error) { u_long count = 0UL; if (socket_handle == IEC104_NET_INVALID_SOCKET || available == NULL) { return Result_With_Error(IEC104_NET_ERROR, WSAEINVAL, native_error); } if (ioctlsocket((SOCKET)socket_handle, FIONREAD, &count) == SOCKET_ERROR) { return Result_With_Error(IEC104_NET_ERROR, WSAGetLastError(), native_error); } *available = (uint32_t)count; return Result_With_Error(IEC104_NET_OK, 0, native_error); } /** * 功能:执行一次同步非阻塞发送,准确报告本次交给本机网络栈的字节数。 * 说明:不代替上层循环补发;调用方须保留未发送的后缀,并按实际进度推进发送计划。 * 使用普通send,不发起需在返回后继续借用data的异步重叠I/O。 */ IEC104_NetIoResult_t IEC104_Net_Send( IEC104_NetSocket_t socket_handle, const void *data, size_t length) { int written; int error; /* 与Recv一致,长度须能无损转换为Winsock要求的正int。 */ if (socket_handle == IEC104_NET_INVALID_SOCKET || data == NULL || length == 0U || length > INT_MAX) { return Io_Result(IEC104_NET_ERROR, 0U, WSAEINVAL); } written = send((SOCKET)socket_handle, (const char *)data, (int)length, 0); /* 允许部分写入及零进度;send返回0不同于recv返回0,不转成EOF。 */ if (written >= 0) { return Io_Result(IEC104_NET_OK, (size_t)written, 0); } error = WSAGetLastError(); return Io_Result(Socket_Error_Result(error), 0U, error); } /** * 功能:通过一次select等待socket就绪并转换事件位;空集合改作有限定时等待。 * 返回值:正常就绪或超时均为OK;中断为RETRY,其他等待失败为ERROR。 * 说明:最多关注一个监听和一个客户端;不执行实际收发,也不在内部重试select。 * 等待期限及中断后的剩余时长由TcpServer重新计算,避免反复等待延长期限。 */ IEC104_NetResult_t IEC104_Net_Wait( IEC104_NetSocket_t listener, IEC104_NetSocket_t client, uint32_t interest, uint32_t timeout_ms, uint32_t *ready_events, int *native_error) { fd_set read_set, write_set; struct timeval timeout; int ready; /* 清除上一轮结果;参数错误、等待失败及正常超时均不会残留旧就绪位。 */ if (ready_events != NULL) { *ready_events = 0U; } /* 只校验本轮实际关注的句柄;空集合用于接入暂停等纯定时等待。 */ if (ready_events == NULL || (interest & ~(IEC104_NET_EVENT_ACCEPT | IEC104_NET_EVENT_READ | IEC104_NET_EVENT_WRITE)) != 0U || ((interest & IEC104_NET_EVENT_ACCEPT) != 0U && listener == IEC104_NET_INVALID_SOCKET) || ((interest & (IEC104_NET_EVENT_READ | IEC104_NET_EVENT_WRITE)) != 0U && client == IEC104_NET_INVALID_SOCKET)) { return Result_With_Error(IEC104_NET_ERROR, WSAEINVAL, native_error); } if (interest == 0U) { /* Winsock的select不接受空集合;0直接返回,UINT32_MAX不能传成Sleep(INFINITE)。 */ if (timeout_ms != 0U) { Sleep((DWORD)(timeout_ms == UINT32_MAX ? UINT32_MAX - 1U : timeout_ms)); } return Result_With_Error(IEC104_NET_OK, 0, native_error); } /* select会改写集合,每次都重新构建;监听接入和客户端接收都使用读集合。 */ FD_ZERO(&read_set); FD_ZERO(&write_set); if ((interest & IEC104_NET_EVENT_ACCEPT) != 0U) { FD_SET((SOCKET)listener, &read_set); } if ((interest & IEC104_NET_EVENT_READ) != 0U) { FD_SET((SOCKET)client, &read_set); } if ((interest & IEC104_NET_EVENT_WRITE) != 0U) { FD_SET((SOCKET)client, &write_set); } /* 将毫秒拆成秒和微秒;二者均为0时select只查询,不等待后续事件。 */ timeout.tv_sec = (long)(timeout_ms / 1000U); timeout.tv_usec = (long)((timeout_ms % 1000U) * 1000U); /* Winsock忽略nfds,固定传0;fd_set和timeval等平台细节不进入公共头文件。 */ ready = select(0, &read_set, &write_set, NULL, &timeout); if (ready == SOCKET_ERROR) { int error = WSAGetLastError(); return Result_With_Error(error == WSAEINTR ? IEC104_NET_RETRY : IEC104_NET_ERROR, error, native_error); } /* select返回就绪数量,不能直接作为事件掩码;逐项还原本轮关注的事件位。 */ if (ready > 0) { if ((interest & IEC104_NET_EVENT_ACCEPT) != 0U && FD_ISSET((SOCKET)listener, &read_set)) { *ready_events |= IEC104_NET_EVENT_ACCEPT; } if ((interest & IEC104_NET_EVENT_READ) != 0U && FD_ISSET((SOCKET)client, &read_set)) { *ready_events |= IEC104_NET_EVENT_READ; } if ((interest & IEC104_NET_EVENT_WRITE) != 0U && FD_ISSET((SOCKET)client, &write_set)) { *ready_events |= IEC104_NET_EVENT_WRITE; } } return Result_With_Error(IEC104_NET_OK, 0, native_error); } /** * 功能:关闭单个socket,将closesocket失败转换为ERROR及原始诊断码。 * 说明:INVALID_SOCKET直接返回OK,便于调用方清理尚未完整初始化的资源。 * 参数按值传入,不会清空调用方字段;也不会释放Init取得的Winsock引用。 */ IEC104_NetResult_t IEC104_Net_Close(IEC104_NetSocket_t socket_handle, int *native_error) { if (socket_handle != IEC104_NET_INVALID_SOCKET && closesocket((SOCKET)socket_handle) == SOCKET_ERROR) { return Result_With_Error(IEC104_NET_ERROR, WSAGetLastError(), native_error); } return Result_With_Error(IEC104_NET_OK, 0, native_error); } /* 读取系统启动以来的64位毫秒计数;供相对期限计算,不受用户修改日历时间影响。 * 不在此处换算为UTC或本地时间,避免将协议超时与日历时钟混用。 */ uint64_t IEC104_Net_Now_Ms(void) { return (uint64_t)GetTickCount64(); }