378 lines
17 KiB
C
378 lines
17 KiB
C
/*
|
||
* 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 <winsock2.h>
|
||
#include <ws2tcpip.h>
|
||
#include <windows.h>
|
||
#include <limits.h>
|
||
#include <string.h>
|
||
|
||
/* 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();
|
||
}
|