Files
STM32F4-Base/IEC60870-Slave/104/IEC104_TcpServer.c
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

2161 lines
111 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.
/*
* 子站TCP服务调度:管理监听调度、单条活动连接、收发预算及业务事件队列。
* 上层IEC104_Slave拥有本模块句柄,并把原始I帧事件包装为ASDU视图和共用发送接口。
* 本模块不依赖Slave的结构或业务函数;Slave只通过本模块的公开API驱动同一套调度。
* 本文件通过IEC104_Net执行网络操作,不包含操作系统socket类型或错误码。
* IEC104启停、序号、组帧和超时规则由Connection及其内部模块维护。
*
* 调用主线:Create -> Init -> Prepare/应用等待/Process/NextEvent -> Close/NextEvent -> Destroy。
* Poll组合相同的准备、等待、推进过程;只有等待步骤调用Net_Wait。
* 接收主线:recv -> Feed按消费量推进 -> 复制I帧事件 -> Release结束原文借用。
* 发送主线:事件入队、应答直接准备或入HIGH -> 共用Write_Output写入及Progress记账。
* 启停通知:START/STOP受理后立即排入ACTIVATED/DEACTIVATED,与CON发送完成解耦。
* 通知容量:普通、启停及收尾分别保证空间;全部通知按顺序保留,不覆盖、不合并。
* 启停专用槽满时暂缓受理下一个改变状态的ACT,NextEvent释放槽位后继续;已有CON仍可发送。
* I帧无普通空间时保留原帧并暂停续收,NextEvent释放容量后恢复;不因通知积压主动断连。
* 请求、回执及连接建立/断开事件不覆盖;协议期限不因等待业务消费而延长。
* 对象边界:Slave或独立TCP调用方只持有本模块句柄;Connection内含Manager、Receiver、Link及SendBuffer。
* NORMAL由本层缓存到服务队列;应答经Connection/SendManager验证会话后直接准备或入HIGH。
* 事件由NextEvent按值取出,应用主循环自行处理;TCP内部不调用业务处理函数。
*
* 平台差异由链接的Net实现处理;Connection只接收字节、发送进度和单调时间值。
* 暂不可读写、部分发送、EOF排空、故障收尾和重连策略仍由本调度层统一处理。
*/
#include "IEC104_TcpServer.h"
#include "IEC104_TcpServer_Config.h"
#include "IEC104_Connection.h"
#include "IEC104_Net.h"
#include <stddef.h>
#include <string.h>
/* 环内只保存头部和对应公开载荷的有效前缀;包含ABI填充,通过memcpy访问,允许跨环尾。 */
typedef struct {
uint64_t generation; /* 入队时的连接代次;断线后仍按原身份交付,不能替换为当前代次。 */
uint16_t payload_length; /* 头部之后的实际字节数,不含本结构;I帧只保存有效APDU前缀。 */
IEC104_TcpEventType_t type; /* 决定载荷布局和出队校验规则,不能把环中数据直接当公开union使用。 */
} TcpEventHeader_t;
/* 事件按有效字节存储,配置条数按最长载荷折算保证容量,短事件可容纳更多条。
* 普通区用于I帧、正常回执和建连;启停区保留状态通知;收尾区容纳一个保留I帧、全部记录和断开。
* 三类逻辑配额共用一个FIFO,避免断线或状态变化时因普通区已满而丢通知。 */
#define TCP_EVENT_I_PREFIX offsetof(IEC104_TcpIFrameEvent_t, apdu)
#define TCP_EVENT_I_MAX_PAYLOAD (TCP_EVENT_I_PREFIX + sizeof(((IEC104_TcpIFrameEvent_t *)0)->apdu))
#define TCP_EVENT_I_MAX_BYTES (sizeof(TcpEventHeader_t) + TCP_EVENT_I_MAX_PAYLOAD)
#define TCP_EVENT_I_MIN_BYTES (sizeof(TcpEventHeader_t) + TCP_EVENT_I_PREFIX + IEC104_APCI_LEN)
#define TCP_EVENT_RECORD_BYTES (sizeof(TcpEventHeader_t) + sizeof(IEC104_TcpRecordEvent_t))
#define TCP_EVENT_CONNECTION_BYTES (sizeof(TcpEventHeader_t) + sizeof(IEC104_TcpConnectionEvent_t))
#define TCP_EVENT_NORMAL_BYTES (IEC104_TCP_EVENT_NORMAL_CAPACITY * TCP_EVENT_I_MAX_BYTES)
#define TCP_EVENT_STATE_BYTES (IEC104_TCP_EVENT_STATE_RESERVE * TCP_EVENT_CONNECTION_BYTES)
#define TCP_EVENT_CLOSE_BYTES \
(TCP_EVENT_I_MAX_BYTES + IEC104_TCP_RECORD_CAPACITY * TCP_EVENT_RECORD_BYTES + TCP_EVENT_CONNECTION_BYTES)
#define TCP_EVENT_CAPACITY_BYTES (TCP_EVENT_NORMAL_BYTES + TCP_EVENT_STATE_BYTES + TCP_EVENT_CLOSE_BYTES)
/* 公开头文件只声明struct名称,完整成员在此定义;业务只能使用指针和公开接口。
* 静态池保存真实类型的对象,尺寸、对齐和地址稳定性由编译器保证。
* Connection内部有指向自身成员的借用指针,初始化后不能复制或移动整个server。
* queue及其字节数组随服务初始化,独立于Connection保存;无客户端时仍可缓存NORMAL原文。
* generation用于业务应答绑定;listen_token/client_token用于排除迟到的事件循环通知。
* 这些编号均不能只用socket数值替代,因为操作系统可能很快复用已关闭的socket值。 */
struct IEC104_TcpServer {
IEC104_NetSocket_t listen_socket; /* 监听句柄,客户端断开后继续保留。 */
IEC104_NetSocket_t client_socket; /* 唯一活动主站;无连接时为NET_INVALID_SOCKET。 */
IEC104_TcpPeerInfo_t peer; /* 接入时保存的对端地址及代次;收尾事件复制后清零。 */
IEC104_TcpServerConfig_t config; /* Init时复制的配置,运行期间不借用业务配置地址。 */
IEC104_Connection_t connection; /* 当前协议会话;负责104分帧、序号、启停和期限。 */
IEC104_AsduQueue_t queue; /* 两级ASDU原文队列,由本层管理跨会话生命周期。 */
IEC104_SendBufferControlFrame_t controls[IEC104_TCP_CONTROL_CAPACITY]; /* 借给SendBuffer的S/U帧槽。 */
IEC104_IFrameSendRecordStorage_t records[IEC104_TCP_RECORD_CAPACITY]; /* 借给Manager的I帧记录槽。 */
uint8_t event_bytes[TCP_EVENT_CAPACITY_BYTES]; /* 变长业务事件FIFO,Close后仍可读取。 */
uint8_t high_bytes[IEC104_TCP_HIGH_CAPACITY]; /* HIGH原文环,保存当前会话的请求应答。 */
uint8_t normal_bytes[IEC104_TCP_NORMAL_CAPACITY]; /* NORMAL服务原文环,跨断线保留,Close清空。 */
uint8_t rx_bytes[IEC104_TCP_RX_CAPACITY]; /* recv批量暂存区,包含尚未Feed的输入尾部。 */
uint32_t rx_begin; /* 未消费输入的起始下标。 */
uint32_t rx_end; /* 有效输入末尾的后一位置;区间为[rx_begin, rx_end)。 */
uint32_t rx_snapshot_remaining; /* 预算让出时已到达内核、尚未recv的固定字节数。 */
uint32_t event_head; /* 最早未读事件头部的字节下标。 */
uint32_t event_used; /* 头部及有效载荷占用字节数,跨环尾不留空隙。 */
uint16_t event_count; /* 普通、启停及收尾通知的未读总数。 */
uint16_t state_event_count; /* FIFO中尚未消费的启停通知数,单独占用启停配额。 */
uint64_t generation; /* 本静态槽的会话代次;Close、重新Init及槽复用均不重置。 */
uint64_t registration_serial; /* 本静态槽已分配的socket注册编号,Reset及槽复用不清零。 */
uint64_t listen_token; /* 当前监听的注册身份,也标识本次Init。 */
uint64_t client_token; /* 当前客户端的注册身份,不能以socket数值代替。 */
uint64_t accept_retry_at_ms; /* 仅now小于此期限时暂停接入;到期不依赖容量或额外解除动作。 */
uint64_t send_retry_at_ms; /* 发送无进展后的重试期限;到期主动重试,不依赖新的可写边沿。 */
uint32_t consecutive_accept_failures; /* 接入连续错误返回次数,饱和递增,OK/WOULD_BLOCK清零。 */
unsigned send_retry_count; /* 跨Process及快速应答累计连续OK+0/RETRY次数,达到预算后暂停。 */
unsigned event_budget; /* 本次推进剩余通知交付数;收尾交付不受预算限制。 */
IEC104_TcpFault_t local_fault; /* TcpServer自身的首次故障;不要求Connection了解通知FIFO。 */
uint16_t bound_port; /* 实际监听端口,配置port=0时记录系统分配值。 */
int last_socket_error; /* 历史后端诊断;本次API的失败原因通过独立error输出。 */
int last_accept_error; /* 最近一次接入错误,客户端收尾及接入成功不清除。 */
uint8_t is_initialized; /* 已成功初始化网络并监听;不表示当前有客户端。 */
uint8_t in_poll; /* 推进、应答发送、Close_Connection及Close执行中的重入标志,不是线程锁。 */
uint8_t queue_initialized; /* 服务队列已绑定存储且可用;解除旧绑定失败时置0,必须Close。 */
uint8_t rx_blocked; /* 最近Feed返回WAIT;暂不recv,先推进发送或期限。 */
uint8_t rx_event_pending; /* 已受理I帧因普通通知空间不足仍留在Receiver,暂停续收。 */
uint8_t rx_eof; /* 已收到有序EOF;拒绝新HIGH,停止选取未编号NORMAL后继续收尾。 */
uint8_t accept_pending; /* 接入就绪尚未耗尽,跨预算及暂停保留,支持边沿触发续跑。 */
uint8_t rx_ready; /* 接收就绪尚未读至WOULD_BLOCK,跨事件交付/预算保留。 */
uint8_t rx_snapshot_active; /* 续收旧快照,不把后来到达的输入加入旧批次。 */
uint8_t write_pending; /* 待发字节;发送暂停时屏蔽可写关注。 */
uint8_t event_yield; /* 本轮已交付足量通知,返回应用一次;下轮无条件恢复推进。 */
uint8_t process_pending; /* 本地状态改变后还需推进一次,不依赖新的socket边沿。 */
};
/* 池的占用信息与运行对象分开,Init重置运行状态不会归还正在使用的槽。 */
typedef struct {
IEC104_TcpServer_t server; /* 地址固定的运行对象,Create返回此成员地址。 */
uint8_t in_use; /* Create占用、Destroy归还;Close不会清除此标志。 */
} TcpServerSlot_t;
_Static_assert(IEC104_TCP_SERVER_CAPACITY > 0U, "Server pool must not be empty");
_Static_assert(sizeof(((IEC104_TcpPeerInfo_t *)0)->address) == sizeof(((IEC104_NetPeer_t *)0)->address),
"Public peer address storage must match the network endpoint");
_Static_assert(IEC104_TCP_RX_CAPACITY > 0U && IEC104_TCP_RX_CAPACITY <= UINT32_MAX,
"Receive storage must fit its indices");
/* 公开提交接口支持全部合法ASDU长度;每级均须容纳一条最大ASDU,包括条目头。 */
_Static_assert(IEC104_TCP_HIGH_CAPACITY >= IEC104_ASDU_HIGH_ENTRY_HEADER_LEN + IEC104_MAX_ASDU_LEN &&
IEC104_TCP_HIGH_CAPACITY <= UINT32_MAX, "HIGH queue must hold a maximum-length ASDU and fit uint32_t");
_Static_assert(IEC104_TCP_NORMAL_CAPACITY >= IEC104_ASDU_NORMAL_ENTRY_HEADER_LEN + IEC104_MAX_ASDU_LEN &&
IEC104_TCP_NORMAL_CAPACITY <= UINT32_MAX, "NORMAL queue must hold a maximum-length ASDU and fit uint32_t");
_Static_assert(IEC104_TCP_CONTROL_CAPACITY > 0U && IEC104_TCP_CONTROL_CAPACITY <= UINT16_MAX,
"Control storage must fit its indices");
_Static_assert(IEC104_TCP_RECORD_CAPACITY > 0U && IEC104_TCP_RECORD_CAPACITY <= IEC104_SEQUENCE_MASK,
"Invalid send record capacity");
static TcpServerSlot_t server_pool[IEC104_TCP_SERVER_CAPACITY];
/* 先比较池内地址,再读取占用状态;公开接口不解引用未经Create取得的对象。
* 只能判断该地址当前是否被占用;槽复用后无法识别旧指针副本,业务须在Destroy后停止使用旧句柄。 */
static int Server_Owned(const IEC104_TcpServer_t *server)
{
size_t index;
for (index = 0U; index < IEC104_TCP_SERVER_CAPACITY; ++index) {
if (server == &server_pool[index].server) {
return server_pool[index].in_use != 0U;
}
}
return 0;
}
/* 在调用方提供的存储中完整写出本次结果;不修改server,也不读取历史网络错误。
* 无效句柄也能返回详情,Destroy重置对象后仍可交付结果;成功明确清除旧诊断。 */
static IEC104_TcpResult_t Result_With_Error(
IEC104_TcpResult_t result, IEC104_TcpErrorStage_t stage, int socket_error, IEC104_TcpError_t *error)
{
if (error != NULL) {
error->result = result;
error->stage = stage;
error->socket_error = socket_error;
}
return result;
}
/* 服务操作共用句柄/重入检查;必须先确认池内所有权,再解引用server。
* 此处不要求已经Init,便于初始化失败后查询、Close以及Destroy。 */
static IEC104_TcpResult_t Check_Server(const IEC104_TcpServer_t *server, IEC104_TcpError_t *error)
{
if (!Server_Owned(server)) {
return Result_With_Error(IEC104_TCP_INVALID_HANDLE, IEC104_TCP_STAGE_VALIDATE, 0, error);
}
if (server->in_poll != 0U) {
return Result_With_Error(IEC104_TCP_BUSY, IEC104_TCP_STAGE_LIFECYCLE, 0, error);
}
return Result_With_Error(IEC104_TCP_OK, IEC104_TCP_STAGE_NONE, 0, error);
}
/* 只用于没有网络资源及未读事件的对象;保留业务代次和注册编号,隔离槽复用前的通知。 */
static void Reset_Server(IEC104_TcpServer_t *server)
{
uint64_t generation = server->generation;
uint64_t registration_serial = server->registration_serial;
memset(server, 0, sizeof(*server));
server->generation = generation;
server->registration_serial = registration_serial;
server->listen_socket = IEC104_NET_INVALID_SOCKET;
server->client_socket = IEC104_NET_INVALID_SOCKET;
}
/* 每轮Process最多执行32次Feed和32次send;Poll复用同一轮Process。
* Feed和send各有独立预算;接收WAIT时的解阻发送与正常发送共用send预算,持续输入也不能无限占用线程。 */
#define TCP_IO_BUDGET 32U
/* 额外连接也计入accept预算,避免连接请求持续到达时饿死当前主站收发。 */
#define TCP_ACCEPT_BUDGET 4U
/* 接入资源暂缺或整轮仅有RETRY时暂停100毫秒,不阻塞当前客户端收发,不提供业务配置项。 */
#define TCP_ACCEPT_RETRY_DELAY_MS 100U
/* 连续无进展最多尝试4次,然后暂停发送10毫秒;正常WOULD_BLOCK仍等待可写通知。 */
#define TCP_SEND_RETRY_BUDGET 4U
#define TCP_SEND_RETRY_DELAY_MS 10U
/* 有界交付后及时交还业务消费NextEvent,避免一批粘包长期占用事件循环。 */
#define TCP_EVENT_BUDGET 8U
/* 配置仍表示保证条数;字节索引用uint32_t,公开未读通知数仍用uint16_t。 */
_Static_assert(IEC104_TCP_EVENT_STATE_RESERVE > 0U && IEC104_TCP_EVENT_STATE_RESERVE <= UINT16_MAX,
"State event reserve must fit in uint16_t");
_Static_assert(IEC104_TCP_EVENT_CAPACITY > IEC104_TCP_EVENT_CLOSE_RESERVE + IEC104_TCP_EVENT_STATE_RESERVE,
"Event queue must leave room for normal events");
_Static_assert(IEC104_TCP_EVENT_CAPACITY <= UINT16_MAX,
"Event guarantees must fit in uint16_t");
_Static_assert(TCP_EVENT_I_MAX_PAYLOAD <= UINT16_MAX &&
sizeof(IEC104_TcpRecordEvent_t) <= UINT16_MAX && sizeof(IEC104_TcpConnectionEvent_t) <= UINT16_MAX,
"Event payload lengths must fit in uint16_t");
_Static_assert(TCP_EVENT_I_MAX_BYTES >= TCP_EVENT_RECORD_BYTES &&
TCP_EVENT_I_MAX_BYTES >= TCP_EVENT_CONNECTION_BYTES &&
TCP_EVENT_I_MIN_BYTES <= TCP_EVENT_RECORD_BYTES && TCP_EVENT_I_MIN_BYTES <= TCP_EVENT_CONNECTION_BYTES,
"I frame bounds must cover all event sizes");
_Static_assert(TCP_EVENT_CAPACITY_BYTES <= UINT32_MAX,
"Event byte storage must fit its indices");
_Static_assert(TCP_EVENT_CAPACITY_BYTES / TCP_EVENT_I_MIN_BYTES <= UINT16_MAX,
"Even shortest events must fit the public event count");
/* 仅ACTIVATED/DEACTIVATED占启停配额;CONNECTED/DISCONNECTED沿普通/收尾容量规则计账。 */
static int Is_State_Event(IEC104_TcpEventType_t type)
{
return type == IEC104_TCP_EVENT_ACTIVATED || type == IEC104_TCP_EVENT_DEACTIVATED;
}
/* 只检查游标和计数;每项的类型、长度在出队前校验,不遍历整个FIFO。 */
static int Event_Queue_Valid(const IEC104_TcpServer_t *server)
{
return server->event_head < sizeof(server->event_bytes) &&
server->event_used <= sizeof(server->event_bytes) &&
server->state_event_count <= IEC104_TCP_EVENT_STATE_RESERVE &&
server->state_event_count <= server->event_count &&
server->state_event_count * TCP_EVENT_CONNECTION_BYTES <= server->event_used &&
server->event_count <= server->event_used / TCP_EVENT_I_MIN_BYTES &&
(server->event_count == 0U) == (server->event_used == 0U);
}
/* 返回环内偏移前进length后的下标;调用方保证index在环内、length不超过整个环容量。
* 先比较尾部余量,避免index+length溢出;头部和载荷均允许跨环尾。 */
static uint32_t Event_Ring_Advance(uint32_t index, uint32_t length)
{
uint32_t tail_bytes = (uint32_t)TCP_EVENT_CAPACITY_BYTES - index;
return length >= tail_bytes ? length - tail_bytes : index + length;
}
/**
* 功能:从事件字节环复制length字节,跨环尾时分两段读取,不移动FIFO游标。
* 前提:index在环内,length不超过环容量,data可写空间充足且不与环存储重叠。
* 说明:调用方负责校验队列和条目长度;memcpy避免未对齐的结构体指针访问。
*/
static void Event_Ring_Read(const IEC104_TcpServer_t *server, uint32_t index, void *data, uint32_t length)
{
uint32_t first_length = (uint32_t)sizeof(server->event_bytes) - index;
if (first_length > length) {
first_length = length;
}
memcpy(data, server->event_bytes + index, first_length);
if (length > first_length) {
memcpy((uint8_t *)data + first_length, server->event_bytes, length - first_length);
}
}
/**
* 功能:将length字节复制到事件环,跨环尾时分两段写入,不修改事件计数或占用量。
* 前提:调用方已检查目标空间,index在环内、length不超过环容量,输入不与环存储重叠。
* 说明:Append_Event完成头部和载荷复制后统一提交占用量,避免把未写完的条目发布给出队接口。
*/
static void Event_Ring_Write(IEC104_TcpServer_t *server, uint32_t index, const void *data, uint32_t length)
{
uint32_t first_length = (uint32_t)sizeof(server->event_bytes) - index;
if (first_length > length) {
first_length = length;
}
memcpy(server->event_bytes + index, data, first_length);
if (length > first_length) {
memcpy(server->event_bytes, (const uint8_t *)data + first_length, length - first_length);
}
}
/**
* 功能:按事件类型检查环中载荷长度,入队和出队共用此格式约束。
* 返回值:1为长度匹配,0为未知类型或长度越界;本函数不检查载荷内部字段。
* 说明:I帧可变长,其余载荷为固定结构;APDU长度与载荷长度的一致性由NextEvent另行核对。
*/
static int Event_Payload_Length_Valid(IEC104_TcpEventType_t type, uint16_t length)
{
switch (type) {
case IEC104_TCP_EVENT_I_FRAME:
return length >= TCP_EVENT_I_PREFIX + IEC104_APCI_LEN && length <= TCP_EVENT_I_MAX_PAYLOAD;
case IEC104_TCP_EVENT_RECORD:
return length == sizeof(IEC104_TcpRecordEvent_t);
case IEC104_TCP_EVENT_CONNECTED:
case IEC104_TCP_EVENT_DISCONNECTED:
case IEC104_TCP_EVENT_ACTIVATED:
case IEC104_TCP_EVENT_DEACTIVATED:
return length == sizeof(IEC104_TcpConnectionEvent_t);
default:
return 0;
}
}
/* 非启停项均计入普通占用,包括上一连接尚未读完的收尾项。
* 收尾后该占用可超过普通配额;降回配额内才能接入新连接并复用收尾预留。
* 此配额与允许覆盖旧ASDU原文的NORMAL发送队列独立,通知FIFO不覆盖旧项。 */
static int Ordinary_Event_Room(const IEC104_TcpServer_t *server, size_t event_bytes)
{
size_t state_used = server->state_event_count * TCP_EVENT_CONNECTION_BYTES;
size_t ordinary_used;
if (server->event_used < state_used) {
return 0;
}
ordinary_used = server->event_used - state_used;
return ordinary_used <= TCP_EVENT_NORMAL_BYTES && event_bytes <= TCP_EVENT_NORMAL_BYTES - ordinary_used &&
server->event_used <= TCP_EVENT_CAPACITY_BYTES - TCP_EVENT_CLOSE_BYTES &&
event_bytes <= TCP_EVENT_CAPACITY_BYTES - TCP_EVENT_CLOSE_BYTES - server->event_used;
}
/* 三个启停入口共用检查:保留条数配额,写入后仍须留下完整收尾空间。 */
static int State_Event_Room(const IEC104_TcpServer_t *server)
{
return server->state_event_count < IEC104_TCP_EVENT_STATE_RESERVE &&
server->event_used <= TCP_EVENT_CAPACITY_BYTES - TCP_EVENT_CLOSE_BYTES &&
TCP_EVENT_CONNECTION_BYTES <= TCP_EVENT_CAPACITY_BYTES - TCP_EVENT_CLOSE_BYTES - server->event_used;
}
/* 新会话要有建连通知容量、完整启停配额及完整收尾预留;旧启停通知须先读完。 */
static int Can_Accept_Connection(const IEC104_TcpServer_t *server)
{
return Ordinary_Event_Room(server, TCP_EVENT_CONNECTION_BYTES) && server->state_event_count == 0U;
}
/* 预算或本类配额刚用尽时只让出当前一轮,让应用及时消费通知。
* 普通区放不下最长通知才视为配额用尽;下一轮仍按实际长度交付,启停独立检查。 */
static void Note_Event_Delivery(IEC104_TcpServer_t *server, int state_event)
{
if (server->event_budget > 0U) {
--server->event_budget;
}
if (server->event_budget == 0U || (!state_event && !Ordinary_Event_Room(server, TCP_EVENT_I_MAX_BYTES)) ||
(state_event && server->state_event_count >= IEC104_TCP_EVENT_STATE_RESERVE)) {
server->event_yield = 1U;
server->process_pending = 1U;
}
}
/* 调用方先检查对应配额;这里再检查物理空间,不直接覆盖已有通知。
* 活动连接最多占普通字节区+启停字节区;Drop_Client另外最多追加RECORD_CAPACITY+2项。
* 改变状态的ACT在受理前检查启停配额,确保变化后有空间保存通知。
* 先完成全部检查,再复制头部和载荷,最后提交占用量;不向调用方暴露环内地址。 */
static int Append_Event(IEC104_TcpServer_t *server, IEC104_TcpEventType_t type,
const void *payload, uint16_t payload_length)
{
TcpEventHeader_t header;
uint32_t index;
uint32_t event_bytes = (uint32_t)sizeof(header) + payload_length;
int state_event = Is_State_Event(type);
if (payload == NULL || server->generation == 0U || !Event_Payload_Length_Valid(type, payload_length) ||
!Event_Queue_Valid(server) || server->event_count == UINT16_MAX ||
event_bytes > sizeof(server->event_bytes) - server->event_used ||
(state_event && !State_Event_Room(server))) {
return 0;
}
memset(&header, 0, sizeof(header));
header.generation = server->generation;
header.payload_length = payload_length;
header.type = type;
index = Event_Ring_Advance(server->event_head, server->event_used);
Event_Ring_Write(server, index, &header, sizeof(header));
index = Event_Ring_Advance(index, sizeof(header));
Event_Ring_Write(server, index, payload, payload_length);
server->event_used += event_bytes;
++server->event_count;
if (state_event) {
++server->state_event_count;
}
return 1;
}
/* 复制完整APDU和必要的数值元数据,绝不把Receiver内部地址保存到事件中。 */
static int Queue_I_Frame(IEC104_TcpServer_t *server, const IEC104_ConnectionReceivedFrame_t *frame)
{
IEC104_TcpIFrameEvent_t copy;
if (frame->frame.apdu == NULL || frame->frame.apdu_length < IEC104_APCI_LEN ||
frame->frame.apdu_length > sizeof(copy.apdu)) {
return 0;
}
memset(&copy, 0, TCP_EVENT_I_PREFIX);
copy.receive_serial = frame->receive_serial;
copy.apdu_length = frame->frame.apdu_length;
copy.send_number = frame->frame.apci.send_number;
copy.receive_number = frame->frame.apci.receive_number;
memcpy(copy.apdu, frame->frame.apdu, copy.apdu_length);
return Append_Event(server, IEC104_TCP_EVENT_I_FRAME, &copy,
(uint16_t)(TCP_EVENT_I_PREFIX + copy.apdu_length));
}
/* 对外发送记录只保留业务需要的事实,逐字段转换,不绑定Manager记录布局。 */
static void Copy_Send_Record(IEC104_TcpSendRecord_t *copy, const IEC104_IFrameSendRecord_t *record)
{
copy->frame_serial = record->frame_id.frame_serial;
copy->queued_at_ms = record->queued_at_ms;
copy->written_at_ms = record->written_at_ms;
copy->send_number = record->i_frame_snapshot.send_number;
copy->receive_number = record->i_frame_snapshot.receive_number;
copy->apdu_length = record->i_frame_snapshot.frame_length;
copy->priority = record->asdu_priority;
copy->local_send_completed = record->local_send_completed;
copy->peer_acknowledged = record->peer_acknowledged;
}
/* 显式映射故障分类,内部新增/调整枚举不会悄悄改变业务接口的数值语义。 */
static IEC104_TcpFault_t Public_Fault(IEC104_ConnectionFault_t fault)
{
switch (fault) {
case IEC104_CONNECTION_FAULT_NONE: return IEC104_TCP_FAULT_NONE;
case IEC104_CONNECTION_FAULT_FRAME: return IEC104_TCP_FAULT_FRAME;
case IEC104_CONNECTION_FAULT_PROTOCOL: return IEC104_TCP_FAULT_PROTOCOL;
case IEC104_CONNECTION_FAULT_INTERNAL: return IEC104_TCP_FAULT_INTERNAL;
case IEC104_CONNECTION_FAULT_TRANSPORT: return IEC104_TCP_FAULT_TRANSPORT;
case IEC104_CONNECTION_FAULT_LOCAL_SEND_TIMEOUT: return IEC104_TCP_FAULT_LOCAL_SEND_TIMEOUT;
case IEC104_CONNECTION_FAULT_I_ACK_TIMEOUT: return IEC104_TCP_FAULT_I_ACK_TIMEOUT;
case IEC104_CONNECTION_FAULT_TESTFR_TIMEOUT: return IEC104_TCP_FAULT_TESTFR_TIMEOUT;
case IEC104_CONNECTION_FAULT_STOP_TIMEOUT: return IEC104_TCP_FAULT_STOP_TIMEOUT;
default: return IEC104_TCP_FAULT_INTERNAL;
}
}
/* 只登记本层首次故障;公开快照仍优先保留Connection已经登记的更早故障。 */
static void Note_Local_Fault(IEC104_TcpServer_t *server, IEC104_TcpFault_t fault)
{
if (server->local_fault == IEC104_TCP_FAULT_NONE) {
server->local_fault = fault;
}
}
/* 通过Connection查询,再生成稳定的业务快照;查询失败时不修改输出。 */
static int Read_Connection_Status(const IEC104_TcpServer_t *server, IEC104_TcpConnectionStatus_t *copy)
{
IEC104_ConnectionStatus_t status;
IEC104_TcpConnectionStatus_t snapshot;
if (server->generation == 0U || IEC104_Connection_Get_Status(
&server->connection, server->generation, &status) != IEC104_CONNECTION_OK) {
return 0;
}
memset(&snapshot, 0, sizeof(snapshot));
switch (status.phase) {
case IEC104_CONNECTION_UNINITIALIZED: snapshot.phase = IEC104_TCP_SESSION_NONE; break;
case IEC104_CONNECTION_ACTIVE: snapshot.phase = IEC104_TCP_SESSION_ACTIVE; break;
case IEC104_CONNECTION_FAILED: snapshot.phase = IEC104_TCP_SESSION_FAILED; break;
case IEC104_CONNECTION_CLOSED: snapshot.phase = IEC104_TCP_SESSION_CLOSED; break;
default: return 0;
}
snapshot.fault = Public_Fault(status.fault);
if (snapshot.fault == IEC104_TCP_FAULT_NONE && server->local_fault != IEC104_TCP_FAULT_NONE) {
snapshot.fault = server->local_fault;
if (snapshot.phase == IEC104_TCP_SESSION_ACTIVE) {
snapshot.phase = IEC104_TCP_SESSION_FAILED;
}
}
snapshot.pending_send_records = status.send_record_count;
snapshot.data_transfer_started = status.dt_state == IEC104_DT_STARTED;
*copy = snapshot;
return 1;
}
/**
* 功能:通过队列的只读接口汇总HIGH/NORMAL积压及容量,不向业务暴露队列布局。
* 输出参数:high/normal仅在两个优先级均查询成功时写入,失败保持原值。
* 返回值:1统计有效;0表示服务队列尚不可用、代次不符或内部查询异常。
* 说明:队列随服务存在,离线代次0也能查询;不能要求Connection仍然有效。
* 待发数=保留原文数-其中已提交待ACK数,不把NORMAL已提交待ACK原文或HIGH发送记录混入待发数。
* 容量来自本服务实际借给队列的数组,空闲量通过队列接口查询;不读取内部环下标。
* 所有服务接口串行执行,期间无修改或回调,多个常数时间查询属于同一状态快照。
*/
static int Read_Asdu_Queues_Status(
const IEC104_TcpServer_t *server, IEC104_TcpAsduQueueStatus_t *high, IEC104_TcpAsduQueueStatus_t *normal)
{
IEC104_AsduQueueStatus_t status;
IEC104_TcpAsduQueueStatus_t snapshots[IEC104_ASDU_PRIORITY_COUNT];
unsigned priority;
if (server->is_initialized == 0U || server->queue_initialized == 0U ||
IEC104_AsduQueue_Get_Status(&server->queue, &status) != IEC104_ASDU_QUEUE_OK ||
status.connection_generation !=
(server->client_socket == IEC104_NET_INVALID_SOCKET ? 0U : server->generation)) {
return 0;
}
memset(snapshots, 0, sizeof(snapshots));
snapshots[IEC104_ASDU_PRIORITY_HIGH].capacity_bytes = (uint32_t)sizeof(server->high_bytes);
snapshots[IEC104_ASDU_PRIORITY_NORMAL].capacity_bytes = (uint32_t)sizeof(server->normal_bytes);
for (priority = 0U; priority < IEC104_ASDU_PRIORITY_COUNT; ++priority) {
IEC104_TcpAsduQueueStatus_t *snapshot = &snapshots[priority];
if (IEC104_AsduQueue_Get_Free_Bytes(&server->queue, (IEC104_AsduPriority_t)priority,
&snapshot->free_bytes) != IEC104_ASDU_QUEUE_OK ||
snapshot->free_bytes > snapshot->capacity_bytes ||
status.retained_unacked_count[priority] > status.entry_count[priority]) {
return 0;
}
snapshot->pending_asdu_count = status.entry_count[priority] - status.retained_unacked_count[priority];
snapshot->stored_asdu_count = status.entry_count[priority];
snapshot->used_bytes = snapshot->capacity_bytes - snapshot->free_bytes;
}
*high = snapshots[IEC104_ASDU_PRIORITY_HIGH];
*normal = snapshots[IEC104_ASDU_PRIORITY_NORMAL];
return 1;
}
/**
* 功能:读取协议及接入/发送重试期限使用的单调毫秒时刻。
* 返回值:Net后端提供的单调毫秒值,不是日历时间。
* 说明:所有Connection时间参数使用同一时间源,修改系统日期不改变计时基准。
*/
static uint64_t Now_Ms(void)
{
return IEC104_Net_Now_Ms();
}
/* Prepare、实际接入和状态查询共用严格小于判断;now等于期限即已到期。 */
static int Accept_Retry_Paused(const IEC104_TcpServer_t *server, uint64_t now_ms)
{
return now_ms < server->accept_retry_at_ms;
}
/* 仅在实际接入失败后调用,保留待处理标志,到期后无需新的监听边沿即可重试。 */
static void Pause_Accept(IEC104_TcpServer_t *server)
{
server->accept_retry_at_ms = Now_Ms() + TCP_ACCEPT_RETRY_DELAY_MS;
server->accept_pending = 1U;
}
/* Prepare与真正send前共用比较;等于期限即允许重试,不能用就绪提示提前解除暂停。 */
static int Send_Retry_Paused(const IEC104_TcpServer_t *server, uint64_t now_ms)
{
return now_ms < server->send_retry_at_ms;
}
/* 清除无进展计数及暂停期限;实际写出、转为等待可写、无待发帧或连接切换时不沿用旧退避状态。
* 不清process_pending,其他收发或通知处理可能仍需要下一轮推进。 */
static void Reset_Send_Retry(IEC104_TcpServer_t *server)
{
server->send_retry_at_ms = 0U;
server->send_retry_count = 0U;
}
/* 只在实际send返回OK+0或RETRY后调用,预算跨所有发送入口累计。
* 暂停期间不再新增发送续跑,也不清除其他工作已经登记的process_pending。 */
static void Schedule_Send_Retry(IEC104_TcpServer_t *server)
{
if (++server->send_retry_count >= TCP_SEND_RETRY_BUDGET) {
server->send_retry_count = 0U;
server->send_retry_at_ms = Now_Ms() + TCP_SEND_RETRY_DELAY_MS;
} else {
server->process_pending = 1U;
}
}
/**
* 功能:在申请系统资源前检查配置范围及地址字符串是否有结束符。
* 输入参数:config——待检查配置,允许NULL。
* 返回值:1通过基础检查,0非法;不修改配置或协议状态。
* 说明:此处不解析IPv4内容,具体地址格式由Init中的Net_Listen继续检查。
*/
static int Config_Valid(const IEC104_TcpServerConfig_t *config)
{
const IEC104_ProtocolConfig_t *protocol;
if (config == NULL || memchr(config->bind_address, '\0', sizeof(config->bind_address)) == NULL) {
return 0;
}
protocol = &config->protocol;
return protocol->link.k > 0U && protocol->link.k <= IEC104_SEQUENCE_MASK &&
protocol->link.w > 0U && protocol->link.w <= IEC104_SEQUENCE_MASK &&
protocol->link.t1_ms > 0U && protocol->link.t2_ms > 0U && protocol->link.t3_ms > 0U &&
protocol->link.t2_ms < protocol->link.t1_ms &&
protocol->local_send_timeout_ms > 0U && protocol->stop_timeout_ms > 0U;
}
/**
* 功能:排入TCP建连或断开事件,复制协议状态和该连接的对端地址。
* 输入参数:server——当前服务;connected——1建连,0断开。
* records_saved——收尾时已成功保存为RECORD事件的内部记录数;建连传0。
* 返回值:1已入队,0物理容量或收尾计数异常;调用方须结束会话或停止服务。
* 说明:调用方已保证事件容量;查询失败或收尾计数不一致时status_valid为0。
* 收尾记录在Close前仍占内部槽,断开快照须扣除其中已成功转为事件的记录。
* socket_error为0不保证没有协议故障,仍须检查有效快照中的fault。
*/
static int Notify_Connection(IEC104_TcpServer_t *server, int connected, uint16_t records_saved)
{
IEC104_TcpConnectionEvent_t event;
int count_valid = 1;
memset(&event, 0, sizeof(event));
event.status_valid = (uint8_t)Read_Connection_Status(server, &event.status);
if (!connected && event.status_valid != 0U) {
if (records_saved > event.status.pending_send_records) {
/* 不让计数下溢,也不把不一致的快照当成有效状态交付。 */
event.status_valid = 0U;
count_valid = 0;
} else {
event.status.pending_send_records = (uint16_t)(event.status.pending_send_records - records_saved);
}
}
event.peer = server->peer;
event.socket_error = server->last_socket_error;
if (!Append_Event(server, connected ? IEC104_TCP_EVENT_CONNECTED : IEC104_TCP_EVENT_DISCONNECTED,
&event, sizeof(event))) {
return 0;
}
if (connected) {
Note_Event_Delivery(server, 0);
}
return count_valid;
}
/**
* 功能:实现Connection启停观察接口的容量检查,在受理改变状态的ACT前确认通知可保存。
* 输入参数:context——注册观察接口时传入的TcpServer地址,生命周期由本服务保证。
* 返回值:1可产生状态通知;0暂缓受理,等待NextEvent释放启停专用配额。
* 说明:此处只查容量,不通知业务、不改变协议;已有CON的发送不重新占用这个配额。
*/
static int Transfer_Event_Room(void *context)
{
const IEC104_TcpServer_t *server = (const IEC104_TcpServer_t *)context;
return State_Event_Room(server);
}
/**
* 功能:实现Connection启停观察接口,在状态提交后复制ACTIVATED/DEACTIVATED通知。
* 输入参数:context——所属TcpServer;state——Connection已经提交的新数据传输状态。
* 返回值:1通知已保存并扣减交付预算;0表示容量、状态或内部入队异常。
* 说明:只读取得协议快照,核对后立即入FIFO;不调用业务,不等CON发送完成。
* 同状态的重复ACT不触发本通知,是否真正改变状态由Connection决定。
*/
static int Notify_Transfer_State(void *context, IEC104_DataTransferState_t state)
{
IEC104_TcpServer_t *server = (IEC104_TcpServer_t *)context;
IEC104_TcpConnectionEvent_t event;
if (!State_Event_Room(server)) {
return 0;
}
memset(&event, 0, sizeof(event));
if (!Read_Connection_Status(server, &event.status) ||
event.status.data_transfer_started != (state == IEC104_DT_STARTED)) {
return 0;
}
event.status_valid = 1U;
event.peer = server->peer;
if (!Append_Event(server, state == IEC104_DT_STARTED ?
IEC104_TCP_EVENT_ACTIVATED : IEC104_TCP_EVENT_DEACTIVATED, &event, sizeof(event))) {
return 0;
}
Note_Event_Delivery(server, 1);
return 1;
}
/**
* 功能:结束当前客户端,保留监听socket供后续连接使用。
* 输入参数:transport_closed——非零时登记传输故障,不表示普通有序EOF;
* socket_error——本次网络错误码,正常关闭或纯协议故障时可为0。
* 说明:须在同步send/recv返回、无未完成I/O借用时调用;无客户端时直接返回。
* 先关闭socket并禁止HIGH入队,再复制剩余I帧、记录及断开通知,最后Close协议并解除队列绑定。
* 使用专门预留的事件容量,即使普通区满也不覆盖旧事件;断开事件最后入队。
* 只统计成功保存的记录供断开快照扣除;读取或入队失败仍按收尾异常处理。
* 已登记的协议/发送故障保持不变;记录中的本地完成和ACK事实不做补造。
*/
static void Drop_Client(IEC104_TcpServer_t *server, int transport_closed, int socket_error)
{
IEC104_IFrameSendRecord_t record;
IEC104_ConnectionReceivedFrame_t frame;
uint16_t offset;
uint16_t records_saved = 0U;
int events_saved = 1;
if (server->client_socket == IEC104_NET_INVALID_SOCKET) {
return;
}
server->last_socket_error = socket_error;
if (transport_closed && IEC104_Connection_Handle_Transport_Closed(
&server->connection, server->generation) != IEC104_CONNECTION_FATAL) {
Note_Local_Fault(server, IEC104_TCP_FAULT_INTERNAL);
}
(void)IEC104_Net_Close(server->client_socket, NULL);
server->client_socket = IEC104_NET_INVALID_SOCKET;
server->client_token = 0U;
/* Feed/Tick可能已受理一个尚未转移的I帧;使用收尾预留保存,不覆盖普通通知。 */
if (IEC104_Connection_Peek_Received_Frame(&server->connection, server->generation, &frame) ==
IEC104_CONNECTION_FRAME_READY) {
events_saved = Queue_I_Frame(server, &frame);
}
for (offset = 0U; offset < IEC104_TCP_RECORD_CAPACITY; ++offset) {
IEC104_TcpRecordEvent_t event;
IEC104_ConnectionResult_t result = IEC104_Connection_Get_Record_By_Offset(
&server->connection, server->generation, offset, &record);
if (result == IEC104_CONNECTION_EMPTY) {
break;
}
if (result != IEC104_CONNECTION_OK) {
events_saved = 0;
break;
}
memset(&event, 0, sizeof(event));
Copy_Send_Record(&event.value, &record);
event.closing = 1U;
if (!Append_Event(server, IEC104_TCP_EVENT_RECORD, &event, sizeof(event))) {
events_saved = 0;
break;
}
records_saved++;
}
if (!events_saved) {
Note_Local_Fault(server, IEC104_TCP_FAULT_INTERNAL);
}
if (!Notify_Connection(server, 0, records_saved)) {
events_saved = 0;
Note_Local_Fault(server, IEC104_TCP_FAULT_INTERNAL);
}
/* socket已关闭,事件已取得独立副本;不让Get_Remote_Address把旧地址当成下一会话的当前地址。 */
memset(&server->peer, 0, sizeof(server->peer));
/* Manager结束后才能解除队列的旧会话记账;NORMAL立即恢复为离线待发,HIGH不跨连接。
* 失败时保留原存储供Close清理,停止服务推进,不能悄悄Init而丢弃已缓存的NORMAL。 */
if (IEC104_Connection_Close(&server->connection, server->generation) != IEC104_CONNECTION_OK ||
IEC104_AsduQueue_Detach(&server->queue, server->generation) != IEC104_ASDU_QUEUE_OK || !events_saved) {
server->queue_initialized = 0U;
}
server->rx_begin = 0U;
server->rx_end = 0U;
server->rx_snapshot_remaining = 0U;
server->rx_snapshot_active = 0U;
server->rx_blocked = 0U;
server->rx_event_pending = 0U;
server->rx_eof = 0U;
server->rx_ready = 0U;
server->write_pending = 0U;
Reset_Send_Retry(server);
server->event_yield = 0U;
server->process_pending = 0U;
}
/* 本地内部状态异常单独登记,不伪造socket错误或协议ACK;通知容量不足不调用本函数。
* Connection已有的更早故障仍由Read_Connection_Status优先交付;本地原因保留到下一会话。
* 收尾只复制当前已受理I帧及实际发送记录,不为未受理TCP字节生成通知。 */
static int Drop_Local_Fault(IEC104_TcpServer_t *server, IEC104_TcpFault_t fault)
{
Note_Local_Fault(server, fault);
Drop_Client(server, 0, 0);
return 0;
}
/**
* 功能:统一处理内部Connection调用的致命结果,避免关闭后继续访问当前连接。
* 返回值:ERROR/FATAL时收尾并返回0;其余结果返回1,包括正常EMPTY和WAIT。
* 说明:FATAL已带首次故障,直接保留;内部调用出现ERROR时按INTERNAL收尾,不伪造传输故障。
* 返回1仅表示可以继续调度,不表示有数据、操作已完成或业务已经成功。
*/
static int Connection_Result_Valid(IEC104_TcpServer_t *server, IEC104_ConnectionResult_t result)
{
if (result == IEC104_CONNECTION_ERROR) {
return Drop_Local_Fault(server, IEC104_TCP_FAULT_INTERNAL);
}
if (result == IEC104_CONNECTION_FATAL) {
Drop_Client(server, 0, 0);
return 0;
}
return 1;
}
/**
* 功能:将当前已受理I帧复制到事件队列,再结束该帧原文的借用。
* 返回值:1连接仍可继续,无就绪帧或本轮交付预算已用完也返回1;0表示已收尾。
* 说明:普通通知空间不足时保留已受理帧,暂停后续Feed/recv;不丢弃、不回放、不主动断连。
* NextEvent腾出空间后由Process重试;等待期间继续发送及检查原有协议期限。
* TCP有序,保留I帧会挡住后续S/U,应用须及时消费;关闭时由收尾预留保存该帧。
* 复制成功后业务仅使用事件副本,无需再Release或操作内部Connection。
*/
static int Deliver_I_Frame(IEC104_TcpServer_t *server)
{
IEC104_ConnectionReceivedFrame_t frame;
IEC104_ConnectionResult_t result = IEC104_Connection_Peek_Received_Frame(
&server->connection, server->generation, &frame);
if (result == IEC104_CONNECTION_FRAME_READY) {
if (!Ordinary_Event_Room(server, sizeof(TcpEventHeader_t) + TCP_EVENT_I_PREFIX + frame.frame.apdu_length)) {
server->rx_event_pending = 1U;
return 1;
}
server->rx_event_pending = 0U;
if (server->event_yield != 0U) {
server->process_pending = 1U;
return 1;
}
if (!Queue_I_Frame(server, &frame)) {
return Drop_Local_Fault(server, IEC104_TCP_FAULT_INTERNAL);
}
result = IEC104_Connection_Release_Received_Frame(
&server->connection, server->generation, frame.receive_serial);
/* 本轮会把新事件交还业务;业务处理后还须续收,哪怕此前的就绪已暂时耗尽。 */
server->process_pending = 1U;
Note_Event_Delivery(server, 0);
}
return Connection_Result_Valid(server, result);
}
/**
* 功能:按发送顺序交付已完整写出且收到有效ACK的记录,并释放记录槽。
* 输出参数:made_progress非NULL时,成功回收至少一条记录则置1;否则保留原值。
* 返回值:1处理完成或队首尚不可消费,0表示内部错误已触发收尾。
* 说明:每次最多检查记录总容量次;EMPTY表示无记录,WAIT表示队首尚待确认。
* 复制为closing=0的记录事件后才回收记录槽;通知满/预算用完则保留记录。
* 有效ACK在协议处理中已登记,回执暂存不会要求主站重发ACK或阻止STOP确认。
* 等待空间的I帧先交付,回执不得利用较短载荷插到它前面。
*/
static int Consume_Records(IEC104_TcpServer_t *server, int *made_progress)
{
IEC104_IFrameSendRecord_t record;
unsigned count;
for (count = 0U; count < IEC104_TCP_RECORD_CAPACITY && server->rx_event_pending == 0U &&
server->event_yield == 0U && Ordinary_Event_Room(server, TCP_EVENT_RECORD_BYTES); ++count) {
IEC104_TcpRecordEvent_t event;
IEC104_ConnectionResult_t result = IEC104_Connection_Peek_Confirmed_Record(
&server->connection, server->generation, &record);
if (result == IEC104_CONNECTION_EMPTY || result == IEC104_CONNECTION_WAIT) {
return 1;
}
if (!Connection_Result_Valid(server, result)) {
return 0;
}
memset(&event, 0, sizeof(event));
Copy_Send_Record(&event.value, &record);
if (!Append_Event(server, IEC104_TCP_EVENT_RECORD, &event, sizeof(event))) {
return Drop_Local_Fault(server, IEC104_TCP_FAULT_INTERNAL);
}
if (!Connection_Result_Valid(server, IEC104_Connection_Remove_Confirmed_Record(
&server->connection, record.frame_id))) {
return 0;
}
if (made_progress != NULL) {
*made_progress = 1;
}
Note_Event_Delivery(server, 0);
}
return 1;
}
/**
* 功能:将rx_bytes中的未消费字节交给Connection,处理分包、粘包及协议背压。
* 输入/输出参数:budget——本轮Process剩余Feed次数,每次尝试都会扣减。
* 返回值:1连接仍可继续,包含WAIT及预算耗尽;0表示连接已经收尾。
* 说明:半帧由Receiver保存,未消费尾部保留在[rx_begin, rx_end),不能覆盖。
* WAIT表示协议暂不能受理当前帧,须允许控制发送推进后再重试。
* 普通通知满仍可处理排在前面的S/U;遇到无处交付的I帧时暂停,TCP尾部仍保留。
*/
static int Process_Input(IEC104_TcpServer_t *server, unsigned *budget)
{
while (*budget > 0U && server->event_yield == 0U && server->rx_event_pending == 0U) {
uint32_t consumed = 0U;
uint32_t available = server->rx_end - server->rx_begin;
IEC104_ConnectionResult_t result;
--*budget;
/* 允许零长度Feed重试Connection中保留的帧,不能仅凭TCP缓冲为空跳过。 */
result = IEC104_Connection_Feed(&server->connection, server->generation,
available != 0U ? server->rx_bytes + server->rx_begin : NULL, available, Now_Ms(), &consumed);
server->rx_begin += consumed;
if (server->rx_begin == server->rx_end) {
server->rx_begin = 0U;
server->rx_end = 0U;
}
if (!Connection_Result_Valid(server, result) || !Deliver_I_Frame(server) || !Consume_Records(server, NULL)) {
return 0;
}
server->rx_blocked = result == IEC104_CONNECTION_WAIT;
if (server->rx_blocked != 0U || server->rx_event_pending != 0U || server->rx_end == 0U) {
break;
}
/* 旧S/U可能本轮刚释放且consumed=0;预算内再次Feed尾部,不丢字节、不无限重试。 */
}
return 1;
}
/**
* 功能:推进协议并取得当前可发送帧的剩余字节,本函数不调用send。
* 输出参数:head——仅返回1时有效,数据借用自Connection内部发送缓冲。
* 返回值:1可发送,0暂无数据或暂受门控,-1当前连接已经收尾。
* 说明:部分发送后仍返回同一帧的尾部;EOF后区分记录回收等待与不可解除的发送门控。
* 新取的时刻用于本地期限及新I帧准入,对端确认超时故障交回接收阶段。
*/
static int Prepare_Output(IEC104_TcpServer_t *server, IEC104_SendBufferHeadInfo_t *head)
{
IEC104_ConnectionResult_t result;
int records_released;
do {
result = IEC104_Connection_Prepare_Next_Send(
&server->connection, server->generation, Now_Ms(), head);
if (!Connection_Result_Valid(server, result)) {
return -1;
}
/* Prepare也可能受理保留的ACK;回收后必须重新准备,不能沿用旧WAIT收尾。
* 仅实际释放记录才重试,次数受本轮通知预算约束,不因WAIT本身空转。 */
records_released = 0;
if (!Deliver_I_Frame(server) || !Consume_Records(server, &records_released)) {
return -1;
}
} while (result == IEC104_CONNECTION_WAIT && records_released != 0);
server->write_pending = result == IEC104_CONNECTION_SEND_READY;
if (server->write_pending == 0U) {
Reset_Send_Retry(server);
}
if (result == IEC104_CONNECTION_SEND_READY) {
return 1;
}
if (server->rx_eof != 0U) {
uint8_t record_wait = 0U;
if (result == IEC104_CONNECTION_WAIT) {
if (!Connection_Result_Valid(server, IEC104_Connection_Get_Record_Wait(
&server->connection, server->generation, &record_wait))) {
return -1;
}
if (record_wait != 0U) {
/* 只是交付预算不足时下一轮继续;容量背压由NextEvent释放后唤醒。
* 没有待写字节,不关注可写,也不为满FIFO反复安排本地续跑。 */
if (server->rx_event_pending == 0U && Ordinary_Event_Room(server, TCP_EVENT_RECORD_BYTES)) {
server->process_pending = 1U;
}
return 0;
}
}
/* EMPTY表示已排空;其余WAIT仍需新的ACK/STARTDT等对端输入,EOF后无法解除。
* 未确认记录及未提交NORMAL仍按原有断线规则交付/保留。 */
Drop_Client(server, 0, 0);
return -1;
}
return 0;
}
/**
* 功能:对已准备帧执行一次非阻塞写入,并恰好一次提交实际字节增量。
* 输入参数:head——当前SendBuffer帧的剩余字节;不能传业务ASDU或另行构造的APDU。
* 输出参数:made_progress非NULL且写入正数字节时置1;无进展时保留原值,便于预算循环累计。
* 返回值:1有进展,0暂不可继续,-1当前连接已收尾;不等待socket,不重试send。
* 说明:Process发送和应答快速发送共用本函数,部分写入、完成记账及故障回收保持一致。
* 已有CON按FIFO继续发送;状态通知容量只约束新ACT的受理。
* CON写入不产生启停通知;内部Drive受理新ACT时可立即产生通知。
* Drive内的状态变化立即保存,随后才交付新I帧及记录。
* 发送暂停覆盖全部调用入口;接收、业务提交或迟到的可写通知不能提前重试。
*/
static int Write_Output(
IEC104_TcpServer_t *server, const IEC104_SendBufferHeadInfo_t *head, int *made_progress)
{
IEC104_LocalSendCompletion_t completion;
IEC104_ConnectionResult_t result;
IEC104_NetIoResult_t written;
if (Send_Retry_Paused(server, Now_Ms())) {
return 0;
}
/* CON发送不再占用启停通知槽;容量背压在受理ACT前检查。 */
server->send_retry_at_ms = 0U;
written = IEC104_Net_Send(server->client_socket, head->pending_data, head->remaining_bytes);
if (written.result == IEC104_NET_WOULD_BLOCK || written.result == IEC104_NET_RETRY) {
/* 中断可能没有新的可写边沿,只允许有界本地重试;WOULD_BLOCK等待可写通知。 */
if (written.result == IEC104_NET_RETRY) {
Schedule_Send_Retry(server);
} else {
Reset_Send_Retry(server);
}
return 0;
}
if (written.result != IEC104_NET_OK) {
result = IEC104_Connection_Handle_Send_Failure(&server->connection, head->frame_id);
if (result != IEC104_CONNECTION_FATAL) {
/* 失败回报未能登记故障时补记内部异常,同时保留实际send的原生错误码。 */
Note_Local_Fault(server, IEC104_TCP_FAULT_INTERNAL);
}
Drop_Client(server, 0, written.native_error);
return -1;
}
if (written.transferred > 0U) {
Reset_Send_Retry(server);
if (made_progress != NULL) {
*made_progress = 1;
}
}
/* 使用send后的真实时刻登记写入;此处不判定ACK/TESTFR超时,由接收阶段先处理确认。
* 即使后续Drive返回FATAL也不能重报;Drop_Client会按已登记的发送事实产生收尾记录。 */
result = IEC104_Connection_Handle_Send_Progress(
&server->connection, head->frame_id, (uint16_t)written.transferred, Now_Ms(), &completion);
if (!Connection_Result_Valid(server, result) || !Deliver_I_Frame(server) || !Consume_Records(server, NULL)) {
return -1;
}
server->write_pending = result != IEC104_CONNECTION_FRAME_WRITTEN;
/* 0字节不推进也不续期,跨轮累计无进展次数;部分写入可能解除接收WAIT。
* Flush_Output在预算内继续写,预算耗尽由Process安排续跑;快速入口已登记本地续跑。 */
if (written.transferred == 0U) {
Schedule_Send_Retry(server);
} else if (server->rx_blocked != 0U) {
server->process_pending = 1U;
}
return written.transferred > 0U;
}
/**
* 功能:在本轮发送预算内准备并写入帧,每次Write_Output消耗一次send预算。
* 输出参数:made_progress可选,至少一次正数字节写入时为1,供接收WAIT路径判断重试价值。
* 返回值:1连接仍有效,包括暂不可写或预算耗尽;0当前连接已收尾。
* 说明:只有此调度入口循环发送;Send_Response快速入口最多调用一次Write_Output。
* 旧确认t1已到时,Connection在已有帧排空后返回WAIT,不能继续为HIGH/NORMAL编号。
*/
static int Flush_Output(IEC104_TcpServer_t *server, unsigned *budget, int *made_progress)
{
if (made_progress != NULL) {
*made_progress = 0;
}
while (*budget > 0U) {
IEC104_SendBufferHeadInfo_t head;
int written;
int prepared = Prepare_Output(server, &head);
if (prepared <= 0) {
return prepared == 0;
}
--*budget;
written = Write_Output(server, &head, made_progress);
if (written <= 0) {
return written == 0;
}
}
return 1;
}
typedef enum {
TCP_READ_CLOSED = -1, /* recv最终失败,客户端已经由Read_Client收尾。 */
TCP_READ_BLOCKED = 0, /* 目前无可读字节,连接仍有效。 */
TCP_READ_DATA = 1, /* 新字节已放入rx_bytes,等待Feed消费。 */
TCP_READ_EOF = 2 /* 对端发送方向关闭,尚未关闭本端发送方向。 */
} TcpReadResult_t;
/**
* 功能:从当前客户端非阻塞读取一批字节,不解析APCI或ASDU。
* 前提:旧TCP缓冲已消费完且没有阻塞中的协议帧,调用方尚未观察到EOF。
* 返回值:DATA/BLOCKED/EOF/CLOSED,分别表示有数据、暂不可读、有序EOF及错误收尾。
* 说明:recv返回0只表示对端不再发送,已生成或可继续准备的应答仍需发送排空。
*/
static TcpReadResult_t Read_Client(IEC104_TcpServer_t *server)
{
size_t capacity = sizeof(server->rx_bytes);
IEC104_NetIoResult_t received;
if (server->rx_snapshot_active != 0U && capacity > server->rx_snapshot_remaining) {
capacity = server->rx_snapshot_remaining;
}
/* 旧快照已读完时不能顺便读取后来到达的数据,也不能用零容量recv探测EOF。 */
if (capacity == 0U) {
return TCP_READ_BLOCKED;
}
received = IEC104_Net_Recv(server->client_socket, server->rx_bytes, capacity);
if (received.result == IEC104_NET_OK) {
if (received.transferred == 0U || received.transferred > capacity) {
(void)Drop_Local_Fault(server, IEC104_TCP_FAULT_INTERNAL);
return TCP_READ_CLOSED;
}
if (server->rx_snapshot_active != 0U) {
server->rx_snapshot_remaining -= (uint32_t)received.transferred;
}
server->rx_begin = 0U;
server->rx_end = (uint32_t)received.transferred;
server->rx_ready = 1U;
return TCP_READ_DATA;
} else if (received.result == IEC104_NET_EOF) {
server->rx_ready = 0U;
return TCP_READ_EOF;
} else if (received.result != IEC104_NET_WOULD_BLOCK && received.result != IEC104_NET_RETRY) {
Drop_Client(server, 1, received.native_error);
return TCP_READ_CLOSED;
}
/* WOULD_BLOCK才耗尽当前就绪;中断后保留续跑,仍结束本批检查期限。 */
server->rx_ready = received.result == IEC104_NET_RETRY;
return TCP_READ_BLOCKED;
}
/**
* 功能:结束本轮接收片段,决定立即检查全部期限,还是保留固定输入快照到下一轮处理。
* 输入参数:may_continue——仅因预算/事件交付让出时为真;read_socket——本批是否允许读取socket。
* 返回值:1连接可继续,0表示查询输入、协议推进或事件交付失败并已收尾。
* 说明:首次让出时记录已在内核等待的字节数;后续轮只消耗该快照,不纳入新到达数据。
* 有旧输入待处理时调用Yield_Receive_Batch并安排立即续跑,先处理可能解除t1的ACK。
* 快照读完、真正容量背压或接收无进展时调用Tick,按原期限判定,不能靠新流量无限推迟t1。
*/
static int Finish_Receive_Slice(IEC104_TcpServer_t *server, int may_continue, int read_socket)
{
IEC104_ConnectionReceivedFrame_t frame;
int pending = 0;
if (may_continue && server->rx_eof == 0U && server->rx_blocked == 0U &&
server->rx_event_pending == 0U) {
if (server->rx_snapshot_active == 0U && read_socket) {
int socket_error = 0;
if (IEC104_Net_Pending_Input(server->client_socket, &server->rx_snapshot_remaining,
&socket_error) != IEC104_NET_OK) {
Drop_Client(server, 1, socket_error);
return 0;
}
}
pending = server->rx_snapshot_remaining != 0U || server->rx_begin != server->rx_end ||
IEC104_Connection_Peek_Received_Frame(&server->connection, server->generation, &frame) ==
IEC104_CONNECTION_FRAME_READY;
}
if (pending) {
server->rx_snapshot_active = 1U;
server->process_pending = 1U;
return Connection_Result_Valid(server, IEC104_Connection_Yield_Receive_Batch(
&server->connection, server->generation, Now_Ms())) &&
Deliver_I_Frame(server) && Consume_Records(server, NULL);
}
server->rx_snapshot_active = 0U;
server->rx_snapshot_remaining = 0U;
return Connection_Result_Valid(server, IEC104_Connection_Tick(
&server->connection, server->generation, Now_Ms())) &&
Deliver_I_Frame(server) && Consume_Records(server, NULL);
}
/**
* 功能:执行一批有界接收,优先受理已有输入,再检查对端确认是否超时。
* 输入/输出参数:rx_budget/tx_budget——本轮Process共享的Feed/send剩余次数。
* 输入参数:read_socket——本轮有接收就绪,或由本地/定时唤醒允许非阻塞检查内核输入。
* 返回值:1本批结束且连接仍可继续,0表示连接已因错误或故障收尾。
* 说明:Begin_Receive_Batch只暂缓I帧ACK和TESTFR_CON期限,本地发送及STOP期限仍有效。
* 控制槽满时尝试有界发送以解除WAIT;无进展则交还调度,不阻塞等待socket。
* 预算让出时固定剩余输入范围并立即续跑;快照耗尽、WAIT或无进展时用Tick检查全部期限。
* 一批输入产生业务事件后不再继续recv,让业务有机会在下一次接收EOF前提交应答。
*/
static int Receive_Input(
IEC104_TcpServer_t *server, unsigned *rx_budget, unsigned *tx_budget, int read_socket)
{
uint16_t events_before = server->event_count;
int stalled = 0;
read_socket = read_socket || server->rx_snapshot_active != 0U;
if (!Connection_Result_Valid(server, IEC104_Connection_Begin_Receive_Batch(
&server->connection, server->generation))) {
return 0;
}
/* 先交付预算让出时保留的I帧;旧回执留到输入处理后,避免阻挡已到达的ACK。 */
if (!Deliver_I_Frame(server)) {
return 0;
}
while (*rx_budget > 0U && server->event_yield == 0U && server->rx_event_pending == 0U &&
server->event_count == events_before) {
if (server->rx_end == 0U && server->rx_blocked == 0U) {
TcpReadResult_t received;
if (server->rx_snapshot_active != 0U && server->rx_snapshot_remaining == 0U) {
break;
}
if (!read_socket) {
break;
}
received = Read_Client(server);
if (received == TCP_READ_CLOSED) {
return 0;
}
if (received == TCP_READ_EOF) {
/* 停止为已有及后来新增的NORMAL创建新I帧,原文留给下一连接。
* 当前已编号帧、已受理HIGH及控制帧仍按原期限和发送调度收尾。
* 仍经下面的Tick结束接收批次,不遗留被暂缓的确认超时。 */
if (IEC104_AsduQueue_Limit_Normal_Send(&server->queue, server->generation) != IEC104_ASDU_QUEUE_OK) {
(void)Drop_Local_Fault(server, IEC104_TCP_FAULT_INTERNAL);
server->queue_initialized = 0U;
return 0;
}
server->rx_eof = 1U;
break;
}
if (received == TCP_READ_BLOCKED) {
stalled = 1;
break;
}
}
if (!Process_Input(server, rx_budget)) {
return 0;
}
if (server->rx_blocked != 0U) {
int made_progress;
IEC104_ConnectionReceivedFrame_t frame;
IEC104_ConnectionResult_t pending;
if (!Flush_Output(server, tx_budget, &made_progress)) {
return 0;
}
if (!made_progress) {
stalled = 1;
break;
}
/* 写出字节不一定解除ACT的通知背压;按保留帧的实际受理状态决定是否续收。 */
pending = IEC104_Connection_Peek_Received_Frame(&server->connection, server->generation, &frame);
if (!Connection_Result_Valid(server, pending)) {
return 0;
}
server->rx_blocked = pending == IEC104_CONNECTION_WAIT;
}
}
return Finish_Receive_Slice(server, !stalled && (*rx_budget == 0U ||
server->event_yield != 0U || server->event_count != events_before), read_socket);
}
/**
* 功能:有界接受连接,并为允许接入的主站建立新的协议会话。
* 返回值:OK表示监听可继续,包括当前无连接待接受;其他结果为服务级失败,应Close。
* 说明:只允许一条活动连接,额外连接接受后关闭,不替换当前主站。
* 新generation不复用旧身份;服务NORMAL通过Rebind绑定,仍须重新STARTDT。
* 资源暂缺立即暂停;整轮均为RETRY也暂停,其他接入进度仍按预算续跑。
* 个别客户端设置失败时只关闭该socket;监听、代次或内部初始化失败按阶段交付原因。
*/
static IEC104_TcpResult_t Accept_Clients(IEC104_TcpServer_t *server, IEC104_TcpError_t *error)
{
unsigned count, retry_count = 0U;
for (count = 0U; count < TCP_ACCEPT_BUDGET; ++count) {
IEC104_NetSocket_t client;
IEC104_NetPeer_t peer;
int socket_error;
IEC104_NetResult_t accepted;
IEC104_AsduQueueResult_t queued;
/* 旧通知须释放新会话所需容量;建连通知也计入本轮预算。 */
if (server->client_socket == IEC104_NET_INVALID_SOCKET) {
if (!Can_Accept_Connection(server)) {
return IEC104_TCP_OK;
}
if (server->event_budget == 0U) {
server->process_pending = 1U;
return IEC104_TCP_OK;
}
}
accepted = IEC104_Net_Accept(server->listen_socket, &client, &peer, &socket_error);
if (accepted == IEC104_NET_WOULD_BLOCK) {
server->accept_pending = 0U;
server->accept_retry_at_ms = 0U;
server->consecutive_accept_failures = 0U;
return IEC104_TCP_OK;
}
if (accepted != IEC104_NET_OK) {
server->last_socket_error = socket_error;
server->last_accept_error = socket_error;
if (server->consecutive_accept_failures < UINT32_MAX) {
++server->consecutive_accept_failures;
}
if (accepted == IEC104_NET_RETRY_LATER) {
Pause_Accept(server);
return IEC104_TCP_OK;
}
if (accepted == IEC104_NET_RETRY) {
++retry_count;
continue;
}
return Result_With_Error(IEC104_TCP_NETWORK_ERROR, IEC104_TCP_STAGE_ACCEPT, socket_error, error);
}
/* Net已成功交付客户端即有接入进度,额外客户端随后关闭也算成功;历史错误另行保留。 */
server->accept_retry_at_ms = 0U;
server->consecutive_accept_failures = 0U;
if (server->client_socket != IEC104_NET_INVALID_SOCKET) {
(void)IEC104_Net_Close(client, NULL);
continue;
}
/* 代次不回绕,避免迟到的旧业务事件误匹配未来连接。 */
if (server->generation == UINT64_MAX) {
(void)IEC104_Net_Close(client, NULL);
server->last_socket_error = 0;
return Result_With_Error(IEC104_TCP_GENERATION_EXHAUSTED, IEC104_TCP_STAGE_GENERATION, 0, error);
}
if (server->registration_serial == UINT64_MAX) {
(void)IEC104_Net_Close(client, NULL);
return Result_With_Error(IEC104_TCP_REGISTRATION_EXHAUSTED, IEC104_TCP_STAGE_REGISTRATION, 0, error);
}
++server->generation;
/* 队列早已随服务初始化;首次接入与重连都只绑定现存NORMAL,不能重新Init覆盖离线缓存。 */
queued = IEC104_AsduQueue_Rebind(&server->queue, server->generation);
if (queued != IEC104_ASDU_QUEUE_OK) {
(void)IEC104_Net_Close(client, NULL);
server->last_socket_error = 0;
server->queue_initialized = 0U;
return Result_With_Error(IEC104_TCP_INTERNAL_ERROR, IEC104_TCP_STAGE_QUEUE_INIT, 0, error);
}
if (IEC104_Connection_Init(&server->connection, &server->queue,
server->controls, IEC104_TCP_CONTROL_CAPACITY, server->records, IEC104_TCP_RECORD_CAPACITY,
&server->config.protocol, server->generation, Now_Ms()) != IEC104_CONNECTION_OK) {
(void)IEC104_Net_Close(client, NULL);
server->last_socket_error = 0;
/* Connection_Init失败不发布新会话;归还队列到离线状态,保留已缓存原文。 */
if (IEC104_AsduQueue_Detach(&server->queue, server->generation) != IEC104_ASDU_QUEUE_OK) {
server->queue_initialized = 0U;
}
return Result_With_Error(IEC104_TCP_INTERNAL_ERROR, IEC104_TCP_STAGE_CONNECTION_INIT, 0, error);
}
{
const IEC104_ConnectionTransferObserver_t observer = {
server, Transfer_Event_Room, Notify_Transfer_State
};
if (IEC104_Connection_Set_Transfer_Observer(&server->connection, server->generation, &observer) !=
IEC104_CONNECTION_OK) {
(void)IEC104_Connection_Close(&server->connection, server->generation);
(void)IEC104_AsduQueue_Detach(&server->queue, server->generation);
(void)IEC104_Net_Close(client, NULL);
server->queue_initialized = 0U;
return Result_With_Error(IEC104_TCP_INTERNAL_ERROR, IEC104_TCP_STAGE_CONNECTION_INIT, 0, error);
}
}
server->client_socket = client;
server->client_token = ++server->registration_serial;
/* 只为成功初始化的当前连接发布地址;额外客户端或接入失败不会覆盖旧主站快照。 */
server->peer.generation = server->generation;
memcpy(server->peer.address, peer.address, sizeof(server->peer.address));
server->peer.port = peer.port;
server->last_socket_error = 0;
server->rx_begin = 0U;
server->rx_end = 0U;
server->rx_snapshot_remaining = 0U;
server->rx_snapshot_active = 0U;
server->rx_blocked = 0U;
server->rx_event_pending = 0U;
server->rx_eof = 0U;
server->rx_ready = 1U;
server->write_pending = 0U;
Reset_Send_Retry(server);
server->local_fault = IEC104_TCP_FAULT_NONE;
server->process_pending = 1U;
/* 此时只表示TCP已建立;Connection初始STOPPED,尚未完成104启动握手。 */
if (!Notify_Connection(server, 1, 0U)) {
(void)Drop_Local_Fault(server, IEC104_TCP_FAULT_INTERNAL);
return Result_With_Error(IEC104_TCP_INTERNAL_ERROR, IEC104_TCP_STAGE_QUEUE_STATE, 0, error);
}
}
/* 没有读到WOULD_BLOCK,不能假定监听队列已空;整轮失败须退避,避免跨轮忙重试。 */
server->accept_pending = 1U;
if (retry_count == TCP_ACCEPT_BUDGET) {
Pause_Accept(server);
}
return IEC104_TCP_OK;
}
/**
* 功能:只读判断是否存在无需等待新socket通知就能尝试的本地工作。
* 输入参数:now_ms——本次Prepare共用的单调时刻;返回1时上层须安排Process,不阻塞等待。
* 说明:包括已登记续跑、到期发送重试、有容量的接入重试,以及未耗尽的接收就绪/缓存。
* 无处交付I帧或协议WAIT时保留接收线索,但不以其反复空转;由释放容量或其他进展恢复。
* 纯协议期限另由Prepare_Events合并,不能把本函数返回0理解为可以无限等待。
*/
static int Has_Local_Work(const IEC104_TcpServer_t *server, uint64_t now_ms)
{
return server->process_pending != 0U ||
(server->client_socket != IEC104_NET_INVALID_SOCKET && server->send_retry_at_ms != 0U &&
!Send_Retry_Paused(server, now_ms)) ||
(server->accept_pending != 0U && !Accept_Retry_Paused(server, now_ms) &&
(server->client_socket != IEC104_NET_INVALID_SOCKET || Can_Accept_Connection(server))) ||
(server->client_socket != IEC104_NET_INVALID_SOCKET && server->rx_eof == 0U &&
server->rx_blocked == 0U && server->rx_event_pending == 0U &&
(server->rx_ready != 0U || server->rx_end != 0U));
}
/**
* 功能:在发送之后重新检查最近协议期限,需要立即处理时登记process_pending。
* 返回值:1可继续,0表示期限查询产生致命错误且连接已收尾;不进行等待或接收。
* 前提:接收批次已结束,客户端仍有效;已有本地续跑时不重复查询。
* 说明:发送可能跨过确认期限,下一轮须先接收已到达的ACK/TESTFR_CON,再按原期限判定。
* 这里只安排调度,不延长期限,也不在发送结束后直接Tick而跳过待处理确认。
*/
static int Schedule_Protocol_Work(IEC104_TcpServer_t *server)
{
uint32_t wait_ms;
if (server->process_pending != 0U) {
return 1;
}
if (!Connection_Result_Valid(server, IEC104_Connection_Get_Wait_Time(
&server->connection, server->generation, Now_Ms(), UINT32_MAX, &wait_ms))) {
return 0;
}
if (wait_ms == 0U) {
server->process_pending = 1U;
}
return 1;
}
/**
* 功能:Prepare与Poll共用的只读准备核心,生成socket关注集合及最近等待期限。
* 输入参数:server——公开入口已验证句柄、重入及初始化状态;error——可选失败诊断。
* 输出参数:info仅OK时一次性写出,失败不交付部分有效的socket列表。
* 返回值:OK快照有效;INTERNAL_ERROR表示服务队列或协议调度查询异常。
* 说明:一次取时后合并协议、接入重试、发送重试期限,撤销暂停方向的socket关注。
* write_pending由实际发送准备维护,业务入队/消费通知通过process_pending唤醒推进。
* 不调用Tick或Prepare_Next_Send,已失败会话也仅安排立即Process,由推进路径完成收尾。
*/
static IEC104_TcpResult_t Prepare_Events(
const IEC104_TcpServer_t *server, IEC104_TcpWaitInfo_t *info, IEC104_TcpError_t *error)
{
IEC104_TcpWaitInfo_t snapshot;
uint32_t client_interest = 0U;
uint64_t now_ms;
int accept_paused;
int send_paused;
if (server->queue_initialized == 0U) {
return Result_With_Error(IEC104_TCP_INTERNAL_ERROR, IEC104_TCP_STAGE_QUEUE_STATE, 0, error);
}
/* 本次快照只取一次时钟,监听门控、本地续跑和全部相对期限使用同一时刻。 */
now_ms = Now_Ms();
accept_paused = Accept_Retry_Paused(server, now_ms);
send_paused = Send_Retry_Paused(server, now_ms);
memset(&snapshot, 0, sizeof(snapshot));
snapshot.service_token = server->listen_token;
snapshot.pending_event_count = server->event_count;
snapshot.receive_paused = server->rx_event_pending;
snapshot.need_process = (uint8_t)Has_Local_Work(server, now_ms);
if (!accept_paused &&
(server->client_socket != IEC104_NET_INVALID_SOCKET || Can_Accept_Connection(server))) {
IEC104_TcpSocketEvent_t *watch = &snapshot.sockets[snapshot.socket_count++];
watch->socket = (IEC104_TcpSocket_t)server->listen_socket;
watch->token = server->listen_token;
watch->events = IEC104_TCP_IO_READ;
}
if (server->client_socket != IEC104_NET_INVALID_SOCKET) {
IEC104_ConnectionResult_t scheduled;
if (server->rx_eof == 0U && server->rx_blocked == 0U && server->rx_event_pending == 0U) {
client_interest |= IEC104_TCP_IO_READ;
}
if (server->write_pending != 0U && !send_paused) {
client_interest |= IEC104_TCP_IO_WRITE;
}
if (client_interest != 0U) {
IEC104_TcpSocketEvent_t *watch = &snapshot.sockets[snapshot.socket_count++];
watch->socket = (IEC104_TcpSocket_t)server->client_socket;
watch->token = server->client_token;
watch->events = client_interest;
}
snapshot.timer_active = 1U;
scheduled = IEC104_Connection_Get_Wait_Time(&server->connection, server->generation,
now_ms, UINT32_MAX, &snapshot.timeout_ms);
if (scheduled == IEC104_CONNECTION_FATAL) {
/* 已失败会话交给Process收尾;只读查询本身不关闭socket或追加业务事件。 */
snapshot.timeout_ms = 0U;
} else if (scheduled != IEC104_CONNECTION_OK) {
return Result_With_Error(IEC104_TCP_INTERNAL_ERROR, IEC104_TCP_STAGE_SCHEDULE, 0, error);
}
if (snapshot.timeout_ms == 0U) {
snapshot.need_process = 1U;
}
if (send_paused) {
uint32_t remaining_ms = (uint32_t)(server->send_retry_at_ms - now_ms);
/* 暂停只限制实际写入,不能推迟更早的本地发送或协议期限。 */
if (remaining_ms < snapshot.timeout_ms) {
snapshot.timeout_ms = remaining_ms;
}
}
}
if (accept_paused) {
uint32_t remaining_ms = (uint32_t)(server->accept_retry_at_ms - now_ms);
/* 无客户端也有接入期限;已到期的期限不再合并,容量不足时不能留下零等待定时器。 */
if (snapshot.timer_active == 0U || remaining_ms < snapshot.timeout_ms) {
snapshot.timeout_ms = remaining_ms;
}
snapshot.timer_active = 1U;
}
*info = snapshot;
return IEC104_TCP_OK;
}
/**
* 功能:Process和Poll共用的唯一推进核心,有界处理当前客户端,再尝试接入新客户端。
* 输入参数:server——公开入口已校验并设置重入保护;ready——本批就绪及定时信息,不能为NULL。
* 输出参数:error——服务级失败的诊断;客户端故障另通过事件交付。
* 返回值:OK表示服务可继续,包括忽略旧通知或当前客户端已收尾;其他结果由调用方处理。
* 说明:先校验整批格式,再匹配service_token和socket/token,最后才改变状态。
* 收到的就绪只是尝试I/O的提示;先接收并提交事实,再检查期限、推进发送,最后accept。
* 每轮重新分配收发和通知预算;未完成工作登记本地续跑,不等待、不操作应用的注册表。
* 新接入连接仅登记下一轮工作,不继承本批旧socket的就绪位。
*/
static IEC104_TcpResult_t Process_Events(
IEC104_TcpServer_t *server, const IEC104_TcpReady_t *ready, IEC104_TcpError_t *error)
{
unsigned rx_budget = TCP_IO_BUDGET, tx_budget = TCP_IO_BUDGET;
uint32_t index, previous, listen_events = 0U, client_events = 0U;
int matched = 0;
int local_wake;
IEC104_TcpResult_t result = IEC104_TCP_OK;
if (ready->socket_count > IEC104_TCP_MAX_SOCKETS || ready->timer_expired > 1U ||
ready->service_token == 0U) {
return Result_With_Error(IEC104_TCP_INVALID_ARGUMENT, IEC104_TCP_STAGE_VALIDATE, 0, error);
}
for (index = 0U; index < ready->socket_count; ++index) {
const IEC104_TcpSocketEvent_t *event = &ready->sockets[index];
if (event->token == 0U || event->events == 0U ||
(event->events & ~(IEC104_TCP_IO_READ | IEC104_TCP_IO_WRITE | IEC104_TCP_IO_ERROR)) != 0U) {
return Result_With_Error(IEC104_TCP_INVALID_ARGUMENT, IEC104_TCP_STAGE_VALIDATE, 0, error);
}
for (previous = 0U; previous < index; ++previous) {
if (event->socket == ready->sockets[previous].socket && event->token == ready->sockets[previous].token) {
return Result_With_Error(IEC104_TCP_INVALID_ARGUMENT, IEC104_TCP_STAGE_VALIDATE, 0, error);
}
}
}
if (ready->service_token != server->listen_token) {
return IEC104_TCP_OK;
}
if (server->queue_initialized == 0U) {
return Result_With_Error(IEC104_TCP_INTERNAL_ERROR, IEC104_TCP_STAGE_QUEUE_STATE, 0, error);
}
for (index = 0U; index < ready->socket_count; ++index) {
const IEC104_TcpSocketEvent_t *event = &ready->sockets[index];
if (event->socket == (IEC104_TcpSocket_t)server->listen_socket && event->token == server->listen_token) {
if ((event->events & IEC104_TCP_IO_WRITE) != 0U) {
return Result_With_Error(IEC104_TCP_INVALID_ARGUMENT, IEC104_TCP_STAGE_VALIDATE, 0, error);
}
listen_events |= event->events;
matched = 1;
} else if (server->client_socket != IEC104_NET_INVALID_SOCKET &&
event->socket == (IEC104_TcpSocket_t)server->client_socket && event->token == server->client_token) {
client_events |= event->events;
matched = 1;
}
}
if (ready->socket_count != 0U && !matched && ready->timer_expired == 0U) {
return IEC104_TCP_OK;
}
server->event_budget = TCP_EVENT_BUDGET;
server->event_yield = 0U;
local_wake = server->process_pending != 0U || ready->socket_count == 0U;
server->process_pending = 0U;
if ((listen_events & (IEC104_TCP_IO_READ | IEC104_TCP_IO_ERROR)) != 0U) {
server->accept_pending = 1U;
}
if ((client_events & (IEC104_TCP_IO_READ | IEC104_TCP_IO_ERROR)) != 0U) {
server->rx_ready = 1U;
}
if (server->client_socket != IEC104_NET_INVALID_SOCKET) {
if (server->rx_eof == 0U) {
/* 同批read/timeout先输入后期限。仅write就绪也先重试已缓存输入,
* 本地/定时唤醒额外允许非阻塞recv,避免检查期限之前遗漏已到达的ACK。 */
(void)Receive_Input(server, &rx_budget, &tx_budget,
server->rx_ready != 0U || local_wake || ready->timer_expired != 0U);
} else if (Connection_Result_Valid(server, IEC104_Connection_Tick(
&server->connection, server->generation, Now_Ms()))) {
(void)(Deliver_I_Frame(server) && Consume_Records(server, NULL));
}
if (server->client_socket != IEC104_NET_INVALID_SOCKET) {
(void)Flush_Output(server, &tx_budget, NULL);
if (server->client_socket != IEC104_NET_INVALID_SOCKET && tx_budget == 0U) {
/* 可能尚有后续业务帧,下一轮重新取得预算,不能依赖新的可写边沿。 */
server->process_pending = 1U;
}
if (server->client_socket != IEC104_NET_INVALID_SOCKET) {
(void)Schedule_Protocol_Work(server);
}
}
}
/* 收尾无法解除旧队列绑定时停止本轮,不能让新客户端继续使用异常队列。 */
if (server->queue_initialized == 0U) {
return Result_With_Error(IEC104_TCP_INTERNAL_ERROR, IEC104_TCP_STAGE_QUEUE_STATE, 0, error);
}
/* 接入放在旧客户端处理之后;任何唤醒原因均须通过暂停期限检查。
* 新客户端只登记本地唤醒,不继承本批旧客户端就绪位。 */
if (server->accept_pending != 0U && !Accept_Retry_Paused(server, Now_Ms())) {
result = Accept_Clients(server, error);
}
return result;
}
/**
* 功能:Poll专用平台等待适配,把公开快照转为Net关注位,等待一次后还原就绪批次。
* 输入参数:info——本次Prepare_Events快照;max_wait_ms——调用方允许的网络等待上限。
* 输出参数:ready——内部使用的就绪批次,仅返回OK时交给Process_Events;error——可选诊断。
* 返回值:OK包括无就绪或可恢复中断;NETWORK_ERROR表示平台等待失败。
* 说明:实际等待取上限和最近期限的较小值;已有业务事件或本地工作时使用0毫秒等待。
* service_token及socket/token沿用等待前快照,timer_expired按实际经过时间计算。
* 外部事件循环不调用这里,可把本模块socket和其他模块事件合并到一次平台等待中。
*/
static IEC104_TcpResult_t Wait_For_Events(
IEC104_TcpServer_t *server, const IEC104_TcpWaitInfo_t *info, uint32_t max_wait_ms,
IEC104_TcpReady_t *ready, IEC104_TcpError_t *error)
{
uint32_t index, interest = 0U, ready_events = 0U, wait_ms = max_wait_ms;
uint64_t wait_started = Now_Ms();
IEC104_NetResult_t waited;
int socket_error;
memset(ready, 0, sizeof(*ready));
ready->service_token = info->service_token;
if (info->timer_active != 0U && wait_ms > info->timeout_ms) {
wait_ms = info->timeout_ms;
}
if (info->need_process != 0U || info->pending_event_count != 0U) {
wait_ms = 0U;
}
for (index = 0U; index < info->socket_count; ++index) {
const IEC104_TcpSocketEvent_t *watch = &info->sockets[index];
if (watch->token == server->listen_token) {
interest |= IEC104_NET_EVENT_ACCEPT;
} else {
if ((watch->events & IEC104_TCP_IO_READ) != 0U) {
interest |= IEC104_NET_EVENT_READ;
}
if ((watch->events & IEC104_TCP_IO_WRITE) != 0U) {
interest |= IEC104_NET_EVENT_WRITE;
}
}
}
/* 空集合可在接入暂停时作有限定时等待;有未读事件时wait_ms已为0,仍立即交还业务。 */
waited = IEC104_Net_Wait(server->listen_socket, server->client_socket, interest,
wait_ms, &ready_events, &socket_error);
if (waited != IEC104_NET_OK && waited != IEC104_NET_RETRY) {
server->last_socket_error = socket_error;
return Result_With_Error(IEC104_TCP_NETWORK_ERROR, IEC104_TCP_STAGE_WAIT, socket_error, error);
}
for (index = 0U; index < info->socket_count; ++index) {
const IEC104_TcpSocketEvent_t *watch = &info->sockets[index];
uint32_t events = 0U;
if (watch->token == server->listen_token) {
if ((ready_events & IEC104_NET_EVENT_ACCEPT) != 0U) {
events = IEC104_TCP_IO_READ;
}
} else {
if ((ready_events & IEC104_NET_EVENT_READ) != 0U) {
events |= IEC104_TCP_IO_READ;
}
if ((ready_events & IEC104_NET_EVENT_WRITE) != 0U) {
events |= IEC104_TCP_IO_WRITE;
}
}
if (events != 0U) {
IEC104_TcpSocketEvent_t *event = &ready->sockets[ready->socket_count++];
*event = *watch;
event->events = events;
}
}
ready->timer_expired = (uint8_t)(info->timer_active != 0U &&
Now_Ms() - wait_started >= info->timeout_ms);
return IEC104_TCP_OK;
}
/**
* 功能:从模块内部静态池取得一个地址固定的服务对象。
* 返回值:成功返回不透明句柄;无空闲槽或候选槽的代次/注册编号耗尽时返回NULL。
* 说明:只重置可复用运行状态,不创建socket;监听由后续Init完成。
* generation和注册编号随静态槽保留,不随句柄归还清零;耗尽的槽不能重新分配。
* 成功后由业务负责配对Destroy;各服务之间的Create/Destroy也必须串行。
*/
IEC104_TcpServer_t *IEC104_TcpServer_Create(void)
{
size_t index;
for (index = 0U; index < IEC104_TCP_SERVER_CAPACITY; ++index) {
TcpServerSlot_t *slot = &server_pool[index];
if (slot->in_use == 0U && slot->server.generation != UINT64_MAX &&
slot->server.registration_serial != UINT64_MAX) {
Reset_Server(&slot->server);
slot->in_use = 1U;
return &slot->server;
}
}
return NULL;
}
/**
* 功能:归还一个已停止且无未读事件的静态槽,不执行网络收尾。
* 输入参数:server——Create返回且尚未Destroy的句柄。
* 返回值:OK归还成功;INVALID_HANDLE/BUSY/INVALID_STATE/EVENTS_PENDING区分拒绝原因。
* 输出参数:error可为NULL;即使句柄无效或已成功归还,也交付本次诊断,不借用对象字段。
* 说明:正常顺序为Close -> NextEvent读空 -> Destroy;尚未Init或Init失败的句柄也可归还。
* Close释放网络资源,Destroy释放对象池名额,二者处理的资源不同。
* 成功后所有旧句柄副本失效;调用方应清空保存的指针,不得自行free。
*/
IEC104_TcpResult_t IEC104_TcpServer_Destroy(IEC104_TcpServer_t *server, IEC104_TcpError_t *error)
{
size_t index;
IEC104_TcpResult_t result = Check_Server(server, error);
if (result != IEC104_TCP_OK) {
return result;
}
if (server->is_initialized != 0U) {
return Result_With_Error(IEC104_TCP_INVALID_STATE, IEC104_TCP_STAGE_LIFECYCLE, 0, error);
}
if (server->event_count != 0U) {
return Result_With_Error(IEC104_TCP_EVENTS_PENDING, IEC104_TCP_STAGE_LIFECYCLE, 0, error);
}
for (index = 0U; index < IEC104_TCP_SERVER_CAPACITY; ++index) {
if (server == &server_pool[index].server) {
/* 保留会话代次后再归还名额;下一次Create仍使用同一块固定地址存储。 */
Reset_Server(server);
server_pool[index].in_use = 0U;
return IEC104_TCP_OK;
}
}
return Result_With_Error(IEC104_TCP_INTERNAL_ERROR, IEC104_TCP_STAGE_LIFECYCLE, 0, error);
}
/**
* 功能:填写默认监听配置及公开协议参数。
* 输出参数:config——默认监听0.0.0.0:2404;NORMAL随服务保留,等待上限由每次Poll指定。
* 说明:传NULL时无操作,不申请系统资源,也不注册业务函数。
*/
void IEC104_TcpServer_Config_Default(IEC104_TcpServerConfig_t *config)
{
if (config != NULL) {
memset(config, 0, sizeof(*config));
memcpy(config->bind_address, "0.0.0.0", sizeof("0.0.0.0"));
config->port = 2404U;
IEC104_Connection_Config_Default(&config->protocol);
}
}
/**
* 功能:初始化网络后端、非阻塞IPv4监听及服务队列;客户端协议会话延后到accept时初始化。
* 输入参数:server——Create取得或已Close且事件已读空的对象;config——公开配置。
* 输出参数:成功时复制配置并设置bound_port;error按本次操作填写,允许NULL。
* 返回值:OK成功;参数、配置、生命周期和网络错误分别返回对应的公开结果。
* 网络失败保留NET_INIT/LISTEN阶段及原生错误,参数错误不再伪装为平台socket错误。
* 说明:首次初始化失败会释放本次申请的网络资源;成功后由应用线程驱动Process或Poll。
*/
IEC104_TcpResult_t IEC104_TcpServer_Init(
IEC104_TcpServer_t *server, const IEC104_TcpServerConfig_t *config, IEC104_TcpError_t *error)
{
int socket_error;
IEC104_TcpResult_t result = Check_Server(server, error);
if (result != IEC104_TCP_OK) {
return result;
}
if (config == NULL) {
return Result_With_Error(IEC104_TCP_INVALID_ARGUMENT, IEC104_TCP_STAGE_VALIDATE, 0, error);
}
if (server->is_initialized != 0U) {
return Result_With_Error(IEC104_TCP_INVALID_STATE, IEC104_TCP_STAGE_LIFECYCLE, 0, error);
}
if (server->event_count != 0U) {
return Result_With_Error(IEC104_TCP_EVENTS_PENDING, IEC104_TCP_STAGE_LIFECYCLE, 0, error);
}
if (!Config_Valid(config)) {
return Result_With_Error(IEC104_TCP_INVALID_CONFIG, IEC104_TCP_STAGE_VALIDATE, 0, error);
}
if (server->registration_serial == UINT64_MAX) {
return Result_With_Error(IEC104_TCP_REGISTRATION_EXHAUSTED, IEC104_TCP_STAGE_REGISTRATION, 0, error);
}
/* Close后外部仍可持有旧事件副本;重新Init不复用旧代次,拒绝迟到的旧应答。 */
Reset_Server(server);
server->config = *config;
/* 成功取得的平台引用须与初始化失败清理或Close配对释放。 */
if (IEC104_Net_Init(&socket_error) != IEC104_NET_OK) {
server->last_socket_error = socket_error;
return Result_With_Error(IEC104_TCP_NETWORK_ERROR, IEC104_TCP_STAGE_NET_INIT, socket_error, error);
}
if (IEC104_Net_Listen(config->bind_address, config->port,
&server->listen_socket, &server->bound_port, &socket_error) != IEC104_NET_OK) {
/* Net_Listen已回收其申请的句柄;调度层只释放自己的平台引用。 */
IEC104_Net_Cleanup();
server->last_socket_error = socket_error;
return Result_With_Error(IEC104_TCP_NETWORK_ERROR, IEC104_TCP_STAGE_LISTEN, socket_error, error);
}
/* 代次0仅表示队列尚未绑定客户端;此时已经可以缓存NORMAL,不虚构协议会话代次。 */
if (IEC104_AsduQueue_Init(&server->queue, server->high_bytes, sizeof(server->high_bytes),
server->normal_bytes, sizeof(server->normal_bytes), 0U) != IEC104_ASDU_QUEUE_OK) {
(void)IEC104_Net_Close(server->listen_socket, NULL);
server->listen_socket = IEC104_NET_INVALID_SOCKET;
server->bound_port = 0U;
IEC104_Net_Cleanup();
return Result_With_Error(IEC104_TCP_INTERNAL_ERROR, IEC104_TCP_STAGE_QUEUE_INIT, 0, error);
}
server->queue_initialized = 1U;
server->listen_token = ++server->registration_serial;
server->is_initialized = 1U;
return IEC104_TCP_OK;
}
/**
* 功能:只读导出当前socket监听集合、协议/接入等待上限及本地续跑要求。
* 输入参数:server——已Init的句柄;info——非NULL快照存储;error——可选诊断。
* 返回值:OK时完整写出info,参数、生命周期或内部查询失败时保持info原值。
* 说明:不提前Tick、构帧、读写或接入,重复查询不会改变状态或刷新协议期限。
* 本次快照不是长期配置;任何推进/入队/事件消费后,等待前都要重新获取。
*/
IEC104_TcpResult_t IEC104_TcpServer_Prepare(
const IEC104_TcpServer_t *server, IEC104_TcpWaitInfo_t *info, IEC104_TcpError_t *error)
{
IEC104_TcpResult_t result = Check_Server(server, error);
if (result != IEC104_TCP_OK) {
return result;
}
if (info == NULL) {
return Result_With_Error(IEC104_TCP_INVALID_ARGUMENT, IEC104_TCP_STAGE_VALIDATE, 0, error);
}
if (server->is_initialized == 0U) {
return Result_With_Error(IEC104_TCP_INVALID_STATE, IEC104_TCP_STAGE_LIFECYCLE, 0, error);
}
return Prepare_Events(server, info, error);
}
/**
* 功能:把应用收集的一批事件交给唯一的有界推进核心,本函数不执行等待。
* 输入参数:server——已Init的句柄;ready——必填批次,本地唤醒也须携带有效service_token。
* 输出参数:error——可选的本次诊断;返回值为OK或参数、生命周期及服务级错误。
* 说明:业务回调不进入模块;每轮返回后应用读取NextEvent,再更新监听与一次性定时器。
* 对非法批次、服务级失败和客户端故障使用与Poll相同的结果/事件交付约定。
*/
IEC104_TcpResult_t IEC104_TcpServer_Process(
IEC104_TcpServer_t *server, const IEC104_TcpReady_t *ready, IEC104_TcpError_t *error)
{
IEC104_TcpResult_t result = Check_Server(server, error);
if (result != IEC104_TCP_OK) {
return result;
}
if (ready == NULL) {
return Result_With_Error(IEC104_TCP_INVALID_ARGUMENT, IEC104_TCP_STAGE_VALIDATE, 0, error);
}
if (server->is_initialized == 0U) {
return Result_With_Error(IEC104_TCP_INVALID_STATE, IEC104_TCP_STAGE_LIFECYCLE, 0, error);
}
server->in_poll = 1U;
result = Process_Events(server, ready, error);
server->in_poll = 0U;
return result;
}
/**
* 功能:为独立TCP循环组合准备、一次平台等待及同一套有界推进。
* 输入参数:server——已Init的句柄;max_wait_ms——网络等待上限,0不等待,UINT32_MAX仍为有限值。
* 输出参数:error——可选诊断;返回值沿用Prepare/等待/Process中首个失败结果,全部成功为OK。
* 说明:等待只出现在Wait_For_Events;不再保留另一套接收/发送/超时调度。
* 即使本地工作或协议期限要求立即处理,也先做0毫秒就绪查询,合并已有输入再推进。
* max_wait_ms只约束网络等待;客户端故障保留监听,服务级失败由应用Close。
*/
IEC104_TcpResult_t IEC104_TcpServer_Poll(
IEC104_TcpServer_t *server, uint32_t max_wait_ms, IEC104_TcpError_t *error)
{
IEC104_TcpWaitInfo_t info;
IEC104_TcpReady_t ready;
IEC104_TcpResult_t result = Check_Server(server, error);
if (result != IEC104_TCP_OK) {
return result;
}
if (server->is_initialized == 0U) {
return Result_With_Error(IEC104_TCP_INVALID_STATE, IEC104_TCP_STAGE_LIFECYCLE, 0, error);
}
server->in_poll = 1U;
result = Prepare_Events(server, &info, error);
if (result == IEC104_TCP_OK) {
result = Wait_For_Events(server, &info, max_wait_ms, &ready, error);
}
if (result == IEC104_TCP_OK) {
result = Process_Events(server, &ready, error);
}
server->in_poll = 0U;
return result;
}
/**
* 功能:从FIFO复制并移除一个完整事件;不依赖网络资源仍然打开。
* 输入参数:server——尚未Destroy的句柄;event——业务提供的非NULL输出存储。
* 返回值:READY写出独立副本,EMPTY/ERROR保持输出不变。
* 说明:这里只消费队列并安排启停续发/回执转移/接入重试,不补做I/O、释放协议帧或收尾。
* READY表示事件已取出;应用处理失败时自行保存副本,本函数不会重新投递。
*/
IEC104_TcpEventResult_t IEC104_TcpServer_NextEvent(IEC104_TcpServer_t *server, IEC104_TcpEvent_t *event)
{
TcpEventHeader_t header;
IEC104_TcpEvent_t copy;
uint32_t event_bytes, remaining_used;
uint16_t remaining_count, remaining_state_count;
int state_event;
if (!Server_Owned(server) || event == NULL || server->in_poll != 0U || !Event_Queue_Valid(server)) {
return IEC104_TCP_EVENT_ERROR;
}
if (server->event_count == 0U) {
return IEC104_TCP_EVENT_EMPTY;
}
Event_Ring_Read(server, server->event_head, &header, sizeof(header));
if (header.generation == 0U || !Event_Payload_Length_Valid(header.type, header.payload_length)) {
return IEC104_TCP_EVENT_ERROR;
}
event_bytes = (uint32_t)sizeof(header) + header.payload_length;
state_event = Is_State_Event(header.type);
if (event_bytes > server->event_used || (state_event && server->state_event_count == 0U)) {
return IEC104_TCP_EVENT_ERROR;
}
remaining_used = server->event_used - event_bytes;
remaining_count = (uint16_t)(server->event_count - 1U);
remaining_state_count = (uint16_t)(server->state_event_count - (state_event ? 1U : 0U));
if (remaining_state_count > remaining_count ||
remaining_used < remaining_state_count * TCP_EVENT_CONNECTION_BYTES +
(remaining_count - remaining_state_count) * TCP_EVENT_I_MIN_BYTES ||
remaining_used > remaining_state_count * TCP_EVENT_CONNECTION_BYTES +
(remaining_count - remaining_state_count) * TCP_EVENT_I_MAX_BYTES) {
return IEC104_TCP_EVENT_ERROR;
}
/* 展开到清零的公开结构;短APDU后的数组尾部不泄漏旧环内容,历史代次照常交付。 */
memset(&copy, 0, sizeof(copy));
copy.type = header.type;
copy.generation = header.generation;
Event_Ring_Read(server, Event_Ring_Advance(server->event_head, sizeof(header)),
&copy.data, header.payload_length);
if (copy.type == IEC104_TCP_EVENT_I_FRAME &&
(copy.data.i_frame.apdu_length < IEC104_APCI_LEN ||
copy.data.i_frame.apdu_length > sizeof(copy.data.i_frame.apdu) ||
TCP_EVENT_I_PREFIX + copy.data.i_frame.apdu_length != header.payload_length)) {
return IEC104_TCP_EVENT_ERROR;
}
/* 所有检查通过后才写输出并提交出队;ERROR时游标、计数和输出均保持原样。 */
*event = copy;
server->event_head = Event_Ring_Advance(server->event_head, event_bytes);
server->event_used = remaining_used;
server->event_count = remaining_count;
server->state_event_count = remaining_state_count;
if (server->is_initialized != 0U) {
/* 新空位可交付保留I帧、继续启停CON、转移回执或接入;不能只等新的socket边沿。 */
server->process_pending = 1U;
if (server->client_socket == IEC104_NET_INVALID_SOCKET) {
server->accept_pending = 1U;
}
}
return IEC104_TCP_EVENT_READY;
}
/**
* 功能:把主动上报复制到服务级NORMAL队列,不要求已经建立TCP或完成STARTDT。
* 输入参数:server——已Init且未Close的服务;asdu/asdu_length——完整ASDU及长度,不含APCI。
* 返回值:OK已复制,WAIT编号窗口暂受阻,ERROR参数或生命周期非法,FATAL服务队列异常。
* 说明:仅本层访问服务队列;有连接时队列使用当前代次,离线时使用0,业务不携带内部绑定身份。
* EOF后的新事件也可入队;全部未编号NORMAL均留给下一连接,离线入队不触发推进空转。
*/
IEC104_TcpSubmitResult_t IEC104_TcpServer_Enqueue_Event(
IEC104_TcpServer_t *server, const uint8_t *asdu, uint16_t asdu_length)
{
IEC104_AsduQueueStatus_t status;
if (!Server_Owned(server) || server->is_initialized == 0U || server->in_poll != 0U) {
return IEC104_TCP_SUBMIT_ERROR;
}
if (server->queue_initialized == 0U ||
IEC104_AsduQueue_Get_Status(&server->queue, &status) != IEC104_ASDU_QUEUE_OK ||
status.connection_generation !=
(server->client_socket == IEC104_NET_INVALID_SOCKET ? 0U : server->generation)) {
server->queue_initialized = 0U;
return IEC104_TCP_SUBMIT_FATAL;
}
switch (IEC104_AsduQueue_Enqueue(&server->queue, status.connection_generation,
IEC104_ASDU_PRIORITY_NORMAL, asdu, asdu_length)) {
case IEC104_ASDU_QUEUE_OK:
if (server->client_socket != IEC104_NET_INVALID_SOCKET && server->rx_eof == 0U) {
server->process_pending = 1U;
}
return IEC104_TCP_SUBMIT_OK;
case IEC104_ASDU_QUEUE_FULL: return IEC104_TCP_SUBMIT_WAIT;
default: return IEC104_TCP_SUBMIT_ERROR;
}
}
/**
* 功能:提交当前会话的应答,通过启停准入后尝试直接准备并非阻塞写入一次,暂不能准备时复制到HIGH。
* 输入参数:generation——原I_FRAME事件的连接代次;asdu/asdu_length——完整ASDU及长度。
* 返回值:OK已接手原文,WAIT未接手、需业务保留重试;ERROR参数/身份/阶段非法,FATAL服务或会话失败。
* 调用关系:业务 -> Connection_Submit_Response -> SendManager统一交接;SEND_READY时调用Write_Output一次。
* 说明:OK包括排队、暂不可写、部分写入和本地整帧完成,不表示对端已ACK,不能重复提交同一应答。
* 仅STARTED+IDLE时接手;否则ERROR且不入HIGH,不因拒绝断连。
* 新STOP受理时HIGH待发应答作废,无逐条事件;当前I帧及ACK记录继续排空,重复START不清理HIGH。
* 部分写入及无进展保留SendBuffer中的当前帧,绝不重新放入HIGH;快速发送也遵守重试暂停。
* FATAL可能发生在已写出部分或整帧之后,须结合RECORD/DISCONNECTED处理,不能直接重放。
* 在事件循环线程处理I_FRAME时调用,携带该事件generation;调用后重新Prepare更新调度条件。
*/
static IEC104_TcpSubmitResult_t Send_Response(
IEC104_TcpServer_t *server, uint64_t generation,
const uint8_t *asdu, uint16_t asdu_length, int bypass_queue)
{
IEC104_SendBufferHeadInfo_t head;
IEC104_ConnectionResult_t submitted;
IEC104_TcpSubmitResult_t result;
if (!Server_Owned(server) || server->is_initialized == 0U || server->in_poll != 0U ||
server->client_socket == IEC104_NET_INVALID_SOCKET || server->rx_eof != 0U ||
generation != server->generation || asdu == NULL ||
asdu_length < IEC104_ASDU_HEADER_LEN || asdu_length > IEC104_MAX_ASDU_LEN) {
return IEC104_TCP_SUBMIT_ERROR;
}
if (server->queue_initialized == 0U) {
return IEC104_TCP_SUBMIT_FATAL;
}
/* 与Process共享串行调度保护;本次不accept/recv、不等待、不循环排空其他帧。 */
server->in_poll = 1U;
server->event_budget = TCP_EVENT_BUDGET;
server->event_yield = 0U;
submitted = bypass_queue ?
IEC104_Connection_Try_Submit_Response(&server->connection, generation,
asdu, asdu_length, Now_Ms(), &head) :
IEC104_Connection_Submit_Response(&server->connection, generation,
asdu, asdu_length, Now_Ms(), &head);
switch (submitted) {
case IEC104_CONNECTION_SEND_READY:
case IEC104_CONNECTION_OK:
/* 只有接手应答才新增提交续跑;WAIT/ERROR保留已有唤醒及后续事件交付。 */
server->process_pending = 1U;
result = IEC104_TCP_SUBMIT_OK;
break;
case IEC104_CONNECTION_WAIT: result = IEC104_TCP_SUBMIT_WAIT; break;
case IEC104_CONNECTION_ERROR: result = IEC104_TCP_SUBMIT_ERROR; break;
case IEC104_CONNECTION_FATAL:
Drop_Client(server, 0, 0);
result = IEC104_TCP_SUBMIT_FATAL;
break;
default:
(void)Drop_Local_Fault(server, IEC104_TCP_FAULT_INTERNAL);
result = IEC104_TCP_SUBMIT_FATAL;
break;
}
if (server->client_socket != IEC104_NET_INVALID_SOCKET) {
/* 提交时的Drive也可能释放接收帧或确认记录,即使本次应答未接手仍须交付这些事实。 */
if (!Deliver_I_Frame(server) || !Consume_Records(server, NULL)) {
result = IEC104_TCP_SUBMIT_FATAL;
} else if (submitted == IEC104_CONNECTION_SEND_READY) {
server->write_pending = 1U;
if (Write_Output(server, &head, NULL) < 0) {
result = IEC104_TCP_SUBMIT_FATAL;
}
}
if (server->client_socket != IEC104_NET_INVALID_SOCKET && !Schedule_Protocol_Work(server)) {
result = IEC104_TCP_SUBMIT_FATAL;
}
}
server->in_poll = 0U;
return result;
}
IEC104_TcpSubmitResult_t IEC104_TcpServer_Send_Response(
IEC104_TcpServer_t *server, uint64_t generation,
const uint8_t *asdu, uint16_t asdu_length)
{
return Send_Response(server, generation, asdu, asdu_length, 0);
}
/* 等待时不保存ASDU;成功后仍使用原部分写入、发送期限、记录和ACK路径。 */
IEC104_TcpSubmitResult_t IEC104_TcpServer_Try_Send_Response(
IEC104_TcpServer_t *server, uint64_t generation,
const uint8_t *asdu, uint16_t asdu_length)
{
return Send_Response(server, generation, asdu, asdu_length, 1);
}
/**
* 功能:返回当前客户端的对端地址及代次,不访问Net或Connection内部对象。
* 输出参数:peer仅OK时完整写出;失败保持原值,error按统一服务结果填写。
* 返回值:OK、INVALID_HANDLE/ARGUMENT/STATE、BUSY或NOT_CONNECTED。
* 说明:地址在Accept成功时转换并保存,查询不执行系统调用;关闭后的历史地址从事件副本读取。
*/
IEC104_TcpResult_t IEC104_TcpServer_Get_Remote_Address(
const IEC104_TcpServer_t *server, IEC104_TcpPeerInfo_t *peer, IEC104_TcpError_t *error)
{
IEC104_TcpResult_t result = Check_Server(server, error);
if (result != IEC104_TCP_OK) {
return result;
}
if (peer == NULL) {
return Result_With_Error(IEC104_TCP_INVALID_ARGUMENT, IEC104_TCP_STAGE_VALIDATE, 0, error);
}
if (server->is_initialized == 0U) {
return Result_With_Error(IEC104_TCP_INVALID_STATE, IEC104_TCP_STAGE_LIFECYCLE, 0, error);
}
if (server->client_socket == IEC104_NET_INVALID_SOCKET) {
return Result_With_Error(IEC104_TCP_NOT_CONNECTED, IEC104_TCP_STAGE_LIFECYCLE, 0, error);
}
*peer = server->peer;
return IEC104_TCP_OK;
}
/**
* 功能:主动关闭匹配代次的当前主站,继续监听并保留服务级NORMAL。
* 输入参数:generation——原事件或Get_Remote_Address取得的非零身份,不能用最新代次替换旧操作的目标。
* 返回值:OK已断开;INVALID_*、BUSY、NOT_CONNECTED、STALE_GENERATION为操作前拒绝,不改状态。
* INTERNAL_ERROR/QUEUE_STATE表示客户端已收尾,但服务队列异常,调用方须Close服务。
* 说明:复用Drop_Client交付记录和断开事件,不发送STOPDT、不排空应答、不伪造故障或ACK。
* 本函数不accept;登记本地续跑,使下一次Process查看监听,支持已有接入就绪和边沿触发。
*/
IEC104_TcpResult_t IEC104_TcpServer_Close_Connection(
IEC104_TcpServer_t *server, uint64_t generation, IEC104_TcpError_t *error)
{
IEC104_TcpResult_t result = Check_Server(server, error);
if (result != IEC104_TCP_OK) {
return result;
}
if (generation == 0U) {
return Result_With_Error(IEC104_TCP_INVALID_ARGUMENT, IEC104_TCP_STAGE_VALIDATE, 0, error);
}
if (server->is_initialized == 0U) {
return Result_With_Error(IEC104_TCP_INVALID_STATE, IEC104_TCP_STAGE_LIFECYCLE, 0, error);
}
if (server->client_socket == IEC104_NET_INVALID_SOCKET) {
return Result_With_Error(IEC104_TCP_NOT_CONNECTED, IEC104_TCP_STAGE_LIFECYCLE, 0, error);
}
if (generation != server->generation) {
return Result_With_Error(IEC104_TCP_STALE_GENERATION, IEC104_TCP_STAGE_GENERATION, 0, error);
}
server->in_poll = 1U;
Drop_Client(server, 0, 0);
/* Close整个服务才清空队列或撤销监听。预留事件未消费完时,原接入背压规则仍然有效。 */
server->accept_pending = 1U;
server->process_pending = 1U;
server->in_poll = 0U;
if (server->queue_initialized == 0U) {
return Result_With_Error(IEC104_TCP_INTERNAL_ERROR, IEC104_TCP_STAGE_QUEUE_STATE, 0, error);
}
return IEC104_TCP_OK;
}
/**
* 功能:读取服务、最近协议会话及两级ASDU队列的公开状态,所有数据均按值交付。
* 输入参数:server——尚未Destroy的句柄;status——业务提供的非NULL输出存储。
* 返回值:OK已写入快照;INVALID_HANDLE/INVALID_ARGUMENT/BUSY时status保持原值。
* 输出参数:error始终表达本次查询结果,允许NULL;snapshot中的last_socket_error是历史网络诊断。
* 说明:Create后、Init失败后及Close后均可查询,不执行I/O,也不推进协议状态。
* 接入暂停按当前时钟判断;接入错误独立保留,Close不清历史诊断,下一次Init重置。
* connected反映当前TCP连接;connection仅在connection_status_valid为1时有效,可能为历史会话。
* 队列快照独立于连接,离线仍可查询NORMAL;asdu_queue_status_valid为0时两级统计均清零。
* 队列/会话查询失败不隐式Close或修改内部状态,通过各自有效标志区分,其他服务字段仍有效。
* 不泄露socket、Connection地址或Manager布局,业务无需读取内部成员来判断状态。
*/
IEC104_TcpResult_t IEC104_TcpServer_Get_Status(
const IEC104_TcpServer_t *server, IEC104_TcpServerStatus_t *status, IEC104_TcpError_t *error)
{
IEC104_TcpServerStatus_t snapshot;
uint64_t now_ms;
IEC104_TcpResult_t result = Check_Server(server, error);
if (result != IEC104_TCP_OK) {
return result;
}
if (status == NULL) {
return Result_With_Error(IEC104_TCP_INVALID_ARGUMENT, IEC104_TCP_STAGE_VALIDATE, 0, error);
}
memset(&snapshot, 0, sizeof(snapshot));
snapshot.generation = server->generation;
snapshot.bound_port = server->bound_port;
snapshot.pending_event_count = server->event_count;
snapshot.pending_state_event_count = server->state_event_count;
snapshot.receive_paused = server->rx_event_pending;
snapshot.last_socket_error = server->last_socket_error;
now_ms = Now_Ms();
snapshot.accept_paused = (uint8_t)Accept_Retry_Paused(server, now_ms);
if (snapshot.accept_paused != 0U) {
snapshot.accept_retry_remaining_ms = (uint32_t)(server->accept_retry_at_ms - now_ms);
}
snapshot.last_accept_error = server->last_accept_error;
snapshot.consecutive_accept_failures = server->consecutive_accept_failures;
snapshot.listening = server->is_initialized;
snapshot.connected = server->client_socket != IEC104_NET_INVALID_SOCKET;
snapshot.connection_status_valid = (uint8_t)Read_Connection_Status(server, &snapshot.connection);
snapshot.asdu_queue_status_valid = (uint8_t)Read_Asdu_Queues_Status(
server, &snapshot.high_queue, &snapshot.normal_queue);
/* 先在局部对象中整理所有值,最后一次性写出;无协议会话时仍可返回服务状态。 */
*status = snapshot;
return IEC104_TCP_OK;
}
/**
* 功能:主动结束客户端和监听,清空内置队列,并释放本服务持有的网络后端引用。
* 输入参数:server——Create取得且尚未Destroy的对象,同一线程串行调用。
* 返回值:OK完成或本就未初始化,INVALID_HANDLE/BUSY表示无效句柄或非法重入。
* 输出参数:error记录本次接口操作结果,允许NULL;不会把历史网络诊断误作本次失败。
* 说明:不等待发送排空或对端ACK;剩余记录先复制为closing事件,再结束协议会话。
* 主动Close清空ASDU队列,但保留事件FIFO,应用可在资源关闭后继续NextEvent。
* Close不归还静态槽;事件读空后可重新Init,或调用Destroy结束句柄生命周期。
*/
IEC104_TcpResult_t IEC104_TcpServer_Close(IEC104_TcpServer_t *server, IEC104_TcpError_t *error)
{
IEC104_TcpResult_t result = Check_Server(server, error);
if (result != IEC104_TCP_OK) {
return result;
}
if (server->is_initialized == 0U) {
return IEC104_TCP_OK;
}
server->in_poll = 1U;
Drop_Client(server, 0, 0);
(void)IEC104_Net_Close(server->listen_socket, NULL);
server->listen_socket = IEC104_NET_INVALID_SOCKET;
server->listen_token = 0U;
server->accept_pending = 0U;
server->accept_retry_at_ms = 0U;
server->process_pending = 0U;
server->bound_port = 0U;
IEC104_AsduQueue_Abort(&server->queue);
server->queue_initialized = 0U;
IEC104_Net_Cleanup();
server->is_initialized = 0U;
server->in_poll = 0U;
return IEC104_TCP_OK;
}