203 lines
13 KiB
C
203 lines
13 KiB
C
/*
|
||
* TCP平台接口:只负责同步非阻塞I/O、就绪等待和单调计时,不依赖104协议模块。
|
||
* 工程须链接且只链接一个实现;当前提供IEC104_Net_Windows.c。
|
||
* 不创建线程、不保存业务回调,也不决定协议故障、EOF排空或重连保留策略。
|
||
*
|
||
* 职责边界:TcpServer决定何时接入、读写和关闭,Net负责执行一次平台操作。
|
||
* 典型生命周期:Init -> Listen -> Wait/Accept/Recv/Send -> Close各句柄 -> Cleanup。
|
||
* 调用方持有监听和客户端句柄;本层不会为其保存连接对象或业务状态。
|
||
*
|
||
* 通用约定:native_error输出均允许NULL;成功时写0,失败时保留平台诊断码。
|
||
* 上层应按result调度,native_error只用于诊断,不应依赖某个平台的错误常量。
|
||
* 所有缓冲区均只在调用期间使用,函数返回后本层不再持有调用方的内存。
|
||
*/
|
||
#ifndef IEC104_NET_H
|
||
#define IEC104_NET_H
|
||
|
||
#include <stddef.h>
|
||
#include <stdint.h>
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/* TCP内部拥有的数值句柄;0可以是有效句柄。
|
||
* TcpServer可按其公开契约借给应用事件循环注册,应用不得接管I/O、关闭或修改句柄属性。
|
||
* 使用整数类型承载平台句柄,使公共头文件无需包含SOCKET等系统类型。
|
||
* 是否无效只能与IEC104_NET_INVALID_SOCKET比较,不能用if (!handle)判断。 */
|
||
typedef uintptr_t IEC104_NetSocket_t;
|
||
#define IEC104_NET_INVALID_SOCKET ((IEC104_NetSocket_t)UINTPTR_MAX)
|
||
|
||
/* 必须按枚举值判断:OK为0,正数不统一表示成功或有数据。
|
||
* WOULD_BLOCK表示当前没有进度,通常等待下一次就绪;RETRY表示本次操作可重试。
|
||
* RETRY_LATER仅用于接入资源暂缺,须由调用方暂停接入并安排定时重试。
|
||
* 重试次数、收发预算及超时期限由调用方限制,不能在后端无限循环。 */
|
||
typedef enum {
|
||
IEC104_NET_ERROR = -1, /* 最终失败,native_error保存平台错误码。 */
|
||
IEC104_NET_OK = 0, /* 操作完成;Wait的就绪标志可为0,表示未报告socket就绪。 */
|
||
IEC104_NET_WOULD_BLOCK, /* 暂不可接受连接/读写,保留原有状态后续再试。 */
|
||
IEC104_NET_RETRY, /* 本次被中断或单个待接入客户端失效,可有界重试。 */
|
||
IEC104_NET_EOF, /* 仅Recv使用:对端发送方向有序关闭。 */
|
||
IEC104_NET_RETRY_LATER /* 仅Accept使用:资源暂缺,不能立即循环重试。 */
|
||
} IEC104_NetResult_t;
|
||
|
||
/* 一次收发的完整结果;把状态、字节数和平台错误分开,避免把EOF当成暂不可读。 */
|
||
typedef struct {
|
||
IEC104_NetResult_t result; /* 调度依据;读取transferred前先判断此字段。 */
|
||
size_t transferred; /* OK时为本次实际读写字节数,其余结果恒为0。 */
|
||
int native_error; /* 平台诊断码,OK/EOF为0;上层按result决定调度。 */
|
||
} IEC104_NetIoResult_t;
|
||
|
||
/* Accept交付的对端地址副本,平台sockaddr和网络字节序不越过Net边界。
|
||
* 当前监听仅支持IPv4;address不包含端口,port使用主机字节序。 */
|
||
typedef struct {
|
||
char address[16]; /* NUL结尾数字IPv4,最长15字符。 */
|
||
uint16_t port; /* 客户端源端口,并非本地监听端口。 */
|
||
} IEC104_NetPeer_t;
|
||
|
||
/* Wait的关注位和就绪位使用同一组掩码,可以按位或组合。
|
||
* 就绪只表示可以尝试操作,最终状态仍由Accept/Recv/Send的返回值确定。 */
|
||
#define IEC104_NET_EVENT_ACCEPT 1U /* 监听句柄可尝试接入客户端。 */
|
||
#define IEC104_NET_EVENT_READ 2U /* 客户端可尝试接收,也可能读到EOF或错误。 */
|
||
#define IEC104_NET_EVENT_WRITE 4U /* 客户端可尝试发送,不保证整段数据一次写完。 */
|
||
|
||
/**
|
||
* 功能:取得本次网络使用所需的平台初始化资源。
|
||
* 输出参数:native_error可为NULL;成功写0,失败写平台错误码。
|
||
* 返回值:OK表示初始化成功;ERROR表示失败,此时无需为本次调用Cleanup。
|
||
* 说明:在创建socket前调用;每次成功Init均须配对一次Cleanup。
|
||
* 重复调用并非幂等操作,不能把多次成功Init只配对一次Cleanup。
|
||
*/
|
||
extern IEC104_NetResult_t IEC104_Net_Init(int *native_error);
|
||
|
||
/**
|
||
* 功能:释放一次成功Init取得的平台初始化资源。
|
||
* 说明:调用前先关闭本次持有的全部socket;本函数不逐一管理调用方的句柄。
|
||
* 无返回值,也不负责清空TcpServer中的连接状态。
|
||
*/
|
||
extern void IEC104_Net_Cleanup(void);
|
||
|
||
/**
|
||
* 功能:取得平台对应的“参数无效”诊断码,不执行网络操作。
|
||
* 返回值:当前后端的参数错误码,用于上层自身参数校验失败时填写诊断信息。
|
||
* 说明:避免TcpServer为记录错误而包含Windows等平台的系统错误常量。
|
||
*/
|
||
extern int IEC104_Net_Invalid_Argument_Error(void);
|
||
|
||
/**
|
||
* 功能:创建、绑定并启动一个非阻塞IPv4 TCP监听socket。
|
||
* 输入参数:bind_address为有效的NUL结尾数字IPv4字符串,不进行域名解析。
|
||
* port使用主机字节序;为0时由系统分配可用端口。
|
||
* 输出参数:listener、bound_port必填,分别接收新监听句柄和实际端口。
|
||
* bound_port同样使用主机字节序;native_error可为NULL。
|
||
* 各输出存储区不得重叠,也不得覆盖输入字符串。
|
||
* 返回值:OK时将新句柄的所有权交给调用方;ERROR时回收本次创建的socket,
|
||
* 并将有效输出指针指向的listener/bound_port置为INVALID_SOCKET/0。
|
||
* 说明:只用于创建新句柄,不会关闭listener原来保存的句柄;先处理旧资源再调用。
|
||
* 失败不撤销此前成功的Init,调用方仍须按生命周期执行Cleanup。
|
||
*/
|
||
extern IEC104_NetResult_t IEC104_Net_Listen(
|
||
const char *bind_address, uint16_t port, IEC104_NetSocket_t *listener,
|
||
uint16_t *bound_port, int *native_error);
|
||
|
||
/**
|
||
* 功能:从非阻塞监听socket接入至多一个客户端,不在内部循环accept。
|
||
* 输入参数:listener为Listen成功交付的监听句柄。
|
||
* 输出参数:client、peer必填且不得重叠,OK时交付非阻塞且启用TCP_NODELAY的客户端及其IPv4地址/源端口;
|
||
* native_error可为NULL。
|
||
* 返回值:OK表示接入成功,调用方负责关闭客户端;WOULD_BLOCK表示暂无待接入连接;
|
||
* RETRY表示本次被中断、待接入客户端失效、非阻塞/TCP_NODELAY设置或地址转换失败;
|
||
* RETRY_LATER表示接入资源暂缺,调用方应暂停关注监听就绪并定时重试;
|
||
* ERROR表示参数无效或其他失败。非OK时有效client输出为INVALID_SOCKET,peer保持原值。
|
||
* 说明:新客户端设置或地址转换失败时,先关闭它再返回RETRY(资源暂缺为RETRY_LATER),
|
||
* 监听句柄仍由调用方持有,后端本身不等待。
|
||
* client只接收新句柄,不会自动关闭其中的旧句柄;不得覆盖仍在使用的连接。
|
||
* 是否接受额外客户端及重试多少次,由TcpServer的连接策略和预算决定。
|
||
*/
|
||
extern IEC104_NetResult_t IEC104_Net_Accept(
|
||
IEC104_NetSocket_t listener, IEC104_NetSocket_t *client, IEC104_NetPeer_t *peer, int *native_error);
|
||
|
||
/**
|
||
* 功能:对已接入的非阻塞客户端执行至多一次同步recv。
|
||
* 输入参数:socket_handle为有效客户端句柄;data非NULL,指向可写缓冲区;
|
||
* capacity为缓冲区容量,必须大于0,当前Windows后端还要求不大于INT_MAX。
|
||
* 输出参数:OK时将transferred个字节写入data,不追加字符串结束符。
|
||
* 返回值:OK表示收到数据;EOF表示对端发送方向有序关闭;WOULD_BLOCK表示暂不可读;
|
||
* RETRY表示本次操作被中断;ERROR表示参数或socket错误。
|
||
* 非OK时transferred为0;OK/EOF时native_error为0,其余状态保留诊断码。
|
||
* 说明:TCP是字节流,一次接收不对应一个完整104报文;拆帧由上层负责。
|
||
* EOF不会自动关闭句柄,调用方仍可排空待发送数据;函数返回后不再借用data。
|
||
* WOULD_BLOCK/RETRY不代表数据进度,不能据此重新计算或延长原超时期限。
|
||
*/
|
||
extern IEC104_NetIoResult_t IEC104_Net_Recv(
|
||
IEC104_NetSocket_t socket_handle, void *data, size_t capacity);
|
||
|
||
/* 只读取得当前内核接收队列的字节数,不等待、不消费输入、不包含后来到达的字节。
|
||
* 用于给跨调度轮次的续收设定有限边界;不是完整APDU数,也不表示连接仍存活。
|
||
* available必填,仅OK时写入;失败返回ERROR并保留平台诊断,由上层结束连接。 */
|
||
extern IEC104_NetResult_t IEC104_Net_Pending_Input(
|
||
IEC104_NetSocket_t socket_handle, uint32_t *available, int *native_error);
|
||
|
||
/**
|
||
* 功能:对已接入的非阻塞客户端执行至多一次同步send。
|
||
* 输入参数:socket_handle为有效客户端句柄;data非NULL,指向待发送数据;
|
||
* length为待发送长度,必须大于0,当前Windows后端还要求不大于INT_MAX。
|
||
* 返回值:OK时transferred表示本次被本机网络栈接受的字节数,可能小于length或为0;
|
||
* WOULD_BLOCK表示暂不可写;RETRY表示本次被中断;ERROR表示参数或socket错误。
|
||
* 非OK时transferred为0;此接口不会返回EOF,写入0字节仍属于OK。
|
||
* 说明:只按transferred推进发送位置,未写入的后缀由调用方保留并择机继续发送。
|
||
* 函数返回后不再借用data;OK不代表对端应用收到数据或完成104协议确认。
|
||
* WOULD_BLOCK/RETRY以及OK但写入0字节都不应被视为发送进度。
|
||
* 调用方须限制连续无进展的立即重试,并安排延时恢复;不能仅依赖新的可写边沿。
|
||
*/
|
||
extern IEC104_NetIoResult_t IEC104_Net_Send(
|
||
IEC104_NetSocket_t socket_handle, const void *data, size_t length);
|
||
|
||
/**
|
||
* 功能:等待本轮关注的监听或客户端事件;空集合只作有限定时等待,不执行I/O。
|
||
* 输入参数:interest为上述事件位的组合,允许为0,不允许包含未知位。
|
||
* 关注ACCEPT时listener须有效;关注READ/WRITE时client须有效;
|
||
* 未被关注的句柄可以为INVALID_SOCKET。
|
||
* timeout_ms为本次等待时长,单位毫秒;为0时只查询,空集合直接返回。
|
||
* UINT32_MAX仍为有限时长;后端须避开系统无限等待哨兵,必要时提前返回。
|
||
* 输出参数:ready_events必填,每次先清零;native_error可为NULL。
|
||
* 返回值:OK时ready_events给出就绪事件的位组合,0表示未报告就绪(包括纯定时等待);
|
||
* RETRY表示等待被中断,ERROR表示参数或等待失败,两者就绪位均为0。
|
||
* 说明:最多调用一次平台等待;RETRY交由调用方按剩余期限重新调度,后端不无限重试。
|
||
* 等待可能提前结束,是否到期由调用方按单调时钟重新判断。
|
||
* 就绪后实际I/O仍可能暂不可读写、部分发送或遇到EOF,必须检查后续操作结果。
|
||
*/
|
||
extern 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);
|
||
|
||
/**
|
||
* 功能:关闭一个监听或客户端句柄,不调用Cleanup或业务回调。
|
||
* 输入参数:socket_handle为要关闭的句柄;INVALID_SOCKET视为已关闭。
|
||
* 输出参数:native_error可为NULL,失败时保留关闭操作的诊断码。
|
||
* 返回值:OK表示关闭成功或传入无效哨兵值;ERROR表示关闭操作失败。
|
||
* 说明:句柄按值传入,本函数无法修改调用方字段;调用方需维护自己的句柄状态。
|
||
* 成功关闭后应将保存的句柄置为INVALID_SOCKET,避免重复关闭已被系统复用的值。
|
||
*/
|
||
extern IEC104_NetResult_t IEC104_Net_Close(
|
||
IEC104_NetSocket_t socket_handle, int *native_error);
|
||
|
||
/**
|
||
* 功能:读取平台单调时钟,供连接超时和收发期限计算使用。
|
||
* 返回值:64位单调毫秒计数,不是UTC时间戳,也不用于显示日期;0及连续读取相等值有效。
|
||
* 说明:与日历时间及校时无关;同一会话的时间差和期限必须使用同一时钟来源。
|
||
* 有效运行期间不得倒退、复位或回绕。
|
||
* 若底层仅提供32位节拍,须通过回绕计数或等效机制扩展为64位,
|
||
* 再按节拍频率换算为毫秒;不得仅作uint64_t强制转换。
|
||
* 扩展机制必须保证不漏记回绕,不能依赖本函数被频繁调用。
|
||
* 扩展计数的更新与读取须正确同步,取得高低位一致的快照,
|
||
* 防止节拍中断或其他并发更新造成读值错误;这不构成从中断上下文调用本接口的保证。
|
||
* 本函数只读取时钟,不创建定时器,也不触发协议超时处理。
|
||
*/
|
||
extern uint64_t IEC104_Net_Now_Ms(void);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
#endif /* IEC104_NET_H */
|