Files
STM32F4-Base/IEC60870-Slave/104/IEC104_TcpServer.h
T
STF 7596f3371a
CI / L0 静态门禁 (pre-commit) (push) Failing after 44s
CI / L0.5 主机侧单测 (MSVC) (push) Failing after 42s
补充总召唤的广播地址,电度召唤+时钟同步业务的实现
2026-10-29 03:20:19 -07:00

199 lines
17 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 */