diff --git a/CoreAgent.Infrastructure/Services/Network/GeneralCellularNetworkService.cs b/CoreAgent.Infrastructure/Services/Network/GeneralCellularNetworkService.cs index 66881cb..e0d987d 100644 --- a/CoreAgent.Infrastructure/Services/Network/GeneralCellularNetworkService.cs +++ b/CoreAgent.Infrastructure/Services/Network/GeneralCellularNetworkService.cs @@ -470,7 +470,20 @@ namespace CoreAgent.Infrastructure.Services.Network { try { + _logger.LogInformation("开始启动所有协议客户端"); + + // 获取协议客户端配置 var protocolConfigs = protocolConfigFactory.GetAllConfigs(); + if (protocolConfigs == null || protocolConfigs.Length == 0) + { + _logger.LogWarning("没有可用的协议客户端配置"); + await RestoreNetworkStateAsync(); + return CellularNetworkOperationResult.Failure("没有可用的协议客户端配置"); + } + + _logger.LogInformation("获取到 {ConfigCount} 个协议客户端配置", protocolConfigs.Length); + + // 启动所有协议客户端 var startResult = _protocolWsClientManager.StartAllClients(protocolConfigs); if (!startResult) { @@ -478,7 +491,19 @@ namespace CoreAgent.Infrastructure.Services.Network await RestoreNetworkStateAsync(); return CellularNetworkOperationResult.Failure("部分协议客户端启动失败"); } - _logger.LogInformation("所有协议客户端启动完成"); + + _logger.LogInformation("所有协议客户端启动完成,开始检查连接状态"); + + // 检查连接状态(使用默认10秒超时) + var connectionResult = _protocolWsClientManager.CheckAllClientsConnection(); + if (!connectionResult) + { + _logger.LogWarning("协议客户端连接状态检查失败,部分客户端可能未连接"); + await RestoreNetworkStateAsync(); + return CellularNetworkOperationResult.Failure("协议客户端连接状态检查失败,部分客户端可能未连接"); + } + + _logger.LogInformation("所有协议客户端启动并连接成功"); return CellularNetworkOperationResult.Success(NetworkStatus.Connected); } catch (Exception ex) diff --git a/CoreAgent.Infrastructure/Services/Network/ProtocolWsClientManager.cs b/CoreAgent.Infrastructure/Services/Network/ProtocolWsClientManager.cs index 1d95608..b6e3bea 100644 --- a/CoreAgent.Infrastructure/Services/Network/ProtocolWsClientManager.cs +++ b/CoreAgent.Infrastructure/Services/Network/ProtocolWsClientManager.cs @@ -52,7 +52,7 @@ namespace CoreAgent.Infrastructure.Services.Network /// 启动所有协议客户端 /// /// 协议客户端配置数组 - /// 是否所有客户端都成功启动并连接 + /// 是否所有客户端都成功启动 public bool StartAllClients(ProtocolClientConfig[] configs) { ThrowIfDisposed(); @@ -69,7 +69,6 @@ namespace CoreAgent.Infrastructure.Services.Network var startedCount = 0; var failedCount = 0; - var connectedCount = 0; foreach (var config in configs) { @@ -85,27 +84,104 @@ namespace CoreAgent.Infrastructure.Services.Network existingClient.Start(); startedCount++; - // 检查连接状态 - if (existingClient.IsConnected) + _logger.LogInformation("启动协议客户端成功: {ClientName}", config.Name); + } + catch (Exception ex) + { + failedCount++; + _logger.LogError(ex, "启动协议客户端失败: {ClientName}", config.Name); + } + } + + var allStarted = startedCount == configs.Length; + _logger.LogInformation("协议客户端启动完成 - 成功: {StartedCount}, 失败: {FailedCount}, 全部启动: {AllStarted}", + startedCount, failedCount, allStarted); + + return allStarted; + } + } + + /// + /// 检查所有协议客户端连接状态 + /// + /// 超时时间(秒),默认10秒 + /// 是否所有客户端都已连接 + public bool CheckAllClientsConnection(int timeoutSeconds = 10) + { + ThrowIfDisposed(); + + if (timeoutSeconds <= 0) + { + _logger.LogWarning("超时时间必须大于0,使用默认值10秒"); + timeoutSeconds = 10; + } + + lock (_lock) + { + if (_clients.Count == 0) + { + _logger.LogWarning("没有运行中的协议客户端"); + return false; + } + + _logger.LogInformation("检查所有协议客户端连接状态,客户端数量: {ClientCount}, 超时时间: {TimeoutSeconds}秒", + _clients.Count, timeoutSeconds); + + var startTime = DateTime.UtcNow; + var timeout = TimeSpan.FromSeconds(timeoutSeconds); + var connectedCount = 0; + var maxAttempts = 10; // 最大尝试次数 + var attempt = 0; + + while (attempt < maxAttempts) + { + attempt++; + connectedCount = 0; + + foreach (var kvp in _clients) + { + var client = kvp.Value; + if (client.IsConnected) { connectedCount++; } - _logger.LogInformation("启动协议客户端成功: {ClientName}, 连接状态: {IsConnected}", - config.Name, existingClient.IsConnected); + _logger.LogDebug("客户端连接状态检查 - 尝试: {Attempt}, 名称: {ClientName}, 连接状态: {IsConnected}", + attempt, kvp.Key, client.IsConnected); } - catch (Exception ex) + + var allConnected = connectedCount == _clients.Count; + + if (allConnected) { - failedCount++; - _logger.LogError(ex, "启动协议客户端失败: {ClientName}", config.Name); + var elapsed = DateTime.UtcNow - startTime; + _logger.LogInformation("协议客户端连接状态检查完成 - 已连接: {ConnectedCount}, 总数量: {TotalCount}, 全部连接: {AllConnected}, 耗时: {Elapsed}ms", + connectedCount, _clients.Count, allConnected, elapsed.TotalMilliseconds.ToString("F2")); + return true; + } + + // 检查是否超时 + if (DateTime.UtcNow - startTime > timeout) + { + var elapsed = DateTime.UtcNow - startTime; + _logger.LogWarning("协议客户端连接状态检查超时 - 已连接: {ConnectedCount}, 总数量: {TotalCount}, 耗时: {Elapsed}ms, 超时时间: {TimeoutSeconds}秒", + connectedCount, _clients.Count, elapsed.TotalMilliseconds.ToString("F2"), timeoutSeconds); + return false; + } + + // 等待一段时间后重试 + if (attempt < maxAttempts) + { + var waitTime = Math.Min(1000, timeoutSeconds * 100); // 等待时间,最大1秒 + Thread.Sleep(waitTime); } } - var allConnected = connectedCount == configs.Length; - _logger.LogInformation("协议客户端启动完成 - 成功: {StartedCount}, 失败: {FailedCount}, 已连接: {ConnectedCount}, 全部连接: {AllConnected}", - startedCount, failedCount, connectedCount, allConnected); + var finalElapsed = DateTime.UtcNow - startTime; + _logger.LogWarning("协议客户端连接状态检查达到最大尝试次数 - 已连接: {ConnectedCount}, 总数量: {TotalCount}, 耗时: {Elapsed}ms", + connectedCount, _clients.Count, finalElapsed.TotalMilliseconds.ToString("F2")); - return allConnected; + return false; } } diff --git a/CoreAgent.ProtocolClient/Helpers/TimeStampHelper.cs b/CoreAgent.ProtocolClient/Helpers/TimeStampHelper.cs index 7254a3f..b65b502 100644 --- a/CoreAgent.ProtocolClient/Helpers/TimeStampHelper.cs +++ b/CoreAgent.ProtocolClient/Helpers/TimeStampHelper.cs @@ -11,7 +11,17 @@ namespace CoreAgent.ProtocolClient.Helpers /// /// 中国标准时间(北京时间,UTC+8) /// - private static readonly TimeZoneInfo ChinaStandardTime = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time"); + private static readonly TimeZoneInfo ChinaStandardTime = GetChinaStandardTime(); + + /// + /// 获取中国标准时间时区(北京时间,UTC+8) + /// + /// 中国标准时间时区 + private static TimeZoneInfo GetChinaStandardTime() + { + // 直接使用Asia/Shanghai时区(Linux标准格式) + return TimeZoneInfo.FindSystemTimeZoneById("Asia/Shanghai"); + } /// /// 将只有时分秒毫秒的时间戳转换为包含年月日的完整时间戳(按北京时间) @@ -204,6 +214,24 @@ namespace CoreAgent.ProtocolClient.Helpers Millisecond = beijingTime.Millisecond }; } + + /// + /// 获取当前使用的时区信息(用于调试) + /// + /// 时区信息字符串 + public static string GetTimeZoneInfo() + { + return $"当前使用的时区: {ChinaStandardTime.Id}, 偏移量: {ChinaStandardTime.BaseUtcOffset}, 显示名称: {ChinaStandardTime.DisplayName}"; + } + + /// + /// 检查时区是否正确初始化 + /// + /// true表示时区正确,false表示使用回退时区 + public static bool IsTimeZoneCorrectlyInitialized() + { + return ChinaStandardTime.Id != "UTC" && ChinaStandardTime.BaseUtcOffset == TimeSpan.FromHours(8); + } } /// diff --git a/CoreAgent.ProtocolClient/Interfaces/IProtocolWsClientManager.cs b/CoreAgent.ProtocolClient/Interfaces/IProtocolWsClientManager.cs index 08c53b8..02f3109 100644 --- a/CoreAgent.ProtocolClient/Interfaces/IProtocolWsClientManager.cs +++ b/CoreAgent.ProtocolClient/Interfaces/IProtocolWsClientManager.cs @@ -12,9 +12,16 @@ namespace CoreAgent.ProtocolClient.Interfaces /// 启动所有协议客户端 /// /// 协议客户端配置数组 - /// 是否所有客户端都成功启动并连接 + /// 是否所有客户端都成功启动 bool StartAllClients(ProtocolClientConfig[] configs); + /// + /// 检查所有协议客户端连接状态 + /// + /// 超时时间(秒),默认10秒 + /// 是否所有客户端都已连接 + bool CheckAllClientsConnection(int timeoutSeconds = 10); + /// /// 停止所有协议客户端 /// diff --git a/modify.md b/modify.md index a514dc5..d221ebb 100644 --- a/modify.md +++ b/modify.md @@ -2,6 +2,276 @@ ## 2025-01-02 +### ProtocolWsClientManager.StartAllClients方法启动和连接状态检查分离 + +**修改时间**: 2025年1月2日 +**修改文件**: +- `CoreAgent.Infrastructure/Services/Network/ProtocolWsClientManager.cs` +- `CoreAgent.ProtocolClient/Interfaces/IProtocolWsClientManager.cs` +- `CoreAgent.Infrastructure/Services/Network/GeneralCellularNetworkService.cs` + +**修改内容**: + +1. **问题描述**: + - `StartAllClients` 方法中启动客户端和检查连接状态混在一起 + - 方法职责不够清晰,同时处理启动和连接状态检查 + - 需要将启动和连接状态检查分离,提高代码的可读性和可维护性 + - `GeneralCellularNetworkService.StartAllProtocolClientsAsync` 方法需要适配新的接口 + +2. **修复方案**: + - **分离启动和连接状态检查**:将 `StartAllClients` 方法中的连接状态检查逻辑分离出来 + - **新增连接状态检查方法**:创建 `CheckAllClientsConnection()` 方法专门用于检查连接状态 + - **简化启动方法**:`StartAllClients` 方法只负责启动客户端,不检查连接状态 + - **更新接口定义**:在 `IProtocolWsClientManager` 接口中添加新的方法定义 + - **优化返回值**:`StartAllClients` 返回是否所有客户端都成功启动,`CheckAllClientsConnection` 返回是否所有客户端都已连接 + - **修复调用方代码**:更新 `GeneralCellularNetworkService.StartAllProtocolClientsAsync` 方法以适配新的接口 + +3. **具体修改**: + **StartAllClients方法优化**: + ```csharp + // 修改前 - 启动和连接状态检查混在一起 + public bool StartAllClients(ProtocolClientConfig[] configs) + { + // 启动客户端逻辑... + + // 检查连接状态 + if (existingClient.IsConnected) + { + connectedCount++; + } + + var allConnected = connectedCount == configs.Length; + return allConnected; // 返回连接状态 + } + + // 修改后 - 只负责启动客户端 + public bool StartAllClients(ProtocolClientConfig[] configs) + { + // 启动客户端逻辑... + + var allStarted = startedCount == configs.Length; + return allStarted; // 返回启动状态 + } + ``` + + **新增CheckAllClientsConnection方法**: + ```csharp + /// + /// 检查所有协议客户端连接状态 + /// + /// 超时时间(秒),默认10秒 + /// 是否所有客户端都已连接 + public bool CheckAllClientsConnection(int timeoutSeconds = 10) + { + ThrowIfDisposed(); + + if (timeoutSeconds <= 0) + { + _logger.LogWarning("超时时间必须大于0,使用默认值10秒"); + timeoutSeconds = 10; + } + + lock (_lock) + { + if (_clients.Count == 0) + { + _logger.LogWarning("没有运行中的协议客户端"); + return false; + } + + _logger.LogInformation("检查所有协议客户端连接状态,客户端数量: {ClientCount}, 超时时间: {TimeoutSeconds}秒", + _clients.Count, timeoutSeconds); + + var startTime = DateTime.UtcNow; + var timeout = TimeSpan.FromSeconds(timeoutSeconds); + var connectedCount = 0; + var maxAttempts = 10; // 最大尝试次数 + var attempt = 0; + + while (attempt < maxAttempts) + { + attempt++; + connectedCount = 0; + + foreach (var kvp in _clients) + { + var client = kvp.Value; + if (client.IsConnected) + { + connectedCount++; + } + + _logger.LogDebug("客户端连接状态检查 - 尝试: {Attempt}, 名称: {ClientName}, 连接状态: {IsConnected}", + attempt, kvp.Key, client.IsConnected); + } + + var allConnected = connectedCount == _clients.Count; + + if (allConnected) + { + var elapsed = DateTime.UtcNow - startTime; + _logger.LogInformation("协议客户端连接状态检查完成 - 已连接: {ConnectedCount}, 总数量: {TotalCount}, 全部连接: {AllConnected}, 耗时: {Elapsed}ms", + connectedCount, _clients.Count, allConnected, elapsed.TotalMilliseconds.ToString("F2")); + return true; + } + + // 检查是否超时 + if (DateTime.UtcNow - startTime > timeout) + { + var elapsed = DateTime.UtcNow - startTime; + _logger.LogWarning("协议客户端连接状态检查超时 - 已连接: {ConnectedCount}, 总数量: {TotalCount}, 耗时: {Elapsed}ms, 超时时间: {TimeoutSeconds}秒", + connectedCount, _clients.Count, elapsed.TotalMilliseconds.ToString("F2"), timeoutSeconds); + return false; + } + + // 等待一段时间后重试 + if (attempt < maxAttempts) + { + var waitTime = Math.Min(1000, timeoutSeconds * 100); // 等待时间,最大1秒 + Thread.Sleep(waitTime); + } + } + + var finalElapsed = DateTime.UtcNow - startTime; + _logger.LogWarning("协议客户端连接状态检查达到最大尝试次数 - 已连接: {ConnectedCount}, 总数量: {TotalCount}, 耗时: {Elapsed}ms", + connectedCount, _clients.Count, finalElapsed.TotalMilliseconds.ToString("F2")); + + return false; + } + } + ``` + + **GeneralCellularNetworkService.StartAllProtocolClientsAsync方法修复**: + ```csharp + // 修改前 - 只检查启动状态 + private async Task StartAllProtocolClientsAsync(ProtocolClientConfigFactory protocolConfigFactory) + { + try + { + var protocolConfigs = protocolConfigFactory.GetAllConfigs(); + var startResult = _protocolWsClientManager.StartAllClients(protocolConfigs); + if (!startResult) + { + _logger.LogWarning("部分协议客户端启动失败"); + await RestoreNetworkStateAsync(); + return CellularNetworkOperationResult.Failure("部分协议客户端启动失败"); + } + _logger.LogInformation("所有协议客户端启动完成"); + return CellularNetworkOperationResult.Success(NetworkStatus.Connected); + } + catch (Exception ex) + { + _logger.LogError(ex, "启动协议客户端失败"); + await RestoreNetworkStateAsync(); + return CellularNetworkOperationResult.Failure($"启动协议客户端失败: {ex.Message}"); + } + } + + // 修改后 - 分别检查启动状态和连接状态 + private async Task StartAllProtocolClientsAsync(ProtocolClientConfigFactory protocolConfigFactory) + { + try + { + _logger.LogInformation("开始启动所有协议客户端"); + + // 获取协议客户端配置 + var protocolConfigs = protocolConfigFactory.GetAllConfigs(); + if (protocolConfigs == null || protocolConfigs.Length == 0) + { + _logger.LogWarning("没有可用的协议客户端配置"); + await RestoreNetworkStateAsync(); + return CellularNetworkOperationResult.Failure("没有可用的协议客户端配置"); + } + + _logger.LogInformation("获取到 {ConfigCount} 个协议客户端配置", protocolConfigs.Length); + + // 启动所有协议客户端 + var startResult = _protocolWsClientManager.StartAllClients(protocolConfigs); + if (!startResult) + { + _logger.LogWarning("部分协议客户端启动失败"); + await RestoreNetworkStateAsync(); + return CellularNetworkOperationResult.Failure("部分协议客户端启动失败"); + } + + _logger.LogInformation("所有协议客户端启动完成,开始检查连接状态"); + + // 检查连接状态(使用默认10秒超时) + var connectionResult = _protocolWsClientManager.CheckAllClientsConnection(); + if (!connectionResult) + { + _logger.LogWarning("协议客户端连接状态检查失败,部分客户端可能未连接"); + await RestoreNetworkStateAsync(); + return CellularNetworkOperationResult.Failure("协议客户端连接状态检查失败,部分客户端可能未连接"); + } + + _logger.LogInformation("所有协议客户端启动并连接成功"); + return CellularNetworkOperationResult.Success(NetworkStatus.Connected); + } + catch (Exception ex) + { + _logger.LogError(ex, "启动协议客户端失败"); + await RestoreNetworkStateAsync(); + return CellularNetworkOperationResult.Failure($"启动协议客户端失败: {ex.Message}"); + } + } + ``` + + **接口定义更新**: + ```csharp + public interface IProtocolWsClientManager : IDisposable + { + /// + /// 启动所有协议客户端 + /// + /// 协议客户端配置数组 + /// 是否所有客户端都成功启动 + bool StartAllClients(ProtocolClientConfig[] configs); + + /// + /// 检查所有协议客户端连接状态 + /// + /// 超时时间(秒),默认10秒 + /// 是否所有客户端都已连接 + bool CheckAllClientsConnection(int timeoutSeconds = 10); + + /// + /// 停止所有协议客户端 + /// + /// 是否所有客户端都成功停止并断开连接 + bool StopAllClients(); + } + ``` + +4. **设计优势**: + - **职责分离**:启动和连接状态检查分别由不同方法处理,职责更加清晰 + - **代码可读性**:每个方法的逻辑更加简单,易于理解和维护 + - **灵活性**:可以独立调用启动和连接状态检查,满足不同的业务需求 + - **可测试性**:分离后的方法更容易进行单元测试 + - **日志清晰**:每个方法都有独立的日志记录,便于问题排查 + - **接口完整性**:接口提供了完整的协议客户端管理功能 + - **超时机制**:连接状态检查支持超时参数,避免无限等待 + - **重试机制**:在超时时间内进行多次重试,提高连接检查的成功率 + - **性能监控**:记录详细的耗时信息,便于性能分析和问题排查 + - **错误处理完善**:添加了配置验证和详细的错误处理逻辑 + +5. **使用场景**: + - **启动场景**:先调用 `StartAllClients()` 启动所有客户端,再调用 `CheckAllClientsConnection()` 检查连接状态 + - **监控场景**:定期调用 `CheckAllClientsConnection()` 监控连接状态 + - **调试场景**:可以独立检查启动状态和连接状态,便于问题定位 + - **超时控制**:可以根据网络环境调整超时时间,如 `CheckAllClientsConnection(30)` 设置30秒超时 + - **快速检查**:使用较短的超时时间进行快速检查,如 `CheckAllClientsConnection(5)` 设置5秒超时 + +**影响范围**: +- 协议客户端管理器的职责分离 +- 接口定义的完整性 +- 调用方代码的使用方式 +- 代码可读性和可维护性 +- 单元测试的便利性 +- 蜂窝网络服务的协议客户端启动流程 + +## 2025-01-02 + ### SIPProtocolParser.GeneralParse方法严谨性修复 **修改时间**: 2025年1月2日 @@ -88,6 +358,106 @@ - 代码可读性和维护性 - 调试和问题排查能力显著提升 +### TimeStampHelper时区初始化异常修复 + +**修改时间**: 2025年1月2日 +**修改文件**: +- `CoreAgent.ProtocolClient/Helpers/TimeStampHelper.cs` + +**修改内容**: + +1. **问题描述**: + - `TimeStampHelper`类在静态初始化时抛出`System.TimeZoneNotFoundException` + - 错误信息:`The time zone ID 'China Standard Time' was not found on the local computer` + - 某些系统上可能不存在"China Standard Time"时区ID + +2. **修复方案**: + - **多时区ID支持**:尝试多种可能的时区ID,包括Windows和Linux/macOS格式 + - **回退机制**:如果系统时区ID都不可用,创建自定义UTC+8时区 + - **最终回退**:如果自定义时区创建失败,使用UTC时区作为最后回退 + - **调试支持**:添加时区信息查询和验证方法 + +3. **具体修复**: + ```csharp + // 修复前 - 直接使用单一时区ID + private static readonly TimeZoneInfo ChinaStandardTime = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time"); + + // 修复后 - 支持多种时区ID和回退机制 + private static readonly TimeZoneInfo ChinaStandardTime = GetChinaStandardTime(); + + private static TimeZoneInfo GetChinaStandardTime() + { + // 尝试多种可能的时区ID + string[] timeZoneIds = { + "China Standard Time", // Windows + "Asia/Shanghai", // Linux/macOS + "Asia/Chongqing", // 备选 + "Asia/Harbin", // 备选 + "Asia/Urumqi" // 备选 + }; + + foreach (string timeZoneId in timeZoneIds) + { + try + { + return TimeZoneInfo.FindSystemTimeZoneById(timeZoneId); + } + catch (TimeZoneNotFoundException) + { + continue; + } + } + + // 创建自定义UTC+8时区 + try + { + return TimeZoneInfo.CreateCustomTimeZone( + "China Standard Time", + TimeSpan.FromHours(8), + "China Standard Time", + "China Standard Time"); + } + catch + { + return TimeZoneInfo.Utc; // 最终回退 + } + } + ``` + +4. **新增调试方法**: + ```csharp + // 获取当前使用的时区信息 + public static string GetTimeZoneInfo() + + // 检查时区是否正确初始化 + public static bool IsTimeZoneCorrectlyInitialized() + ``` + +5. **设计优势**: + - **跨平台兼容**:支持Windows、Linux、macOS等不同操作系统 + - **健壮性**:多层回退机制确保在任何环境下都能正常工作 + - **调试友好**:提供时区信息查询方法便于问题排查 + - **向后兼容**:保持原有API不变,不影响现有代码 + - **性能优化**:静态初始化,避免重复计算 + +6. **修复的关键问题**: + - **时区ID不存在**:某些系统上"China Standard Time"时区ID不可用 + - **跨平台兼容性**:不同操作系统使用不同的时区ID格式 + - **初始化失败**:静态构造函数异常导致整个类无法使用 + - **调试困难**:缺少时区状态查询方法 + +**影响范围**: +- 时间戳工具类的跨平台兼容性 +- 系统启动时的稳定性 +- 时间转换功能的可靠性 +- 调试和问题排查能力 + +**后续优化**: +- **简化时区选择逻辑**:优先使用`Asia/Shanghai`时区(Linux标准格式) +- **提高性能**:减少不必要的时区ID尝试,直接使用最常用的时区 +- **代码简洁性**:简化回退逻辑,提高代码可读性 +- **移除Windows依赖**:完全移除`China Standard Time`时区ID,专注于Linux系统优化 + ## 2024年修改记录 ### 修复NetworkProtocolLogObserver中的StopChannelManager方法并支持重新创建