using System.Collections.Concurrent;
using System.Net.Sockets;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_QuartzJob;
using WIDESEAWCS_Tasks.SocketServer;
namespace WIDESEAWCS_Tasks
{
///
/// 机械手客户端连接管理器 - 负责 TCP 客户端连接管理和事件订阅
///
///
/// 核心职责:
/// 1. 维护与机械手设备的 TCP 连接状态
/// 2. 确保每个客户端只启动一次消息处理循环
/// 3. 管理客户端连接/断开的生命周期事件
/// 4. 提供发送消息到客户端的接口
///
public class RobotClientManager
{
///
/// TCP Socket 服务器实例,用于管理所有客户端连接
///
private readonly TcpSocketServer _tcpSocket;
///
/// 机械手状态管理器,用于读写设备状态
///
private readonly RobotStateManager _stateManager;
///
/// 跟踪已启动消息处理的客户端,避免重复启动
///
///
/// Key: 客户端 IP 地址
/// Value: 是否已启动(true 表示已启动)
/// 使用 ConcurrentDictionary 保证线程安全
///
private static readonly ConcurrentDictionary _handleClientStarted = new();
///
/// 事件订阅标志,确保 RobotReceived 事件只订阅一次
///
///
/// 使用原子操作 Interlocked.CompareExchange 保证全局只订阅一次
///
private static int _eventSubscribedFlag;
///
/// 客户端断开连接时触发的事件
///
///
/// 事件参数包含断开连接的机械手状态信息
///
public event EventHandler? OnClientDisconnected;
///
/// 构造函数
///
/// TCP Socket 服务器实例
/// 状态管理器实例
public RobotClientManager(TcpSocketServer tcpSocket, RobotStateManager stateManager)
{
_tcpSocket = tcpSocket;
_stateManager = stateManager;
}
///
/// 确保客户端已连接并订阅消息事件
///
///
/// 这是 RobotJob Execute 方法中的核心检查逻辑:
/// 1. 验证客户端是否在线
/// 2. 订阅断开事件(全局只执行一次)
/// 3. 确保消息处理循环已启动
/// 4. 防止重复启动 HandleClientAsync
///
/// 设备 IP 地址
/// 机器人设备信息
/// 客户端是否可用(已连接且消息处理已启动)
public bool EnsureClientSubscribed(string ipAddress, RobotCraneDevice robotCrane)
{
// 从 TCP 服务器获取所有已连接客户端的 ID 列表
var clientIds = _tcpSocket.GetClientIds();
// 检查该 IP 地址的客户端是否已连接
bool isClientConnected = clientIds.Contains(ipAddress);
// 如果客户端未连接
if (!isClientConnected)
{
// 清理该客户端的 HandleClientAsync 启动标志
// 以便下次重连时可以重新启动处理
_handleClientStarted.TryRemove(ipAddress, out _);
return false;
}
// 订阅一次 RobotReceived 事件(全局只订阅一次)
// 使用 Interlocked.CompareExchange 实现原子操作,确保线程安全
if (System.Threading.Interlocked.CompareExchange(ref _eventSubscribedFlag, 1, 0) == 0)
{
// 绑定客户端断开连接的事件处理
_tcpSocket.RobotReceived += OnRobotReceived;
// 记录日志(注意:日志内容为"客户端已断开连接",可能是遗留的占位文本)
QuartzLogger.Warn($"客户端已断开连接", robotCrane.DeviceName);
}
// 从 TCP 服务器的客户端字典中获取 TcpClient 对象
TcpClient? tcpClient = null;
_tcpSocket._clients.TryGetValue(ipAddress, out tcpClient);
// 如果获取失败(虽然 isClientConnected 为 true,但可能存在字典不同步的情况)
if (tcpClient == null)
{
// 移除启动标志,返回 false 表示客户端不可用
_handleClientStarted.TryRemove(ipAddress, out _);
return false;
}
// 检查是否已经为这个客户端启动过消息处理循环
bool alreadyStarted = _handleClientStarted.TryGetValue(ipAddress, out _);
// 如果尚未启动,则启动消息处理循环
if (!alreadyStarted)
{
// 记录日志
QuartzLogger.Info($"启动客户端消息处理", robotCrane.DeviceName);
// 获取最新的状态对象
var latestStateForSubscribe = _stateManager.GetState(ipAddress);
if (latestStateForSubscribe != null)
{
// 标记为已启动,防止重复启动
_handleClientStarted[ipAddress] = true;
// 异步启动客户端消息处理循环
// 使用 TaskContinuationOptions.OnlyOnFaulted 捕获异常情况
_ = _tcpSocket.HandleClientAsync(tcpClient, robotCrane.IPAddress, _tcpSocket._cts.Token, latestStateForSubscribe)
.ContinueWith(t =>
{
// 如果处理出现异常
if (t.IsFaulted)
{
// 记录错误日志
QuartzLogger.Info($"监听客户端消息事件异常", robotCrane.DeviceName);
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] HandleClientAsync error: {t.Exception?.GetBaseException().Message}");
// 发生错误时,移除启动标志,允许下次重试
_handleClientStarted.TryRemove(ipAddress, out _);
}
}, TaskContinuationOptions.OnlyOnFaulted);
// 安全更新状态,标记为已订阅消息事件
_stateManager.TryUpdateStateSafely(ipAddress, s =>
{
s.IsEventSubscribed = true;
return s;
});
}
}
// 返回 true 表示客户端可用
return true;
}
///
/// 事件处理:客户端断开连接时调用
///
///
/// 触发时机:当 TCP 服务器检测到客户端断开连接时
/// 处理逻辑:
/// 1. 清理 HandleClientAsync 启动标志
/// 2. 重置设备状态(取消订阅、清除动作和状态)
/// 3. 触发 OnClientDisconnected 事件通知上层
///
/// 断开连接的客户端 IP 地址
/// 固定返回 null,因为是事件处理器而非真正的消息处理器
private Task OnRobotReceived(string clientId)
{
// 移除该客户端的 HandleClientAsync 启动标志
_handleClientStarted.TryRemove(clientId, out _);
// 重置该客户端的状态信息
_stateManager.TryUpdateStateSafely(clientId, state =>
{
state.IsEventSubscribed = false; // 取消订阅标志
state.CurrentAction = ""; // 清除当前动作
state.OperStatus = ""; // 清除运行状态
state.RobotArmObject = 0; // 重置手臂对象状态
state.RobotControlMode = 0; // 重置控制模式
state.RobotRunMode = 0; // 重置运行模式
return state;
});
// 触发客户端断开连接事件,通知上层(如 RobotJob)
// 使用空的状态对象作为后备(如果获取不到)
OnClientDisconnected?.Invoke(this, _stateManager.GetState(clientId) ?? new RobotSocketState { IPAddress = clientId });
// 返回 null,因为这是事件处理而非真正的消息路由
return Task.FromResult(null);
}
///
/// 检查客户端是否已连接
///
/// 设备 IP 地址
/// 如果已连接则返回 true
public bool IsClientConnected(string ipAddress)
{
// 获取所有已连接客户端的 ID 列表
var clientIds = _tcpSocket.GetClientIds();
// 检查列表中是否包含指定的 IP 地址
return clientIds.Contains(ipAddress);
}
///
/// 发送消息到客户端
///
///
/// 封装 TcpSocketServer 的发送方法,提供更简洁的接口给业务层
///
/// 目标客户端 IP 地址
/// 要发送的消息内容
/// 发送是否成功
public async Task SendToClientAsync(string ipAddress, string message)
{
return await _tcpSocket.SendToClientAsync(ipAddress, message);
}
///
/// 获取 TcpSocketServer 引用
///
///
/// RobotJob 可能需要直接访问 TcpSocketServer 进行配置
/// 此属性提供只读访问
///
public TcpSocketServer TcpSocket => _tcpSocket;
}
}