using System.Net.Sockets; namespace Flyshot.Runtime.Fanuc.Protocol; /// /// FANUC TCP 10010 状态通道客户端,持续接收状态帧并缓存最新快照。 /// public sealed class FanucStateClient : IDisposable { private readonly object _stateLock = new(); private TcpClient? _tcpClient; private NetworkStream? _stream; private CancellationTokenSource? _receiveCts; private Task? _receiveTask; private FanucStateFrame? _latestFrame; private bool _disposed; /// /// 获取当前是否已建立连接。 /// public bool IsConnected => _tcpClient?.Connected ?? false; /// /// 建立到 FANUC 控制柜 TCP 10010 状态通道的连接并启动后台接收循环。 /// /// 控制柜 IP 地址。 /// 状态通道端口,默认 10010。 /// 取消令牌。 public async Task ConnectAsync(string ip, int port = 10010, CancellationToken cancellationToken = default) { ObjectDisposedException.ThrowIf(_disposed, this); if (string.IsNullOrWhiteSpace(ip)) { throw new ArgumentException("IP 不能为空。", nameof(ip)); } if (_tcpClient is not null) { throw new InvalidOperationException("状态通道已经连接,请先 Disconnect。"); } _tcpClient = new TcpClient { NoDelay = true }; await _tcpClient.ConnectAsync(ip, port, cancellationToken).ConfigureAwait(false); _stream = _tcpClient.GetStream(); _receiveCts = new CancellationTokenSource(); _receiveTask = Task.Run(() => ReceiveLoopAsync(_receiveCts.Token), _receiveCts.Token); } /// /// 断开状态通道并停止后台接收循环。 /// public void Disconnect() { ObjectDisposedException.ThrowIf(_disposed, this); _receiveCts?.Cancel(); try { _receiveTask?.Wait(TimeSpan.FromSeconds(2)); } catch (AggregateException) { // 后台循环可能因取消而抛出 OperationCanceledException,忽略即可。 } _receiveTask?.Dispose(); _receiveTask = null; _receiveCts?.Dispose(); _receiveCts = null; _stream?.Dispose(); _stream = null; _tcpClient?.Dispose(); _tcpClient = null; lock (_stateLock) { _latestFrame = null; } } /// /// 获取最近一次解析的状态帧;若尚未收到任何帧则返回 null。 /// /// 最新状态帧或 null。 public FanucStateFrame? GetLatestFrame() { ObjectDisposedException.ThrowIf(_disposed, this); lock (_stateLock) { return _latestFrame; } } /// /// 释放客户端资源。 /// public void Dispose() { if (_disposed) { return; } _disposed = true; _receiveCts?.Cancel(); try { _receiveTask?.Wait(TimeSpan.FromSeconds(2)); } catch (AggregateException) { // 忽略取消异常。 } _receiveTask?.Dispose(); _receiveCts?.Dispose(); _stream?.Dispose(); _tcpClient?.Dispose(); } /// /// 后台循环:持续从流中读取固定长度状态帧并更新缓存。 /// private async Task ReceiveLoopAsync(CancellationToken cancellationToken) { if (_stream is null) { return; } var buffer = new byte[FanucStateProtocol.StateFrameLength]; try { while (!cancellationToken.IsCancellationRequested) { await ReadExactAsync(buffer, cancellationToken).ConfigureAwait(false); var frame = FanucStateProtocol.ParseFrame(buffer); lock (_stateLock) { _latestFrame = frame; } } } catch (OperationCanceledException) { // 正常取消,无需处理。 } catch (IOException) { // 连接断开,退出循环。 } catch (InvalidDataException) { // 解析到异常帧,退出循环由上层重连。 } } /// /// 从流中精确读取固定长度字节。 /// private async Task ReadExactAsync(byte[] buffer, CancellationToken cancellationToken) { if (_stream is null) { throw new InvalidOperationException("状态通道未连接。"); } var totalRead = 0; while (totalRead < buffer.Length) { var read = await _stream.ReadAsync(buffer.AsMemory(totalRead), cancellationToken).ConfigureAwait(false); if (read == 0) { throw new IOException("状态通道已断开,读取到 EOF。"); } totalRead += read; } } }