|
|
@@ -37,6 +37,10 @@ namespace SWRIS.Core
|
|
|
public event EventHandler<ClockResultReceivedEventArgs> ClockResultReceived;
|
|
|
public event EventHandler<EncoderDirectionResultReceivedEventArgs> EncoderDirectionResultReceived;
|
|
|
public event EventHandler<HeartbeatReceviedEventArgs> HeartbeatReceived;
|
|
|
+ /// <summary>
|
|
|
+ /// 数据激活状态变化事件(数据在正常收发/数据静默切换时触发)
|
|
|
+ /// </summary>
|
|
|
+ public event EventHandler<DataActiveStateChangedEventArgs> DataActiveStateChanged;
|
|
|
public event EventHandler<DebugMessageReceivedEventArgs> DebugMessageReceived;
|
|
|
private Socket _listener;
|
|
|
private bool _isRunning;
|
|
|
@@ -44,6 +48,18 @@ namespace SWRIS.Core
|
|
|
private readonly int _port;
|
|
|
private readonly ByteTransform _byteTransform;
|
|
|
private const string PASSWORD = "WNDTM4";
|
|
|
+ /// <summary>
|
|
|
+ /// 心跳检测周期(秒)
|
|
|
+ /// </summary>
|
|
|
+ private const int HeartbeatCheckIntervalSeconds = 5;
|
|
|
+ /// <summary>
|
|
|
+ /// 心跳超时时间(秒):超过该时间未收到任何数据则判定连接失效
|
|
|
+ /// </summary>
|
|
|
+ private const int HeartbeatTimeoutSeconds = 15;
|
|
|
+ /// <summary>
|
|
|
+ /// 数据静默超时时间(秒):超过该时间未收到有效数据则数据激活状态置为失活
|
|
|
+ /// </summary>
|
|
|
+ private const int DataTimeoutSeconds = 15;
|
|
|
private static ConcurrentDictionary<string, ClientState> _connectedClients;
|
|
|
private CancellationTokenSource ctsToken = new CancellationTokenSource();
|
|
|
public TcpServerFrame(string ipAddress, int port)
|
|
|
@@ -155,15 +171,31 @@ namespace SWRIS.Core
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
- await Task.Delay(TimeSpan.FromSeconds(5), token.Token);
|
|
|
+ await Task.Delay(TimeSpan.FromSeconds(HeartbeatCheckIntervalSeconds), token.Token);
|
|
|
|
|
|
+ var now = DateTime.UtcNow;
|
|
|
foreach (var client in _connectedClients.ToArray())
|
|
|
{
|
|
|
- if ((DateTime.UtcNow - client.Value.LastActiveTime).TotalSeconds > 15)
|
|
|
+ var clientState = client.Value;
|
|
|
+
|
|
|
+ // 数据在正常收发时,链路是活跃的:即使心跳包缺失也应保持连接
|
|
|
+ // 避免设备持续上报实时数据却被判为离线
|
|
|
+ if ((now - clientState.LastDataReceivedTime).TotalSeconds <= DataTimeoutSeconds)
|
|
|
{
|
|
|
- SafeCloseClient(client.Value);
|
|
|
+ // 数据激活即刷新连接活跃时间,心跳缺失不再触发误判
|
|
|
+ clientState.LastActiveTime = now;
|
|
|
+ SetDataActiveState(clientState, true);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 数据已静默,置为失活后再按心跳超时判定
|
|
|
+ SetDataActiveState(clientState, false);
|
|
|
+
|
|
|
+ if ((now - clientState.LastActiveTime).TotalSeconds > HeartbeatTimeoutSeconds)
|
|
|
+ {
|
|
|
+ SafeCloseClient(clientState);
|
|
|
DebugMessageReceived?.Invoke(this,
|
|
|
- new DebugMessageReceivedEventArgs($"心跳超时,强制断开", ipAddress: client.Value.IpAddress));
|
|
|
+ new DebugMessageReceivedEventArgs($"心跳超时,强制断开", ipAddress: clientState.IpAddress));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -299,7 +331,8 @@ namespace SWRIS.Core
|
|
|
byte[] heartBeatData = new byte[2];
|
|
|
client.ReceiveBuffer.Read(heartBeatData, 0, 2);
|
|
|
|
|
|
- client.LastActiveTime = DateTime.UtcNow;
|
|
|
+ // 心跳也是有效数据,统一走数据激活标记
|
|
|
+ MarkDataActive(client);
|
|
|
RespondHeartbeat(heartBeatData, client.IpAddress, client.ClientSocket);
|
|
|
|
|
|
HeartbeatReceived?.Invoke(this, new HeartbeatReceviedEventArgs(client.IpAddress));
|
|
|
@@ -349,6 +382,10 @@ namespace SWRIS.Core
|
|
|
byte[] payload = new byte[remainingLength];
|
|
|
Buffer.BlockCopy(packetData, payloadStart, payload, 0, remainingLength);
|
|
|
|
|
|
+ // 收到完整有效的业务数据包即标记数据激活,
|
|
|
+ // 使设备在持续上报实时数据时不会因心跳缺失被误判离线
|
|
|
+ MarkDataActive(client);
|
|
|
+
|
|
|
// 处理数据包
|
|
|
await ProcessEventByType(eventType, payload, client);
|
|
|
|
|
|
@@ -1471,14 +1508,55 @@ namespace SWRIS.Core
|
|
|
LogHelper.Error("发送开始或停止实时数据命令时错误", ex);
|
|
|
}
|
|
|
}
|
|
|
- private void SafeCloseClient(ClientState client)
|
|
|
+ /// <summary>
|
|
|
+ /// 标记客户端数据激活:收到任意有效数据(心跳包或业务数据包)时调用。
|
|
|
+ /// 数据在正常收发时保持连接活跃,避免心跳包缺失导致误判离线。
|
|
|
+ /// </summary>
|
|
|
+ private void MarkDataActive(ClientState client)
|
|
|
{
|
|
|
if (client == null)
|
|
|
return;
|
|
|
+
|
|
|
+ client.LastDataReceivedTime = DateTime.UtcNow;
|
|
|
+ client.LastActiveTime = DateTime.UtcNow;
|
|
|
+ SetDataActiveState(client, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 设置数据激活状态,仅在状态发生变化时对外触发事件
|
|
|
+ /// </summary>
|
|
|
+ private void SetDataActiveState(ClientState client, bool isDataActive)
|
|
|
+ {
|
|
|
+ if (client == null || client.IsDataActive == isDataActive)
|
|
|
+ return;
|
|
|
+
|
|
|
+ client.IsDataActive = isDataActive;
|
|
|
+ DataActiveStateChanged?.Invoke(this,
|
|
|
+ new DataActiveStateChangedEventArgs(client.IpAddress, isDataActive, client.LastDataReceivedTime));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取指定设备的数据激活状态(数据超时窗口内收到过有效数据)
|
|
|
+ /// </summary>
|
|
|
+ public bool IsDataActive(string ipAddress)
|
|
|
+ {
|
|
|
+ if (string.IsNullOrEmpty(ipAddress))
|
|
|
+ return false;
|
|
|
+ return _connectedClients.TryGetValue(ipAddress, out var client) && client.IsDataActive;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void SafeCloseClient(ClientState client)
|
|
|
+ {
|
|
|
+ // 已关闭则直接返回,避免心跳线程与接收回调重复触发断开事件
|
|
|
+ if (client == null || client.IsClosed)
|
|
|
+ return;
|
|
|
+ client.IsClosed = true;
|
|
|
try
|
|
|
{
|
|
|
ClientDisconnected?.Invoke(this, new ClientDisconnectedEventArgs(client.ClientSocket, client.IpAddress));
|
|
|
_connectedClients.TryRemove(client.IpAddress, out _);
|
|
|
+ // 连接断开后数据激活状态置为失活
|
|
|
+ SetDataActiveState(client, false);
|
|
|
var socket = client.ClientSocket;
|
|
|
client.ClientSocket = null;
|
|
|
if (socket != null)
|