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; } }