2161 lines
111 KiB
C
2161 lines
111 KiB
C
/*
|
||
* 子站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(©, 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, ©,
|
||
(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(©, 0, sizeof(copy));
|
||
copy.type = header.type;
|
||
copy.generation = header.generation;
|
||
Event_Ring_Read(server, Event_Ring_Advance(server->event_head, sizeof(header)),
|
||
©.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;
|
||
}
|