namespace XFEExtension.NetCore.XFEConsole; using XFEExtension.NetCore.CyberComm; using XFEExtension.NetCore.DelegateExtension; using XFEExtension.NetCore.FormatExtension; /// /// XFE控制台终端客户端 /// public sealed class XFEConsoleTerminalClient : IAsyncDisposable { private TaskCompletionSource? authenticationSignal; private int authenticated; /// 创建主动连接调试程序服务器的终端客户端。 public XFEConsoleTerminalClient(string serverAddress, string password = "", string? terminalName = null, string? terminalId = null) { if (!Uri.TryCreate(serverAddress, UriKind.Absolute, out var serverUri) || serverUri.Scheme is not ("ws" or "wss")) throw new ArgumentException("调试服务器地址必须是有效的 ws 或 wss 地址。", nameof(serverAddress)); ServerAddress = serverUri.ToString(); Password = password ?? string.Empty; TerminalName = string.IsNullOrWhiteSpace(terminalName) ? Environment.MachineName : terminalName; TerminalId = string.IsNullOrWhiteSpace(terminalId) ? Guid.NewGuid().ToString("N") : terminalId; Client = new CyberCommClient(new CyberCommClientOptions { ServerUri = serverUri, Reconnect = new CyberCommReconnectOptions { Enabled = false, MaxAttempts = 0 }, RequestHeaders = new Dictionary { [XFEConsoleProtocol.TerminalNameHeader] = TerminalName, [XFEConsoleProtocol.TerminalIdHeader] = TerminalId, [XFEConsoleProtocol.PasswordHeader] = Password } }) { MessageHandler = HandleMessageAsync, ConnectionClosedHandler = HandleConnectionClosedAsync, ErrorHandler = HandleError }; } /// 底层通信客户端。 public CyberCommClient Client { get; } /// 调试程序服务器地址。 public string ServerAddress { get; } /// 连接密码。 public string Password { get; } /// 终端名称。 public string TerminalName { get; } /// 终端唯一标识。 public string TerminalId { get; } /// 服务器返回的调试程序名称。 public string RemoteProgramName { get; private set; } = string.Empty; /// 服务器返回的调试程序唯一标识。 public string RemoteProgramId { get; private set; } = string.Empty; /// 最近一次鉴权失败原因。 public string? AuthenticationFailureReason { get; private set; } /// 是否已完成密码鉴权。 public bool IsAuthenticated => Volatile.Read(ref authenticated) == 1; /// 鉴权成功并建立调试连接时触发。 public event XFEEventHandler? Connected; /// 已鉴权的调试连接断开时触发。 public event XFEEventHandler? Disconnected; /// 接收到调试程序的控制台输出时触发。 public event XFEEventHandler? MessageReceived; /// 底层通信发生错误时触发。 public event XFEEventHandler? ErrorOccurred; /// 连接服务器并等待密码鉴权结果。 /// 密码正确时返回 true;密码被拒绝时返回 false。 public async Task ConnectAsync(TimeSpan? timeout = null, CancellationToken cancellationToken = default) { if (IsAuthenticated && Client.IsConnected) return true; AuthenticationFailureReason = null; RemoteProgramName = string.Empty; RemoteProgramId = string.Empty; Interlocked.Exchange(ref authenticated, 0); authenticationSignal = new(TaskCreationOptions.RunContinuationsAsynchronously); using var timeoutSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); timeoutSource.CancelAfter(timeout ?? TimeSpan.FromSeconds(10)); try { await Client.ConnectAsync(timeoutSource.Token).ConfigureAwait(false); var accepted = await authenticationSignal.Task.WaitAsync(timeoutSource.Token).ConfigureAwait(false); if (!accepted) { await DisconnectIgnoringErrorsAsync().ConfigureAwait(false); return false; } Interlocked.Exchange(ref authenticated, 1); Connected?.Invoke(this); return true; } catch { await DisconnectIgnoringErrorsAsync().ConfigureAwait(false); throw; } finally { authenticationSignal = null; } } /// 主动断开调试连接。 public Task DisconnectAsync(CancellationToken cancellationToken = default) => Client.DisconnectAsync(cancellationToken); private ValueTask HandleMessageAsync(CyberCommClientEventArgs eventArgs, CancellationToken cancellationToken) { if (eventArgs.MessageType == BackMessageType.Error) { if (eventArgs.Exception is not null) HandleError(eventArgs.Exception); return ValueTask.CompletedTask; } if (eventArgs.MessageType != BackMessageType.Text || eventArgs.TextMessage is null) return ValueTask.CompletedTask; if (!IsAuthenticated && TryHandleAuthentication(eventArgs.TextMessage)) return ValueTask.CompletedTask; if (IsAuthenticated) MessageReceived?.Invoke(this, eventArgs.TextMessage); return ValueTask.CompletedTask; } private bool TryHandleAuthentication(string message) { try { var dictionary = new XFEDictionary(message); if (dictionary[XFEConsoleProtocol.MessageTypeKey] != XFEConsoleProtocol.AuthenticationMessage) return false; RemoteProgramName = dictionary[XFEConsoleProtocol.ProgramNameKey] ?? string.Empty; RemoteProgramId = dictionary[XFEConsoleProtocol.ProgramIdKey] ?? string.Empty; var accepted = dictionary[XFEConsoleProtocol.AuthenticatedKey] == "true"; AuthenticationFailureReason = accepted ? null : dictionary[XFEConsoleProtocol.ReasonKey] ?? "密码错误"; authenticationSignal?.TrySetResult(accepted); return true; } catch (Exception exception) { authenticationSignal?.TrySetException(new InvalidDataException("调试服务器返回了无效的鉴权消息。", exception)); return true; } } private ValueTask HandleConnectionClosedAsync(CancellationToken cancellationToken) { var wasAuthenticated = Interlocked.Exchange(ref authenticated, 0) == 1; authenticationSignal?.TrySetException(new IOException("调试服务器在完成鉴权前关闭了连接。")); if (wasAuthenticated) Disconnected?.Invoke(this); return ValueTask.CompletedTask; } private void HandleError(Exception exception) { authenticationSignal?.TrySetException(exception); ErrorOccurred?.Invoke(this, exception); } private async Task DisconnectIgnoringErrorsAsync() { try { await Client.DisconnectAsync().ConfigureAwait(false); } catch { } finally { Interlocked.Exchange(ref authenticated, 0); } } /// public async ValueTask DisposeAsync() { await DisconnectIgnoringErrorsAsync().ConfigureAwait(false); await Client.DisposeAsync().ConfigureAwait(false); } }