/* * 子站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 #include /* 环内只保存头部和对应公开载荷的有效前缀;包含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; }