199 lines
17 KiB
C
199 lines
17 KiB
C
/*
|
||
* IPv4子站TCP业务接口:隐藏Connection、队列及存储布局;socket只借给事件循环注册。
|
||
* 子站业务默认通过IEC104_Slave.h接入;本接口供Slave及独立TCP接入使用。
|
||
* Connection、SendManager等由TCP内部调度,服务对象仍保持不透明。
|
||
* 一个Slave拥有一个本模块句柄;本模块每个实例同时只服务一条主站TCP连接。
|
||
* 本层交付完整APDU及发送/连接事件,Slave再建立ASDU视图和原连接应答接口。
|
||
* 本层不解析具体业务命令、不自动应答ASDU;104序号、启停和超时由内部Connection处理。
|
||
*
|
||
* 生命周期:Create -> Config_Default/Init -> Prepare/Process/NextEvent -> Close/NextEvent -> Destroy。
|
||
* 独立TCP循环也可使用Poll,由它组合准备、平台等待和推进;两种入口使用同一推进实现。
|
||
* Close释放网络并保留通知,Destroy在通知读空后归还静态槽,两者不能互相替代。
|
||
* 服务结果、提交结果和取事件结果是三种枚举;须分别比较OK、SUBMIT_OK、EVENT_READY。
|
||
* error可为NULL;非NULL时完整填写本次调用诊断,历史网络错误另见状态快照。
|
||
*
|
||
* 同一服务的全部接口在同一线程串行调用;Create/Destroy也须跨服务串行调用。
|
||
* 工程只链接一个Net后端;当前Windows实现需链接Ws2_32。
|
||
*/
|
||
#ifndef IEC104_TCPSERVER_H
|
||
#define IEC104_TCPSERVER_H
|
||
|
||
#include "IEC104_TcpTypes.h"
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/* 不透明句柄:这里仅声明结构体名称,完整成员在IEC104_TcpServer.c中定义。
|
||
* 业务只持有Create返回的指针,不能定义对象、计算其sizeof、复制对象或访问内部成员。
|
||
* Slave可以保存并传递此指针,无需看见完整结构;这不表示把底层对象公开给业务。
|
||
* Connection、SendManager和缓冲区都由实现持有,业务无需为它们分配存储。 */
|
||
typedef struct IEC104_TcpServer IEC104_TcpServer_t;
|
||
|
||
/* 从模块内部静态池取得对象,无malloc、无网络操作;无可用槽时返回NULL。
|
||
* 默认容量1,可在IEC104_TcpServer_Config.h或编译参数中配置;成功后必须配对Destroy。
|
||
* 代次或注册编号耗尽的槽不再分配;句柄至Destroy成功前有效,不得free、清零或自行分配对象。 */
|
||
extern IEC104_TcpServer_t *IEC104_TcpServer_Create(void);
|
||
|
||
/* 只释放已Close(或尚未Init)且事件已读空的对象,成功返回OK。
|
||
* 无效句柄、重入、仍在运行、未读事件分别返回INVALID_HANDLE/BUSY/INVALID_STATE/EVENTS_PENDING。
|
||
* 不隐式丢弃事件或关闭运行中的服务;成功后所有旧句柄副本失效。
|
||
* 同一静态槽再分配仍保留代次及注册计数;调用方须结束旧业务任务,不能继续使用旧句柄。
|
||
* 成功后应将业务保存的指针置NULL;对象来自静态池,不得调用free。 */
|
||
extern IEC104_TcpResult_t IEC104_TcpServer_Destroy(
|
||
IEC104_TcpServer_t *server, IEC104_TcpError_t *error);
|
||
|
||
/* 填写监听和协议默认值;NULL无操作,不占用对象池。
|
||
* 监听默认0.0.0.0:2404;协议默认值沿用Connection配置,静态存储容量由Config.h控制。 */
|
||
extern void IEC104_TcpServer_Config_Default(IEC104_TcpServerConfig_t *config);
|
||
|
||
/* 成功返回OK;server来自Create,或已Close且事件已读空。
|
||
* 参数/基础配置失败为INVALID_ARGUMENT/INVALID_CONFIG,运行中为INVALID_STATE,旧事件未读为EVENTS_PENDING。
|
||
* 协议配置须满足0<t2_ms<t1_ms;不满足时在申请网络资源前返回INVALID_CONFIG,error阶段为VALIDATE。
|
||
* 注册编号耗尽为REGISTRATION_EXHAUSTED,error阶段为REGISTRATION;须Destroy归还句柄,不再重试Init。
|
||
* NETWORK_ERROR通过error区分NET_INIT/LISTEN阶段;地址内容解析失败也属于LISTEN阶段。
|
||
* 成功后服务队列已经可用,尚未接入客户端也可Enqueue_Event;内部队列初始化异常为INTERNAL_ERROR。
|
||
* 失败回收本次申请的网络资源,句柄仍归业务持有,可查询状态、重试或Destroy。
|
||
* 重复Init不关闭旧服务;成功后通过Prepare/Process或Poll持续驱动。 */
|
||
extern IEC104_TcpResult_t IEC104_TcpServer_Init(
|
||
IEC104_TcpServer_t *server, const IEC104_TcpServerConfig_t *config, IEC104_TcpError_t *error);
|
||
|
||
/* 查询下一轮事件循环需要的socket、协议/接入/发送等待上限和本地工作标志;仅OK修改info。
|
||
* server须已Init;失败原因通过统一结果和error交付,不读取或修改Connection内部布局。
|
||
* service_token标识本次Init,socket token标识某次socket注册;二者与业务连接generation用途不同。
|
||
* Enqueue_Event、Send_Response、NextEvent、Process、Close_Connection、Init/Close改变调度条件,等待前须重新查询。
|
||
* 借出的socket只用于注册;Process/Send_Response/Close_Connection/Close可能关闭它们,之后须移除过期注册。
|
||
* 接入边沿触发时仍须遵守need_process;预算耗尽或背压恢复后不能只等下一个边沿。
|
||
* 接入资源暂缺或整轮4次均为RETRY时暂停100毫秒;期间撤销监听关注,由timer安排到期检查。
|
||
* 到期有接入容量即要求本地重试;无容量时等待NextEvent释放通知空间,不保留已到期定时器。
|
||
* 连续4次send返回OK+0或RETRY后暂停发送10毫秒;期间撤销客户端WRITE关注,由timer恢复。
|
||
* 暂停同时约束Process及快速应答;到期主动重试,接收和原协议期限继续推进。
|
||
* 单次通知模式须由应用按新快照重新布防;业务事件继续通过NextEvent取出。 */
|
||
extern IEC104_TcpResult_t IEC104_TcpServer_Prepare(
|
||
const IEC104_TcpServer_t *server, IEC104_TcpWaitInfo_t *info, IEC104_TcpError_t *error);
|
||
|
||
/* 根据一批就绪/定时/本地通知,有界执行接受、非阻塞收发和协议推进,绝不调用Net_Wait。
|
||
* ready必填;本地工作也使用最近Prepare的service_token构造空批次,不能用旧通知冒充新唤醒。
|
||
* 每轮最多32次Feed、32次send、4次accept和8条业务通知交付;断线收尾须一次完整交付。
|
||
* 交付预算或本类配额刚耗尽时先返回业务一次,下一轮重新获得预算;无处交付的I帧等待普通空间。
|
||
* 改变状态的ACT等待启停专用槽,NextEvent消费后安排重试;保留接收顺序,不影响已有CON续写。
|
||
* 可立即推进的工作由need_process提示,接入/发送暂停由timer提示,原有协议期限不重置。
|
||
* 就绪只是提示,实际I/O仍可能暂不可用;本地/定时唤醒也允许一次有界的非阻塞接收检查。
|
||
* 本批先接收并提交已到达事实,再检查确认期限;输入事件产生后交还应用处理,再续收。
|
||
* 仅因预算让出时,固定已到达输入的字节范围并要求立即续收,处理完后再检查ACK/TESTFR期限。
|
||
* 后来到达的输入不扩大旧批次;真正容量背压或接收无进展仍检查原期限。发送使用独立预算。
|
||
* OK包括无进度、过期通知被忽略及客户端已断线;服务级错误应保存error后Close。
|
||
* 所有调用必须在同一线程;不创建线程、系统定时器、跨线程唤醒或业务回调。 */
|
||
extern IEC104_TcpResult_t IEC104_TcpServer_Process(
|
||
IEC104_TcpServer_t *server, const IEC104_TcpReady_t *ready, IEC104_TcpError_t *error);
|
||
|
||
/* 独立循环的组合入口:Prepare -> 至多一次Net_Wait -> Process,使用同一套推进逻辑。
|
||
* 每轮返回后NextEvent处理事件;使用应用自己的事件循环时直接调用Prepare/Process。
|
||
* max_wait_ms是本轮网络等待上限:0仅查询就绪,正数允许等待;UINT32_MAX仍为有限毫秒数。
|
||
* 实际等待还受最近协议/接入/发送重试期限约束;已有事件或可立即推进的工作时不等待。
|
||
* 无socket关注时也可有限等待;0毫秒立即返回,UINT32_MAX不会转成系统无限睡眠。
|
||
* 上限只约束网络等待,不保证整个函数的执行耗时;0模式下由业务主循环安排其他任务和休眠。
|
||
* OK表示服务可继续(包括客户端已断线);尚未Init为INVALID_STATE,重入为BUSY。
|
||
* NETWORK_ERROR/INTERNAL_ERROR及身份编号耗尽为服务级失败,保存error后应Close。
|
||
* 空闲、接入容量/资源暂缺、可恢复的网络中断及客户端故障均不作为服务级失败。
|
||
* 普通通知满仍可处理排在前面的S/U,不以普通通知容量阻挡CON;已确认回执暂留内部记录。
|
||
* 遇到无处交付的I帧时暂停续收并置receive_paused;启停专用槽满时暂缓受理后续改变状态的ACT。
|
||
* 消费对应通知后由Process恢复,不丢通知、不因通知积压直接断连;等待启停槽不设置receive_paused。
|
||
* 保留的I帧会挡住同一TCP流中后续S/U;应用须及时消费事件,原有协议期限不延长,超时仍可断连。
|
||
* EOF后停止为未编号NORMAL创建新I帧,原文留给下一连接;当前已编号帧、已受理HIGH及控制帧继续收尾。
|
||
* 部分写入跨Poll续发,不再等待新的ACK/STARTDT。
|
||
* 若仅因已确认记录尚未转为事件而缺少记录槽,则保留已受理应答,预算恢复或NextEvent腾出空间后续发;
|
||
* 已排空或仍需对端输入才能解除限制时关闭,原有协议及发送期限仍生效。 */
|
||
extern IEC104_TcpResult_t IEC104_TcpServer_Poll(
|
||
IEC104_TcpServer_t *server, uint32_t max_wait_ms, IEC104_TcpError_t *error);
|
||
|
||
/* 复制并取出最早事件,不执行I/O或业务函数;仅READY修改输出。
|
||
* EMPTY/ERROR保持输出不变,副本无需Release;Close后可读完全部收尾事件。
|
||
* 本层事件只含值和原文字节,可按值保存;Slave事件另外含自引用,须通过Slave_Copy_Event复制。
|
||
* 建立/激活/停用/断开均通过本入口逐条交付,不合并中间变化。
|
||
* 消费后安排保留I帧交付、启停CON续发、回执转移或接入重试;这里只取副本,不执行I/O或业务函数。
|
||
* 应用须及时消费通知并持续调用Process/Poll;等待通知空间不延长协议期限,长期不消费仍可能超时。
|
||
* READY后事件已从内部FIFO移除;需要稍后处理时由业务另存副本,不能依赖自动重投。 */
|
||
extern IEC104_TcpEventResult_t IEC104_TcpServer_NextEvent(
|
||
IEC104_TcpServer_t *server, IEC104_TcpEvent_t *event);
|
||
|
||
/* 复制主动上报到服务级低优先级事件队列(内部NORMAL);本入口只缓存,不立即写socket。
|
||
* Init成功后即可调用,无需连接代次,首次连接前和断线期间也可缓存。
|
||
* EOF后全部尚未编号的数据及新增数据留给下一连接;重连后现存NORMAL按原顺序重新组帧,STARTDT完成后才发送。
|
||
* 容量不足沿用覆盖最旧原文的策略,包括未ACK原文;不持久化,Close清空队列。
|
||
* 未ACK不代表对端未收到,恢复可能重复;同一服务应面向相同业务接收方或冗余域。
|
||
* 有活动连接且未EOF时,成功入队使Prepare报告need_process;离线缓存不会触发空转。
|
||
* 本入口与Send_Response均接收6..249字节ASDU,不含APCI;OK已接手,WAIT需业务保存重试。
|
||
* 所有调用须在所属事件循环线程串行执行;异步生产者先唤醒该线程,入队后刷新等待条件。 */
|
||
extern IEC104_TcpSubmitResult_t IEC104_TcpServer_Enqueue_Event(
|
||
IEC104_TcpServer_t *server, const uint8_t *asdu, uint16_t asdu_length);
|
||
|
||
/* 提交当前请求的应答ASDU,须携带原I_FRAME事件的generation;旧代次、无连接及EOF后均拒绝。
|
||
* 仅STARTED+IDLE时接手;否则在容量检查前ERROR,不入HIGH、不因拒绝断连。
|
||
* 已受理START、窗口及记录有余量、没有当前帧或旧HIGH且公平配额允许时,直接准备I帧并尝试一次send。
|
||
* 准入后,已到协议期限时先入HIGH,交由Process接收待处理ACK后再检查期限;其他暂不能准备的情况也入HIGH。
|
||
* send后按真实时刻登记进度;若期间跨过ACK/TESTFR的t1,安排立即Process先接收再按原期限判定。
|
||
* 确认t1已到且尚未解除时,Process也暂停为HIGH/NORMAL创建新I帧;已有帧及控制发送仍可推进。
|
||
* 不增加宽限时间;本地发送和STOP总期限仍在发送推进时检查。
|
||
* START受理即激活,已有CON待发时应答入HIGH,实际I帧发送仍排在控制FIFO之后。
|
||
* STOP受理即停用并清空待发HIGH,不等待START_CON完成;新应答返回ERROR。
|
||
* 后到START成功受理即重新激活并取消旧STOP总计时,已有CON继续按FIFO完成。
|
||
* 本项目保留已启用状态下重复START不清理HIGH的约定;重新激活不恢复已经丢弃的HIGH。
|
||
* 不等待、不循环发送,并遵守发送重试暂停;无进展时保留当前APDU,由Process/Poll续发,不重复放入HIGH。
|
||
* OK已复制到发送缓冲或HIGH,可以复用原文;不承诺已写完或已ACK。HIGH满返回WAIT,本次未接手。
|
||
* 受理新STOPDT_ACT时,尚在HIGH中的应答立即作废,无逐条事件;已有I帧及ACK记录继续排空。
|
||
* FATAL可能已写出字节并关闭当前连接,须处理收尾事件;HIGH及直接应答均不转给新连接。
|
||
* 所有调用在事件循环线程串行执行;返回后刷新Prepare,及时处理本地续跑、socket及定时变化。 */
|
||
extern IEC104_TcpSubmitResult_t IEC104_TcpServer_Send_Response(
|
||
IEC104_TcpServer_t *server, uint64_t generation,
|
||
const uint8_t *asdu, uint16_t asdu_length);
|
||
|
||
/* 不排队应答:HIGH/NORMAL有待发数据、当前帧未完成、窗口/记录不足或协议工作到期时WAIT。
|
||
* WAIT未接手,不保存ASDU;继续Process/Poll后可用新取值重试。
|
||
* OK已复制到SendBuffer并固定发送顺序,部分写入时也不能重交,不要求已ACK。
|
||
* 调用串行、会话身份、FATAL和等待条件刷新约定同Send_Response。 */
|
||
extern IEC104_TcpSubmitResult_t IEC104_TcpServer_Try_Send_Response(
|
||
IEC104_TcpServer_t *server, uint64_t generation,
|
||
const uint8_t *asdu, uint16_t asdu_length);
|
||
|
||
/* 查询当前主站的IPv4、源端口及连接代次;仅OK写peer,不查询socket、不推进协议。
|
||
* server须已Init且当前持有客户端(包括EOF收尾期间);未Init为INVALID_STATE,无客户端为NOT_CONNECTED。
|
||
* NULL输出为INVALID_ARGUMENT;无效句柄/重入为INVALID_HANDLE/BUSY;失败不改peer,error可为NULL。
|
||
* 结果是可独立保存的副本,不保证以后仍有同一连接;Close_Connection应携带该副本的generation。
|
||
* 处理历史事件时使用事件内peer,不可将此处查到的新主站地址归到旧代次。 */
|
||
extern IEC104_TcpResult_t IEC104_TcpServer_Get_Remote_Address(
|
||
const IEC104_TcpServer_t *server, IEC104_TcpPeerInfo_t *peer, IEC104_TcpError_t *error);
|
||
|
||
/* 立即结束指定代次的当前主站,保留监听、服务队列和已有业务事件;不执行STOPDT或等待发送/ACK。
|
||
* generation来自原连接事件或Get_Remote_Address,0为INVALID_ARGUMENT;非零但与当前连接不符为STALE_GENERATION。
|
||
* 未Init为INVALID_STATE,无客户端为NOT_CONNECTED;拒绝结果均不关闭连接、不产生新的收尾事件。
|
||
* 成功复用断线收尾流程:交付剩余I帧/记录及DISCONNECTED,清空HIGH,保留现存NORMAL并恢复离线缓存。
|
||
* 主动断开不新增协议故障,不补造发送完成或ACK;部分/未确认NORMAL恢复后可能重复,由业务处理。
|
||
* 队列解除绑定异常返回INTERNAL_ERROR/QUEUE_STATE:客户端已结束,监听保留,须Close服务处理异常。
|
||
* 同一事件循环线程串行调用;返回后重新Prepare并移除旧客户端注册,后续Process继续接入新主站。
|
||
* 此操作不是封禁;对端可再次连接。Close才关闭整个服务并清空缓存。 */
|
||
extern IEC104_TcpResult_t IEC104_TcpServer_Close_Connection(
|
||
IEC104_TcpServer_t *server, uint64_t generation, IEC104_TcpError_t *error);
|
||
|
||
/* 取得端口、诊断、会话及HIGH/NORMAL队列快照,不执行I/O;Create后、Init失败后、Close后均可用。
|
||
* OK时写入status;INVALID_HANDLE/INVALID_ARGUMENT/BUSY保持status不变,但仍填写error。
|
||
* 会话/队列分别检查connection_status_valid/asdu_queue_status_valid,无效快照不妨碍读取其他服务状态。
|
||
* 队列统计在Init成功后即可读取,断线期间继续有效;Close后无效且清零,不返回历史队列占用。
|
||
* accept_paused及剩余时间按查询时刻计算;last_accept_error和连续失败计数用于观察接入异常。
|
||
* 所有调用须在同一线程串行执行;查询为常数时间,不消费ASDU或记录,也不改变发送调度。
|
||
* 接口失败诊断直接使用该次调用的error,统计数值不能代替提交返回值。 */
|
||
extern IEC104_TcpResult_t IEC104_TcpServer_Get_Status(
|
||
const IEC104_TcpServer_t *server, IEC104_TcpServerStatus_t *status, IEC104_TcpError_t *error);
|
||
|
||
/* OK表示完成收尾或本就未初始化;无效句柄/重入为INVALID_HANDLE/BUSY。
|
||
* 关闭网络、清空ASDU队列并撤销接入/发送重试期限,保留未读事件及接入历史诊断。
|
||
* 不等待业务发送或ACK;重复Close有效。事件读空后可以Init重开,或Destroy归还静态槽。
|
||
* 应用负责取消注册和定时器;关闭前已取得的socket及通知不可作为下一次Init的身份使用。 */
|
||
extern IEC104_TcpResult_t IEC104_TcpServer_Close(
|
||
IEC104_TcpServer_t *server, IEC104_TcpError_t *error);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
#endif /* IEC104_TCPSERVER_H */
|