29 changed files with 3602 additions and 999 deletions
@ -0,0 +1,118 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Text.RegularExpressions; |
|||
using System.Threading.Tasks; |
|||
using CoreAgent.ProtocolClient.Models; |
|||
using Newtonsoft.Json; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Context |
|||
{ |
|||
/// <summary>
|
|||
/// 参数管理
|
|||
/// </summary>
|
|||
public class CellParameterManager |
|||
{ |
|||
public Dictionary<int, CellConfig> Parameters { get; set; } = new(); |
|||
public List<string> RanIds { get; set; } = new(); |
|||
|
|||
public void SetRanId(string ranId) |
|||
{ |
|||
RanIds.Add(ranId); |
|||
} |
|||
|
|||
public void AddCell(int id, CellConfig config) |
|||
{ |
|||
if (Parameters.ContainsKey(id)) |
|||
{ |
|||
Parameters[id] = config; |
|||
} |
|||
else |
|||
{ |
|||
Parameters.Add(id, config); |
|||
} |
|||
} |
|||
|
|||
public void SetHeaders(string[] headers, ProtocolBasicInfo basicInfo) |
|||
{ |
|||
basicInfo.Headers = headers; |
|||
var cells = new List<Dictionary<string, object>>(); |
|||
for (int i = 0; i < headers.Length; i++) |
|||
{ |
|||
var header = headers[i]; |
|||
Match? info; |
|||
info = Regex.Match(header, @"lte(\w+) version ([\d-]+)", RegexOptions.IgnoreCase); |
|||
if (info.Success) |
|||
{ |
|||
basicInfo.Model = info.Groups[1].Value.ToUpper(); |
|||
basicInfo.Version = info.Groups[2].Value; |
|||
} |
|||
else if (Regex.IsMatch(header, @"Licensed to")) |
|||
{ |
|||
basicInfo.License = header; |
|||
} |
|||
else if ((info = Regex.Match(header, @"Metadata:(.+)$")).Success) |
|||
{ |
|||
var metadata = JsonConvert.DeserializeObject<Dictionary<string, object>>(info.Groups[1].Value); |
|||
} |
|||
else if ((info = Regex.Match(header, @"(global_(ran_node|enb)_id)=([\d\.]+)")).Success) |
|||
{ |
|||
this.SetRanId(info.Groups[3].Value); |
|||
} |
|||
else if ((info = Regex.Match(header, @"Cell 0x(\d+): (.*)")).Success) |
|||
{ |
|||
var cell = new Dictionary<string, object> |
|||
{ |
|||
["cell_id"] = int.Parse(info.Groups[1].Value, System.Globalization.NumberStyles.HexNumber), |
|||
["sib1Decoded"] = true, |
|||
["sib2Decoded"] = true |
|||
}; |
|||
var list = info.Groups[2].Value.Split(' '); |
|||
foreach (var param in list) |
|||
{ |
|||
var parts = param.Split('='); |
|||
if (parts.Length == 2) |
|||
{ |
|||
cell[parts[0]] = HeaderCellParam(parts[0], parts[1]); |
|||
} |
|||
} |
|||
cells.Add(cell); |
|||
} |
|||
else if (cells.Count > 0) |
|||
{ |
|||
info = Regex.Match(header, @"([UD]L): (.*)"); |
|||
if (info.Success) |
|||
{ |
|||
var cell = cells[cells.Count - 1]; |
|||
var dir = info.Groups[1].Value; |
|||
var list = info.Groups[2].Value.Split(' '); |
|||
foreach (var param in list) |
|||
{ |
|||
var parts = param.Split('='); |
|||
if (parts.Length == 2) |
|||
{ |
|||
cell[parts[0]] = HeaderCellParam(parts[0], parts[1]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private object HeaderCellParam(string param, string value) |
|||
{ |
|||
switch (param) |
|||
{ |
|||
case "br_dl_sf_bitmap": |
|||
case "nb_dl_sf_bitmap": |
|||
case "label": |
|||
return value; |
|||
default: |
|||
if (int.TryParse(value, out var intValue)) |
|||
return intValue; |
|||
return value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Context |
|||
{ |
|||
/// <summary>
|
|||
/// 基础信息
|
|||
/// </summary>
|
|||
public class ProtocolBasicInfo |
|||
{ |
|||
public string? Name { get; set; } |
|||
public string? Version { get; set; } |
|||
public string? License { get; set; } |
|||
public string? Model { get; set; } |
|||
public string[]? Headers { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using CoreAgent.ProtocolClient.Context.UeStateManager; |
|||
using CoreAgent.ProtocolClient.Enums; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Context |
|||
{ |
|||
public class ProtocolClientContext |
|||
{ |
|||
// 常量和正则表达式已迁移至 ProtocolLogPatterns 静态类,便于统一管理和调用。
|
|||
|
|||
private readonly ILoggerFactory _loggerFactory; |
|||
|
|||
public ProtocolBasicInfo BasicInfo { get; set; } = new(); |
|||
public ProtocolFeatureFlags FeatureFlags { get; set; } = new(); |
|||
public ProtocolLogContext LogContext { get; set; } |
|||
public UeIdentifierManager UeIdentifier { get; set; } |
|||
public CellParameterManager CellParameterManager { get; set; } = new(); |
|||
public ClientState State { get; set; } = ClientState.Stop; |
|||
|
|||
public ProtocolClientContext(ILoggerFactory loggerFactory) |
|||
{ |
|||
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); |
|||
|
|||
// 使用ILoggerFactory为各个组件创建正确的Logger
|
|||
var ueIdentifierLogger = _loggerFactory.CreateLogger<UeIdentifierManager>(); |
|||
var logContextLogger = _loggerFactory.CreateLogger<ProtocolLogContext>(); |
|||
|
|||
// 先创建UeIdentifierManager
|
|||
UeIdentifier = new UeIdentifierManager(ueIdentifierLogger); |
|||
|
|||
// 然后创建ProtocolLogContext,传入this引用
|
|||
LogContext = new ProtocolLogContext(logContextLogger, this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Context |
|||
{ |
|||
/// <summary>
|
|||
/// 功能标志
|
|||
/// </summary>
|
|||
public class ProtocolFeatureFlags |
|||
{ |
|||
public bool HasCell { get; set; } |
|||
public bool HasPhy { get; set; } |
|||
public bool HasData { get; set; } |
|||
public bool HasRnti { get; set; } |
|||
public bool HasRb { get; set; } |
|||
public bool HasSignalRecord { get; set; } |
|||
public bool IsFirstGetLogs { get; set; } = true; |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using CoreAgent.ProtocolClient.Context.UeStateManager; |
|||
using CoreAgent.ProtocolClient.Models; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Context |
|||
{ |
|||
/// <summary>
|
|||
/// 日志相关
|
|||
/// </summary>
|
|||
public class ProtocolLogContext |
|||
{ |
|||
private static int _logIdCounter; |
|||
private readonly ILogger<ProtocolLogContext> _logger; |
|||
private readonly ProtocolClientContext _context; |
|||
|
|||
public List<SourceProtocolLog> Logs { get; set; } = new(); |
|||
public int LogCount => Logs.Count; |
|||
|
|||
public ProtocolLogContext(ILogger<ProtocolLogContext> logger, ProtocolClientContext context) |
|||
{ |
|||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); |
|||
_context = context ?? throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
public void ResetLogs() |
|||
{ |
|||
if (LogCount > 0) |
|||
{ |
|||
Logs.Clear(); |
|||
_context.FeatureFlags.HasCell = false; |
|||
_context.FeatureFlags.HasPhy = false; |
|||
_context.FeatureFlags.HasData = false; |
|||
_context.FeatureFlags.HasRnti = false; |
|||
_context.FeatureFlags.HasRb = false; |
|||
_context.FeatureFlags.HasSignalRecord = false; |
|||
} |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// 更新日志的时间戳并分配唯一ID。
|
|||
/// 检查并处理时间戳回绕,详细记录回绕前后各关键字段,包括:
|
|||
/// 1. 原始日志时间戳(log.Timestamp)
|
|||
/// 2. 当前偏移量(parser.TimestampOffset)
|
|||
/// 3. 计算得到的当前时间戳(timestamp)
|
|||
/// 4. 上次处理的时间戳(parser.LastTimestamp)
|
|||
/// 5. 回绕后新的时间戳(timestamp + 86400000)
|
|||
/// 6. 本次回绕的增量(86400000)
|
|||
/// 7. 日志分配的唯一ID(log.Id)
|
|||
/// </summary>
|
|||
public void UpdateLogTimestampAndAssignId(ref BuildProtocolLog log) |
|||
{ |
|||
// 检查时间戳回绕
|
|||
var parser = _context.UeIdentifier; |
|||
var timestamp = log.Timestamp + parser.TimestampOffset; |
|||
if (timestamp < parser.LastTimestamp - 100) |
|||
{ |
|||
_logger.LogInformation($"[Log回绕] 原始时间戳: {log.Timestamp}, 偏移量: {parser.TimestampOffset}, 回绕前: {timestamp}, parser.LastTimestamp(回绕前): {parser.LastTimestamp}, 回绕后: {timestamp + 86400000}, parser.LastTimestamp(回绕后): {parser.LastTimestamp}, 增量: 86400000, 日志ID(回绕前): {log.Id}"); |
|||
timestamp += 86400000; // 24小时
|
|||
parser.TimestampOffset += 86400000; |
|||
_logger.LogInformation($"[Log回绕后] 新时间戳: {timestamp}, parser.LastTimestamp(回绕后): {parser.LastTimestamp}, 新偏移量: {parser.TimestampOffset}, 日志ID(回绕后): {log.Id}"); |
|||
} |
|||
var lastTimestampBefore = parser.LastTimestamp; |
|||
parser.LastTimestamp = log.Timestamp = timestamp; |
|||
log.Id = GenerateLogId(); |
|||
_logger.LogDebug($"[Log分配ID] 日志最终时间戳: {log.Timestamp}, 分配ID: {log.Id}, 当前偏移量: {parser.TimestampOffset}, parser.LastTimestamp(分配前): {lastTimestampBefore}, parser.LastTimestamp(分配后): {parser.LastTimestamp}"); |
|||
} |
|||
|
|||
|
|||
private static int GenerateLogId() |
|||
{ |
|||
return Interlocked.Increment(ref _logIdCounter); |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Text.RegularExpressions; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Context |
|||
{ |
|||
/// <summary>
|
|||
/// 协议日志相关常量与正则表达式,便于全局统一管理和调用。
|
|||
/// </summary>
|
|||
public static class ProtocolLogPatterns |
|||
{ |
|||
#region 常量定义
|
|||
|
|||
/// <summary>
|
|||
/// HFN回绕阈值
|
|||
/// </summary>
|
|||
public const int HFN_WRAP_THRESHOLD = 512; |
|||
|
|||
/// <summary>
|
|||
/// 最大日志数量
|
|||
/// </summary>
|
|||
public const int LOGS_MAX = 2000000; |
|||
|
|||
/// <summary>
|
|||
/// BSR表 - 对应JavaScript中的_bsr_table
|
|||
/// </summary>
|
|||
public static readonly int[] BsrTable = { |
|||
0, 10, 12, 14, 17, 19, 22, 26, 31, 36, 42, 49, 57, 67, 78, 91, 107, 125, 146, 171, 200, 234, 274, 321, 376, 440, 515, 603, 706, 826, 967, 1132, 1326, 1552, 1817, 2127, 2490, 2915, 3413, 3995, 4677, 5476, 6411, 7505, 8787, 10287, 12043, 14099, 16507, 19325, 22624, 26487, 31009, 36304, 42502, 49759, 58255, 68201, 79846, 93479, 109439, 128125, 150000, 500000 |
|||
}; |
|||
|
|||
#endregion
|
|||
|
|||
#region 正则表达式
|
|||
|
|||
public static readonly Regex RegExpPhy = new(@"^([a-f0-9\-]+)\s+([a-f0-9\-]+)\s+([\d\.\-]+) (\w+): (.+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpInfo1 = new(@"^([\w\-]+): (.+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpInfo2 = new(@"^([\w]+) (.+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpIP = new(@"^(len=\d+)\s+(\S+)\s+(.*)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpIPsec = new(@"^len=(\d+)\s+(.*)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpSDULen = new(@"SDU_len=(\d+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpSIP = new(@"^([:\.\[\]\da-f]+)\s+(\S+) (.+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpMediaReq = new(@"^(\S+) (.+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpSignalRecord = new(@"Link:\s([\w\d]+)@(\d+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpCellID = new(@"^([a-f0-9\-]+) (.+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpRRC_UE_ID = new(@"Changing UE_ID to 0x(\d+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpRRC_TMSI = new(@"(5G|m)-TMSI '([\dA-F]+)'H", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpRRC_NEW_ID = new(@"newUE-Identity (['\dA-FH]+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpRRC_CRNTI = new(@"c-RNTI '([\dA-F]+)'H", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpNAS_TMSI = new(@"[Mm]-TMSI = 0x([\da-f]+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpNAS_5GTMSI = new(@"5G-TMSI = 0x([\da-f]+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpRRC_BC = new(@"(EUTRA|MRDC|NR|NRDC) band combinations", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpPDCCH = new(@"^\s*(.+)=(\d+)$", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpS1NGAP = new(@"^([\da-f\-]+)\s+([\da-f\-]+) (([^\s]+) .+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegExpHexDump = new(@"^[\da-f]+:(\s+[\da-f]{2}){1,16}\s+.{1,16}$", RegexOptions.Compiled); |
|||
public static readonly Regex RegMccPattern = new Regex(@"MCC\s*=\s*(\d+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegMncPattern = new Regex(@"MNC\s*=\s*(\d+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegMSINPattern = new Regex(@"MSIN\s*=\s*(\d+)", RegexOptions.Compiled); |
|||
public static readonly Regex RegIMSIPattern = new Regex(@"IMSI = \s*(\d+)", RegexOptions.Compiled); |
|||
|
|||
public static Match? FindData(this Regex regex, List<string> data) |
|||
{ |
|||
return data?.FirstOrDefault(line => regex.IsMatch(line)) is string matchedLine |
|||
? regex.Match(matchedLine) |
|||
: null; |
|||
} |
|||
#endregion
|
|||
} |
|||
} |
|||
@ -0,0 +1,154 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Text.RegularExpressions; |
|||
using System.Threading.Tasks; |
|||
using CoreAgent.ProtocolClient.Enums; |
|||
using CoreAgent.ProtocolClient.Models; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Context.UeStateManager |
|||
{ |
|||
/// <summary>
|
|||
/// UeIdentifierManager 协议解析部分
|
|||
/// </summary>
|
|||
public partial class UeIdentifierManager |
|||
{ |
|||
#region 协议解析方法
|
|||
|
|||
/// <summary>
|
|||
/// 解析提示信息
|
|||
/// </summary>
|
|||
/// <param name="log">协议日志</param>
|
|||
/// <param name="config">解析配置</param>
|
|||
public void ParseHints(BuildProtocolLog log, Dictionary<string, object> config) |
|||
{ |
|||
if (log?.Info != (int)LogChannelId.ID_HINTS) |
|||
{ |
|||
_logger?.LogDebug("跳过非提示信息日志: Info={Info}", log?.Info); |
|||
return; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
var hints = ParseArguments(log.Message, config); |
|||
_logger?.LogDebug("解析提示信息成功,共解析 {Count} 个参数", hints.Count); |
|||
// TODO: 处理解析后的提示信息
|
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger?.LogError(ex, "解析提示信息失败: {Message}", log.Message); |
|||
throw new ParsingException("Hints", log?.Message ?? "", $"解析提示信息失败: {ex.Message}", ex); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 解析参数字符串为字典
|
|||
/// 性能优化:使用编译后的正则表达式
|
|||
/// </summary>
|
|||
/// <param name="arguments">参数字符串,格式如 "a=1 b=2 c=hello"</param>
|
|||
/// <param name="config">
|
|||
/// 配置字典,key为参数名,value为类型(Type)或处理函数(Func<string, object>)
|
|||
/// 例如:{ "a", typeof(int) }, { "b", (Func<string, object>)(s => s.ToUpper()) }
|
|||
/// </param>
|
|||
/// <returns>解析后的参数字典</returns>
|
|||
private Dictionary<string, object> ParseArguments(string arguments, Dictionary<string, object> config) |
|||
{ |
|||
if (string.IsNullOrEmpty(arguments)) |
|||
{ |
|||
return new Dictionary<string, object>(); |
|||
} |
|||
|
|||
var result = new Dictionary<string, object>(); |
|||
// 使用编译后的正则表达式提高性能
|
|||
var whitespaceRegex = new Regex(@"\s+", RegexOptions.Compiled); |
|||
|
|||
try |
|||
{ |
|||
foreach (var argument in whitespaceRegex.Split(arguments)) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(argument)) |
|||
continue; |
|||
|
|||
var keyValuePair = argument.Split('=', 2); |
|||
if (keyValuePair.Length != 2) |
|||
{ |
|||
_logger?.LogWarning("跳过格式无效的参数: {Argument}", argument); |
|||
continue; |
|||
} |
|||
|
|||
var key = keyValuePair[0]; |
|||
var value = keyValuePair[1]; |
|||
|
|||
result[key] = ParseValue(value, key, config); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger?.LogError(ex, "解析参数字符串失败: {Arguments}", arguments); |
|||
throw new ParsingException("Arguments", arguments, $"解析参数字符串失败: {ex.Message}", ex); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 根据配置解析参数值
|
|||
/// </summary>
|
|||
/// <param name="value">原始值字符串</param>
|
|||
/// <param name="key">参数键</param>
|
|||
/// <param name="config">解析配置</param>
|
|||
/// <returns>解析后的值</returns>
|
|||
private object ParseValue(string value, string key, Dictionary<string, object> config) |
|||
{ |
|||
if (!config.TryGetValue(key, out var typeOrFunc)) |
|||
return value; |
|||
|
|||
try |
|||
{ |
|||
return typeOrFunc switch |
|||
{ |
|||
Type type when type == typeof(int) => int.TryParse(value, out var intValue) ? intValue : 0, |
|||
Type type when type == typeof(double) => double.TryParse(value, out var doubleValue) ? doubleValue : 0.0, |
|||
Func<string, object> func => func(value), |
|||
_ => value |
|||
}; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger?.LogError(ex, "解析参数值失败: Key={Key}, Value={Value}", key, value); |
|||
return value; // 返回原始值作为回退
|
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 批量解析参数
|
|||
/// 性能优化:批量处理减少重复计算
|
|||
/// </summary>
|
|||
/// <param name="argumentsList">参数字符串列表</param>
|
|||
/// <param name="config">解析配置</param>
|
|||
/// <returns>解析结果列表</returns>
|
|||
public List<Dictionary<string, object>> ParseArgumentsBatch( |
|||
IEnumerable<string> argumentsList, |
|||
Dictionary<string, object> config) |
|||
{ |
|||
if (argumentsList == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(argumentsList)); |
|||
} |
|||
|
|||
var results = new List<Dictionary<string, object>>(); |
|||
foreach (var arguments in argumentsList) |
|||
{ |
|||
results.Add(ParseArguments(arguments, config)); |
|||
} |
|||
|
|||
_logger?.LogDebug("批量解析参数完成,共处理 {Count} 个参数字符串", results.Count); |
|||
return results; |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Context.UeStateManager |
|||
{ |
|||
/// <summary>
|
|||
/// UeIdentifierManager 字符串ID管理部分
|
|||
/// </summary>
|
|||
public partial class UeIdentifierManager |
|||
{ |
|||
#region 字符串ID管理方法
|
|||
|
|||
/// <summary>
|
|||
/// 将字符串转换为ID,如果不存在则创建新的ID
|
|||
/// 性能优化:使用缓存减少重复计算
|
|||
/// </summary>
|
|||
/// <param name="str">要转换的字符串</param>
|
|||
/// <returns>对应的ID</returns>
|
|||
/// <exception cref="ArgumentNullException">字符串为空时抛出</exception>
|
|||
public int StringToId(string str) |
|||
{ |
|||
if (string.IsNullOrEmpty(str)) |
|||
{ |
|||
_logger?.LogWarning("尝试转换空字符串为ID"); |
|||
return 0; |
|||
} |
|||
|
|||
lock (_lockObject) |
|||
{ |
|||
IncrementCacheStat("StringToId"); |
|||
|
|||
if (_stringIdCache.TryGetValue(str, out int existingId)) |
|||
{ |
|||
IncrementCacheStat("CacheHits"); |
|||
return existingId; |
|||
} |
|||
|
|||
IncrementCacheStat("CacheMisses"); |
|||
int newId = _stringList.Count + 1; |
|||
_stringIdCache[str] = newId; |
|||
_stringList.Add(str); |
|||
|
|||
_logger?.LogDebug("创建新的字符串ID映射: '{String}' -> {Id}", str, newId); |
|||
return newId; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 根据ID获取对应的字符串
|
|||
/// 性能优化:使用缓存减少重复计算
|
|||
/// </summary>
|
|||
/// <param name="id">字符串ID</param>
|
|||
/// <returns>对应的字符串,如果ID无效则返回空字符串</returns>
|
|||
public string IdToString(int id) |
|||
{ |
|||
if (id <= 0) |
|||
{ |
|||
_logger?.LogWarning("尝试获取无效ID的字符串: {Id}", id); |
|||
return string.Empty; |
|||
} |
|||
|
|||
lock (_lockObject) |
|||
{ |
|||
IncrementCacheStat("IdToString"); |
|||
|
|||
if (id <= _stringList.Count) |
|||
{ |
|||
IncrementCacheStat("CacheHits"); |
|||
return _stringList[id - 1]; |
|||
} |
|||
|
|||
IncrementCacheStat("CacheMisses"); |
|||
_logger?.LogWarning("ID超出范围: {Id}, 最大ID: {MaxId}", id, _stringList.Count); |
|||
return string.Empty; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 批量转换字符串为ID
|
|||
/// 性能优化:批量处理减少锁开销
|
|||
/// </summary>
|
|||
/// <param name="strings">字符串列表</param>
|
|||
/// <returns>对应的ID列表</returns>
|
|||
public List<int> StringToIdBatch(IEnumerable<string> strings) |
|||
{ |
|||
if (strings == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(strings)); |
|||
} |
|||
|
|||
var results = new List<int>(); |
|||
lock (_lockObject) |
|||
{ |
|||
foreach (var str in strings) |
|||
{ |
|||
results.Add(StringToId(str)); |
|||
} |
|||
} |
|||
|
|||
_logger?.LogDebug("批量转换字符串为ID,共处理 {Count} 个字符串", results.Count); |
|||
return results; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取字符串缓存信息
|
|||
/// </summary>
|
|||
/// <returns>缓存信息</returns>
|
|||
public (int CacheSize, int ListSize) GetStringCacheInfo() |
|||
{ |
|||
lock (_lockObject) |
|||
{ |
|||
return (_stringIdCache.Count, _stringList.Count); |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region 私有辅助方法
|
|||
|
|||
/// <summary>
|
|||
/// 增加缓存统计计数
|
|||
/// </summary>
|
|||
/// <param name="statName">统计名称</param>
|
|||
private void IncrementCacheStat(string statName) |
|||
{ |
|||
if (_cacheStats.ContainsKey(statName)) |
|||
{ |
|||
_cacheStats[statName]++; |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
|||
@ -0,0 +1,261 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using CoreAgent.ProtocolClient.Models; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Context.UeStateManager |
|||
{ |
|||
/// <summary>
|
|||
/// UeIdentifierManager TMSI匹配部分
|
|||
/// </summary>
|
|||
public partial class UeIdentifierManager |
|||
{ |
|||
#region TMSI匹配方法
|
|||
|
|||
/// <summary>
|
|||
/// 根据TMSI匹配生成请求UE ID和接收UE ID的对应关系
|
|||
/// 使用TmsiMatchProcessor处理匹配逻辑
|
|||
/// </summary>
|
|||
/// <returns>TMSI匹配结果列表</returns>
|
|||
public List<TmsiMatchResult> GenerateTmsiMatches() |
|||
{ |
|||
try |
|||
{ |
|||
var processor = new TmsiMatchProcessor(TmsiToUeId, RequestTmsiToUeId, ImsiToUeId); |
|||
var results = processor.GenerateTmsiMatches(); |
|||
_logger?.LogDebug("生成TMSI匹配结果,共 {Count} 个匹配", results.Count); |
|||
return results; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger?.LogError(ex, "生成TMSI匹配失败"); |
|||
throw new GeneralUeIdentifierManagerException("GenerateTmsiMatches", "生成TMSI匹配失败", ex); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 根据UE ID获取对应的IMSI
|
|||
/// 优先查找当前UE ID,如果未找到则查找整个UE链中的IMSI
|
|||
/// </summary>
|
|||
/// <param name="ueId">UE ID</param>
|
|||
/// <returns>对应的IMSI,如果未找到则返回空字符串</returns>
|
|||
private string GetImsiForUeId(int ueId) |
|||
{ |
|||
if (ueId <= 0) |
|||
{ |
|||
_logger?.LogWarning("尝试获取无效UE ID的IMSI: {UeId}", ueId); |
|||
return string.Empty; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
// 1. 首先查找当前UE ID对应的IMSI
|
|||
var currentImsiMapping = ImsiToUeId.FirstOrDefault(kvp => kvp.Value.Contains(ueId)); |
|||
if (!string.IsNullOrEmpty(currentImsiMapping.Key)) |
|||
{ |
|||
return currentImsiMapping.Key; |
|||
} |
|||
|
|||
// 2. 如果当前UE ID没有IMSI,查找整个UE链中的IMSI
|
|||
var anyImsiMapping = ImsiToUeId.FirstOrDefault(kvp => !string.IsNullOrEmpty(kvp.Key)); |
|||
return anyImsiMapping.Key ?? string.Empty; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger?.LogError(ex, "获取UE {UeId} 的IMSI失败", ueId); |
|||
return string.Empty; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取UE链的统计信息
|
|||
/// </summary>
|
|||
/// <returns>UE链统计信息</returns>
|
|||
public UeChainStats GetUeChainStats() |
|||
{ |
|||
try |
|||
{ |
|||
var processor = new TmsiMatchProcessor(TmsiToUeId, RequestTmsiToUeId, ImsiToUeId); |
|||
var stats = processor.GetUeChainStats(); |
|||
_logger?.LogDebug("获取UE链统计信息成功"); |
|||
return stats; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger?.LogError(ex, "获取UE链统计信息失败"); |
|||
throw new GeneralUeIdentifierManagerException("GetUeChainStats", "获取UE链统计信息失败", ex); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取所有UE链的详细信息
|
|||
/// </summary>
|
|||
/// <returns>UE链详细信息列表</returns>
|
|||
public List<UeChainInfo> GetUeChainDetails() |
|||
{ |
|||
try |
|||
{ |
|||
var processor = new TmsiMatchProcessor(TmsiToUeId, RequestTmsiToUeId, ImsiToUeId); |
|||
var details = processor.GetUeChainDetails(); |
|||
_logger?.LogDebug("获取UE链详细信息成功,共 {Count} 个链", details.Count); |
|||
return details; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger?.LogError(ex, "获取UE链详细信息失败"); |
|||
throw new GeneralUeIdentifierManagerException("GetUeChainDetails", "获取UE链详细信息失败", ex); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 将 SrTmsiToUeId 平铺成 SrTmsiMapping 列表,并与 GenerateTmsiMatches 结果匹配更新 IMSI、Root UE ID 和 PLMN
|
|||
/// 性能优化:使用LINQ提高查找效率
|
|||
/// </summary>
|
|||
/// <returns>平铺后的 SrTmsiMapping 列表,包含匹配的 IMSI、Root UE ID 和 PLMN 信息</returns>
|
|||
public List<SrTmsiMapping> GenerateSrTmsiMappings(List<TmsiMatchResult> tmsiMatches) |
|||
{ |
|||
if (tmsiMatches == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(tmsiMatches)); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
var srTmsiMappings = new List<SrTmsiMapping>(); |
|||
|
|||
// 1. 平铺 SrTmsiToUeId 字典数据
|
|||
foreach (var kvp in SrTmsiToUeId) |
|||
{ |
|||
uint tmsi = kvp.Key; |
|||
var ueIds = kvp.Value; |
|||
|
|||
foreach (int ueId in ueIds) |
|||
{ |
|||
var mapping = new SrTmsiMapping(string.Empty, tmsi, ueId); |
|||
srTmsiMappings.Add(mapping); |
|||
} |
|||
} |
|||
|
|||
// 2. 获取 UE 链信息以获取最外层的 Root UE ID
|
|||
var ueChainDetails = GetUeChainDetails(); |
|||
|
|||
// 3. 根据 TMSI 匹配结果更新 SrTmsiMapping 的 IMSI 和 Root UE ID
|
|||
foreach (var srMapping in srTmsiMappings) |
|||
{ |
|||
UpdateSrTmsiMapping(srMapping, tmsiMatches, ueChainDetails); |
|||
} |
|||
|
|||
// 4. 根据 RootUeId 从 PlmnToUeId 映射表更新 PLMN 信息
|
|||
foreach (var srMapping in srTmsiMappings) |
|||
{ |
|||
UpdateSrTmsiMappingPlmn(srMapping, ueChainDetails); |
|||
} |
|||
|
|||
_logger?.LogDebug("生成Service Request TMSI映射成功,共 {Count} 个映射", srTmsiMappings.Count); |
|||
return srTmsiMappings; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger?.LogError(ex, "生成Service Request TMSI映射失败"); |
|||
throw new GeneralUeIdentifierManagerException("GenerateSrTmsiMappings", "生成Service Request TMSI映射失败", ex); |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region 私有辅助方法
|
|||
|
|||
/// <summary>
|
|||
/// 更新 SrTmsiMapping 的 IMSI 和 Root UE ID
|
|||
/// </summary>
|
|||
/// <param name="srMapping">Service Request TMSI映射</param>
|
|||
/// <param name="tmsiMatches">TMSI匹配结果</param>
|
|||
/// <param name="ueChainDetails">UE链详细信息</param>
|
|||
private void UpdateSrTmsiMapping(SrTmsiMapping srMapping, List<TmsiMatchResult> tmsiMatches, List<UeChainInfo> ueChainDetails) |
|||
{ |
|||
// 查找匹配的 TMSI 结果
|
|||
var matchingResult = tmsiMatches.FirstOrDefault(match => match.Tmsi == srMapping.Tmsi); |
|||
|
|||
if (matchingResult != null && !string.IsNullOrEmpty(matchingResult.Imsi)) |
|||
{ |
|||
// 更新 IMSI
|
|||
srMapping.Imsi = matchingResult.Imsi; |
|||
|
|||
// 查找对应的最外层 Root UE ID
|
|||
var chainInfo = ueChainDetails.FirstOrDefault(chain => |
|||
chain.UeIds.Contains(srMapping.UeId)); |
|||
|
|||
if (chainInfo != null) |
|||
{ |
|||
srMapping.RootUeId = chainInfo.RootUeId; |
|||
} |
|||
else |
|||
{ |
|||
// 如果当前 UE ID 不在任何链中,尝试从匹配结果中查找
|
|||
chainInfo = ueChainDetails.FirstOrDefault(chain => |
|||
chain.UeIds.Contains(matchingResult.RequestUeId) || |
|||
chain.UeIds.Contains(matchingResult.ReceiveUeId)); |
|||
|
|||
if (chainInfo != null) |
|||
{ |
|||
srMapping.RootUeId = chainInfo.RootUeId; |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// 如果 TMSI 匹配中没有找到,尝试从 ImsiToUeId 映射中查找
|
|||
string imsi = GetImsiForUeId(srMapping.UeId); |
|||
if (!string.IsNullOrEmpty(imsi)) |
|||
{ |
|||
srMapping.Imsi = imsi; |
|||
|
|||
// 查找对应的最外层 Root UE ID
|
|||
var chainInfo = ueChainDetails.FirstOrDefault(chain => |
|||
chain.UeIds.Contains(srMapping.UeId)); |
|||
|
|||
if (chainInfo != null) |
|||
{ |
|||
srMapping.RootUeId = chainInfo.RootUeId; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 更新 SrTmsiMapping 的 PLMN 信息
|
|||
/// </summary>
|
|||
/// <param name="srMapping">Service Request TMSI映射</param>
|
|||
private void UpdateSrTmsiMappingPlmn(SrTmsiMapping srMapping, List<UeChainInfo> ueChainDetails) |
|||
{ |
|||
if (srMapping.RootUeId > 0) |
|||
{ |
|||
// 查找包含 RootUeId 的 PLMN 映射
|
|||
var plmnMapping = PlmnToUeId.FirstOrDefault(kvp => kvp.Value.Contains(srMapping.RootUeId)); |
|||
if (!string.IsNullOrEmpty(plmnMapping.Key)) |
|||
{ |
|||
srMapping.Plmn = plmnMapping.Key; |
|||
} |
|||
} |
|||
else if (srMapping.UeId > 0) |
|||
{ |
|||
var Filter = TmsiToUeId.FirstOrDefault(s => s.Key == srMapping.Tmsi); |
|||
// 如果 RootUeId 为空,尝试使用 UeId 查找 PLMN
|
|||
var chainInfo = ueChainDetails.FirstOrDefault(chain => |
|||
chain.UeIds.Contains(Filter.Value)); |
|||
|
|||
var plmnMapping = PlmnToUeId.FirstOrDefault(kvp => kvp.Value.Contains(Filter.Value)); |
|||
if (!string.IsNullOrEmpty(plmnMapping.Key)) |
|||
{ |
|||
srMapping.Plmn = plmnMapping.Key; |
|||
srMapping.RootUeId = chainInfo.RootUeId; |
|||
} |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
|||
@ -0,0 +1,126 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Context.UeStateManager |
|||
{ |
|||
/// <summary>
|
|||
/// UeIdentifierManager 相关异常基类
|
|||
/// </summary>
|
|||
public abstract class UeIdentifierManagerException : Exception |
|||
{ |
|||
protected UeIdentifierManagerException(string message) : base(message) { } |
|||
protected UeIdentifierManagerException(string message, Exception innerException) : base(message, innerException) { } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 标识符格式异常
|
|||
/// </summary>
|
|||
public class IdentifierFormatException : UeIdentifierManagerException |
|||
{ |
|||
public string Identifier { get; } |
|||
public string ExpectedFormat { get; } |
|||
|
|||
public IdentifierFormatException(string identifier, string expectedFormat, string message = null) |
|||
: base(message ?? $"标识符 '{identifier}' 格式无效,期望格式: {expectedFormat}") |
|||
{ |
|||
Identifier = identifier; |
|||
ExpectedFormat = expectedFormat; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// UE信息异常
|
|||
/// </summary>
|
|||
public class UeInfoException : UeIdentifierManagerException |
|||
{ |
|||
public int UeId { get; } |
|||
|
|||
public UeInfoException(int ueId, string message) |
|||
: base(message) |
|||
{ |
|||
UeId = ueId; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 映射关系异常
|
|||
/// </summary>
|
|||
public class MappingException : UeIdentifierManagerException |
|||
{ |
|||
public string MappingType { get; } |
|||
public object Key { get; } |
|||
|
|||
public MappingException(string mappingType, object key, string message) |
|||
: base(message) |
|||
{ |
|||
MappingType = mappingType; |
|||
Key = key; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 缓存异常
|
|||
/// </summary>
|
|||
public class CacheException : UeIdentifierManagerException |
|||
{ |
|||
public string CacheType { get; } |
|||
|
|||
public CacheException(string cacheType, string message) |
|||
: base(message) |
|||
{ |
|||
CacheType = cacheType; |
|||
} |
|||
|
|||
public CacheException(string cacheType, string message, Exception innerException) |
|||
: base(message, innerException) |
|||
{ |
|||
CacheType = cacheType; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 解析异常
|
|||
/// </summary>
|
|||
public class ParsingException : UeIdentifierManagerException |
|||
{ |
|||
public string ParsingType { get; } |
|||
public string Input { get; } |
|||
|
|||
public ParsingException(string parsingType, string input, string message) |
|||
: base(message) |
|||
{ |
|||
ParsingType = parsingType; |
|||
Input = input; |
|||
} |
|||
|
|||
public ParsingException(string parsingType, string input, string message, Exception innerException) |
|||
: base(message, innerException) |
|||
{ |
|||
ParsingType = parsingType; |
|||
Input = input; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 通用UE标识符管理器异常
|
|||
/// </summary>
|
|||
public class GeneralUeIdentifierManagerException : UeIdentifierManagerException |
|||
{ |
|||
public string Operation { get; } |
|||
|
|||
public GeneralUeIdentifierManagerException(string operation, string message) |
|||
: base(message) |
|||
{ |
|||
Operation = operation; |
|||
} |
|||
|
|||
public GeneralUeIdentifierManagerException(string operation, string message, Exception innerException) |
|||
: base(message, innerException) |
|||
{ |
|||
Operation = operation; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,205 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using CoreAgent.ProtocolClient.Enums; |
|||
using CoreAgent.ProtocolClient.Models; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Context.UeStateManager |
|||
{ |
|||
/// <summary>
|
|||
/// UE标识符管理类
|
|||
/// 负责管理UE信息、标识符映射、时间戳等状态
|
|||
/// 优化版本:增强异常处理、性能优化、代码拆分
|
|||
/// </summary>
|
|||
public partial class UeIdentifierManager |
|||
{ |
|||
#region 公共属性 - 映射表
|
|||
|
|||
/// <summary>
|
|||
/// TMSI到UE ID的映射表
|
|||
/// </summary>
|
|||
public Dictionary<uint, int> TmsiToUeId { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// 请求TMSI到UE ID的映射表
|
|||
/// </summary>
|
|||
public Dictionary<uint, int> RequestTmsiToUeId { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// IMSI到UE ID的映射表
|
|||
/// </summary>
|
|||
public Dictionary<string, List<int>> ImsiToUeId { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// PLMN到UE ID的映射表
|
|||
/// </summary>
|
|||
public Dictionary<string, List<int>> PlmnToUeId { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// Service Request TMSI到UE ID的映射表
|
|||
/// </summary>
|
|||
public Dictionary<uint, List<int>> SrTmsiToUeId { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// RNTI到UE ID的映射表
|
|||
/// </summary>
|
|||
public Dictionary<int, int> RntiToUeId { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// UE信息列表,以UE ID为键
|
|||
/// </summary>
|
|||
public Dictionary<int, UEInfo> UeList { get; set; } = new(); |
|||
|
|||
#endregion
|
|||
|
|||
#region 公共属性 - 时间戳和小区信息
|
|||
|
|||
/// <summary>
|
|||
/// 最后处理的时间戳
|
|||
/// </summary>
|
|||
public long LastTimestamp { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 时间戳偏移量
|
|||
/// </summary>
|
|||
public long TimestampOffset { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 最后处理的小区ID
|
|||
/// </summary>
|
|||
public int? LastCell { get; set; } |
|||
|
|||
#endregion
|
|||
|
|||
#region 私有字段 - 字符串缓存和性能优化
|
|||
|
|||
/// <summary>
|
|||
/// 字符串到ID的映射缓存
|
|||
/// </summary>
|
|||
private readonly Dictionary<string, int> _stringIdCache = new(); |
|||
|
|||
/// <summary>
|
|||
/// ID到字符串的列表缓存
|
|||
/// </summary>
|
|||
private readonly List<string> _stringList = new(); |
|||
|
|||
/// <summary>
|
|||
/// 日志记录器
|
|||
/// </summary>
|
|||
private readonly ILogger<UeIdentifierManager> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 缓存统计信息
|
|||
/// </summary>
|
|||
private readonly Dictionary<string, int> _cacheStats = new(); |
|||
|
|||
/// <summary>
|
|||
/// 线程锁对象
|
|||
/// </summary>
|
|||
private readonly object _lockObject = new object(); |
|||
|
|||
#endregion
|
|||
|
|||
#region 构造函数
|
|||
|
|||
/// <summary>
|
|||
/// 初始化UE标识符管理器
|
|||
/// </summary>
|
|||
/// <param name="logger">日志记录器</param>
|
|||
public UeIdentifierManager(ILogger<UeIdentifierManager> logger) |
|||
{ |
|||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); |
|||
InitializeProtocolTypeStrings(); |
|||
InitializeCacheStats(); |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region 私有初始化方法
|
|||
|
|||
/// <summary>
|
|||
/// 初始化协议类型字符串缓存
|
|||
/// </summary>
|
|||
private void InitializeProtocolTypeStrings() |
|||
{ |
|||
try |
|||
{ |
|||
string[] protocolTypeNames = Enum.GetNames(typeof(LogChannelId)); |
|||
foreach (var protocolTypeName in protocolTypeNames) |
|||
{ |
|||
StringToId(protocolTypeName); |
|||
} |
|||
_logger.LogDebug("协议类型字符串缓存初始化完成,共 {Count} 个类型", protocolTypeNames.Length); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "初始化协议类型字符串缓存失败"); |
|||
throw new CacheException("ProtocolTypeStrings", "初始化协议类型字符串缓存失败", ex); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 初始化缓存统计信息
|
|||
/// </summary>
|
|||
private void InitializeCacheStats() |
|||
{ |
|||
_cacheStats["StringToId"] = 0; |
|||
_cacheStats["IdToString"] = 0; |
|||
_cacheStats["CacheHits"] = 0; |
|||
_cacheStats["CacheMisses"] = 0; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region 公共方法 - 统计和监控
|
|||
|
|||
/// <summary>
|
|||
/// 获取缓存统计信息
|
|||
/// </summary>
|
|||
/// <returns>缓存统计信息字典</returns>
|
|||
public Dictionary<string, int> GetCacheStats() |
|||
{ |
|||
lock (_lockObject) |
|||
{ |
|||
return new Dictionary<string, int>(_cacheStats); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取映射表统计信息
|
|||
/// </summary>
|
|||
/// <returns>映射表统计信息</returns>
|
|||
public Dictionary<string, int> GetMappingStats() |
|||
{ |
|||
return new Dictionary<string, int> |
|||
{ |
|||
["TmsiToUeId"] = TmsiToUeId.Count, |
|||
["RequestTmsiToUeId"] = RequestTmsiToUeId.Count, |
|||
["ImsiToUeId"] = ImsiToUeId.Count, |
|||
["PlmnToUeId"] = PlmnToUeId.Count, |
|||
["SrTmsiToUeId"] = SrTmsiToUeId.Count, |
|||
["RntiToUeId"] = RntiToUeId.Count, |
|||
["UeList"] = UeList.Count |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 清理缓存
|
|||
/// </summary>
|
|||
public void ClearCache() |
|||
{ |
|||
lock (_lockObject) |
|||
{ |
|||
_stringIdCache.Clear(); |
|||
_stringList.Clear(); |
|||
InitializeCacheStats(); |
|||
_logger.LogInformation("字符串缓存已清理"); |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
|||
@ -0,0 +1,271 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Text.RegularExpressions; |
|||
using System.Threading.Tasks; |
|||
using CoreAgent.ProtocolClient.Enums; |
|||
using CoreAgent.ProtocolClient.Models; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Context.UeStateManager |
|||
{ |
|||
/// <summary>
|
|||
/// UeIdentifierManager UE标识符映射部分
|
|||
/// </summary>
|
|||
public partial class UeIdentifierManager |
|||
{ |
|||
#region UE标识符映射方法
|
|||
|
|||
/// <summary>
|
|||
/// 设置IMSI与UE ID的映射关系
|
|||
/// 性能优化:使用HashSet提高查找效率
|
|||
/// </summary>
|
|||
/// <param name="ueId">UE ID</param>
|
|||
/// <param name="imsi">IMSI标识符</param>
|
|||
/// <exception cref="UeInfoException">UE ID无效时抛出</exception>
|
|||
public void SetImsi(int ueId, string imsi) |
|||
{ |
|||
if (string.IsNullOrEmpty(imsi)) |
|||
{ |
|||
_logger?.LogWarning("尝试设置空的IMSI"); |
|||
return; |
|||
} |
|||
|
|||
if (ueId <= 0) |
|||
{ |
|||
throw new UeInfoException(ueId, "UE ID必须大于0"); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
lock (_lockObject) |
|||
{ |
|||
if (!ImsiToUeId.TryGetValue(imsi, out var existingUeIds)) |
|||
{ |
|||
ImsiToUeId[imsi] = new List<int> { ueId }; |
|||
_logger?.LogDebug("创建新的IMSI映射: {Imsi} -> UE {UeId}", imsi, ueId); |
|||
} |
|||
else if (!existingUeIds.Contains(ueId)) |
|||
{ |
|||
existingUeIds.Add(ueId); |
|||
_logger?.LogDebug("添加IMSI映射: {Imsi} -> UE {UeId}", imsi, ueId); |
|||
} |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger?.LogError(ex, "设置IMSI映射失败: UE {UeId}, IMSI {Imsi}", ueId, imsi); |
|||
throw new MappingException("IMSI", imsi, $"设置IMSI映射失败: {ex.Message}"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 设置TMSI与UE ID的映射关系
|
|||
/// 性能优化:使用缓存减少重复转换
|
|||
/// </summary>
|
|||
/// <param name="ueId">UE ID</param>
|
|||
/// <param name="tmsi">TMSI标识符(十六进制字符串)</param>
|
|||
/// <exception cref="IdentifierFormatException">TMSI格式无效时抛出</exception>
|
|||
public void SetTmsi(int ueId, string tmsi) |
|||
{ |
|||
if (string.IsNullOrEmpty(tmsi)) |
|||
{ |
|||
_logger?.LogWarning("尝试设置空的TMSI"); |
|||
return; |
|||
} |
|||
|
|||
if (ueId <= 0) |
|||
{ |
|||
throw new UeInfoException(ueId, "UE ID必须大于0"); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
uint tmsiKey = Convert.ToUInt32(tmsi, 16); |
|||
|
|||
lock (_lockObject) |
|||
{ |
|||
if (TmsiToUeId.TryGetValue(tmsiKey, out int existingUeId) && existingUeId > 0) |
|||
{ |
|||
// 同步IMSI信息
|
|||
SyncImsiFromExistingUe(existingUeId, ueId); |
|||
// 合并UE信息
|
|||
MergeUeInfo(existingUeId, ueId); |
|||
_logger?.LogDebug("更新TMSI映射: {Tmsi} -> UE {UeId} (原UE {ExistingUeId})", tmsi, ueId, existingUeId); |
|||
} |
|||
else |
|||
{ |
|||
TmsiToUeId[tmsiKey] = ueId; |
|||
_logger?.LogDebug("创建新的TMSI映射: {Tmsi} -> UE {UeId}", tmsi, ueId); |
|||
} |
|||
} |
|||
} |
|||
catch (FormatException ex) |
|||
{ |
|||
var errorMessage = $"TMSI格式无效: '{tmsi}',期望十六进制格式"; |
|||
_logger?.LogError(ex, errorMessage); |
|||
throw new IdentifierFormatException(tmsi, "十六进制格式", errorMessage); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger?.LogError(ex, "设置TMSI映射失败: UE {UeId}, TMSI {Tmsi}", ueId, tmsi); |
|||
throw new MappingException("TMSI", tmsi, $"设置TMSI映射失败: {ex.Message}"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 设置请求TMSI与UE ID的映射关系
|
|||
/// </summary>
|
|||
/// <param name="ueId">UE ID</param>
|
|||
/// <param name="tmsi">请求TMSI标识符(十六进制字符串)</param>
|
|||
public void SetRequestTmsi(int ueId, string tmsi) |
|||
{ |
|||
if (string.IsNullOrEmpty(tmsi)) |
|||
{ |
|||
_logger?.LogWarning("尝试设置空的请求TMSI"); |
|||
return; |
|||
} |
|||
|
|||
if (ueId <= 0) |
|||
{ |
|||
throw new UeInfoException(ueId, "UE ID必须大于0"); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
uint tmsiKey = Convert.ToUInt32(tmsi, 16); |
|||
|
|||
lock (_lockObject) |
|||
{ |
|||
RequestTmsiToUeId[tmsiKey] = ueId; |
|||
_logger?.LogDebug("设置请求TMSI映射: {Tmsi} -> UE {UeId}", tmsi, ueId); |
|||
} |
|||
} |
|||
catch (FormatException ex) |
|||
{ |
|||
var errorMessage = $"请求TMSI格式无效: '{tmsi}',期望十六进制格式"; |
|||
_logger?.LogError(ex, errorMessage); |
|||
throw new IdentifierFormatException(tmsi, "十六进制格式", errorMessage); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 设置RNTI与UE ID的映射关系
|
|||
/// </summary>
|
|||
/// <param name="log">协议日志</param>
|
|||
/// <param name="rnti">RNTI标识符(十六进制字符串)</param>
|
|||
public void SetRnti(BuildProtocolLog log, string rnti) |
|||
{ |
|||
if (log?.UeId == null || string.IsNullOrEmpty(rnti)) |
|||
{ |
|||
_logger?.LogWarning("设置RNTI映射失败:日志或RNTI为空"); |
|||
return; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
int rntiId = int.Parse(rnti, System.Globalization.NumberStyles.HexNumber); |
|||
|
|||
lock (_lockObject) |
|||
{ |
|||
RntiToUeId[rntiId] = log.UeId.Value; |
|||
_logger?.LogDebug("设置RNTI映射: {Rnti} -> UE {UeId}", rnti, log.UeId.Value); |
|||
} |
|||
} |
|||
catch (FormatException ex) |
|||
{ |
|||
var errorMessage = $"RNTI格式无效: '{rnti}',期望十六进制格式"; |
|||
_logger?.LogError(ex, errorMessage); |
|||
throw new IdentifierFormatException(rnti, "十六进制格式", errorMessage); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 设置PLMN与UE ID的映射关系
|
|||
/// </summary>
|
|||
/// <param name="ueId">UE ID</param>
|
|||
/// <param name="plmn">PLMN标识符</param>
|
|||
public void SetPlmn(int ueId, string plmn) |
|||
{ |
|||
if (string.IsNullOrEmpty(plmn)) |
|||
{ |
|||
_logger?.LogWarning("尝试设置空的PLMN"); |
|||
return; |
|||
} |
|||
|
|||
if (ueId <= 0) |
|||
{ |
|||
throw new UeInfoException(ueId, "UE ID必须大于0"); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
lock (_lockObject) |
|||
{ |
|||
if (!PlmnToUeId.TryGetValue(plmn, out var existingUeIds)) |
|||
{ |
|||
PlmnToUeId[plmn] = new List<int> { ueId }; |
|||
_logger?.LogDebug("创建新的PLMN映射: {Plmn} -> UE {UeId}", plmn, ueId); |
|||
} |
|||
else if (!existingUeIds.Contains(ueId)) |
|||
{ |
|||
existingUeIds.Add(ueId); |
|||
_logger?.LogDebug("添加PLMN映射: {Plmn} -> UE {UeId}", plmn, ueId); |
|||
} |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger?.LogError(ex, "设置PLMN映射失败: UE {UeId}, PLMN {Plmn}", ueId, plmn); |
|||
throw new MappingException("PLMN", plmn, $"设置PLMN映射失败: {ex.Message}"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 设置Service Request TMSI与UE ID的映射关系
|
|||
/// </summary>
|
|||
/// <param name="ueId">UE ID</param>
|
|||
/// <param name="srTmsi">Service Request TMSI标识符(十六进制字符串)</param>
|
|||
public void SetSrTmsi(int ueId, string srTmsi) |
|||
{ |
|||
if (string.IsNullOrEmpty(srTmsi)) |
|||
{ |
|||
_logger?.LogWarning("尝试设置空的Service Request TMSI"); |
|||
return; |
|||
} |
|||
|
|||
if (ueId <= 0) |
|||
{ |
|||
throw new UeInfoException(ueId, "UE ID必须大于0"); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
uint srTmsiKey = Convert.ToUInt32(srTmsi, 16); |
|||
|
|||
lock (_lockObject) |
|||
{ |
|||
if (!SrTmsiToUeId.TryGetValue(srTmsiKey, out var existingUeIds)) |
|||
{ |
|||
SrTmsiToUeId[srTmsiKey] = new List<int> { ueId }; |
|||
_logger?.LogDebug("创建新的Service Request TMSI映射: {SrTmsi} -> UE {UeId}", srTmsi, ueId); |
|||
} |
|||
else if (!existingUeIds.Contains(ueId)) |
|||
{ |
|||
existingUeIds.Add(ueId); |
|||
_logger?.LogDebug("添加Service Request TMSI映射: {SrTmsi} -> UE {UeId}", srTmsi, ueId); |
|||
} |
|||
} |
|||
} |
|||
catch (FormatException ex) |
|||
{ |
|||
var errorMessage = $"Service Request TMSI格式无效: '{srTmsi}',期望十六进制格式"; |
|||
_logger?.LogError(ex, errorMessage); |
|||
throw new IdentifierFormatException(srTmsi, "十六进制格式", errorMessage); |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
|||
@ -0,0 +1,244 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using CoreAgent.ProtocolClient.Models; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Context.UeStateManager |
|||
{ |
|||
/// <summary>
|
|||
/// UeIdentifierManager UE信息管理部分
|
|||
/// </summary>
|
|||
public partial class UeIdentifierManager |
|||
{ |
|||
#region UE信息管理方法
|
|||
|
|||
/// <summary>
|
|||
/// 合并两个UE的信息
|
|||
/// 性能优化:减少不必要的对象创建
|
|||
/// </summary>
|
|||
/// <param name="ueId1">第一个UE ID</param>
|
|||
/// <param name="ueId2">第二个UE ID</param>
|
|||
public void MergeUeInfo(int ueId1, int ueId2) |
|||
{ |
|||
if (ueId1 <= 0 || ueId2 <= 0) |
|||
{ |
|||
throw new UeInfoException(Math.Min(ueId1, ueId2), "UE ID必须大于0"); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
lock (_lockObject) |
|||
{ |
|||
UeList.TryGetValue(ueId1, out var ue1); |
|||
UeList.TryGetValue(ueId2, out var ue2); |
|||
|
|||
if (ReferenceEquals(ue1, ue2)) |
|||
{ |
|||
if (ue2 == null) |
|||
{ |
|||
ue2 = CreateUeInfo(ueId2); |
|||
UeList[ueId1] = ue2; |
|||
} |
|||
} |
|||
else if (ue1 == null) |
|||
{ |
|||
UeList[ueId1] = ue2; |
|||
} |
|||
else if (ue2 == null) |
|||
{ |
|||
UeList[ueId2] = ue1; |
|||
SyncPlmnFromExistingUe(ueId1, ueId2); |
|||
} |
|||
else |
|||
{ |
|||
UeList[ueId2] = ue1; |
|||
SyncPlmnFromExistingUe(ueId1, ueId2); |
|||
} |
|||
|
|||
_logger?.LogDebug("合并UE信息: UE {UeId1} 和 UE {UeId2}", ueId1, ueId2); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger?.LogError(ex, "合并UE信息失败: UE {UeId1} 和 UE {UeId2}", ueId1, ueId2); |
|||
throw new UeInfoException(Math.Min(ueId1, ueId2), $"合并UE信息失败: {ex.Message}"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 创建新的UE信息
|
|||
/// </summary>
|
|||
/// <param name="ueId">UE ID</param>
|
|||
/// <returns>创建的UE信息</returns>
|
|||
private UEInfo CreateUeInfo(int ueId) |
|||
{ |
|||
var ueInfo = new UEInfo |
|||
{ |
|||
UeId = ueId, |
|||
Caps = new ProtocolCaps { UeId = ueId } |
|||
}; |
|||
|
|||
UeList[ueId] = ueInfo; |
|||
_logger?.LogDebug("创建新的UE信息: UE {UeId}", ueId); |
|||
return ueInfo; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取UE信息
|
|||
/// </summary>
|
|||
/// <param name="ueId">UE ID</param>
|
|||
/// <returns>UE信息,如果不存在则返回null</returns>
|
|||
public UEInfo? GetUeInfo(int ueId) |
|||
{ |
|||
if (ueId <= 0) |
|||
{ |
|||
_logger?.LogWarning("尝试获取无效UE ID的信息: {UeId}", ueId); |
|||
return null; |
|||
} |
|||
|
|||
lock (_lockObject) |
|||
{ |
|||
return UeList.TryGetValue(ueId, out var ueInfo) ? ueInfo : null; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取UE能力信息,如果不存在则创建
|
|||
/// 同时根据ImsiToUeId映射表更新IMSI信息
|
|||
/// </summary>
|
|||
/// <param name="ueId">UE ID</param>
|
|||
/// <returns>UE能力信息</returns>
|
|||
public ProtocolCaps GetUeCaps(int ueId) |
|||
{ |
|||
if (ueId <= 0) |
|||
{ |
|||
throw new UeInfoException(ueId, "UE ID必须大于0"); |
|||
} |
|||
|
|||
lock (_lockObject) |
|||
{ |
|||
if (!UeList.TryGetValue(ueId, out var ueInfo) || ueInfo == null) |
|||
{ |
|||
ueInfo = CreateUeInfo(ueId); |
|||
} |
|||
|
|||
if (ueInfo.Caps == null) |
|||
{ |
|||
ueInfo.Caps = new ProtocolCaps { UeId = ueId }; |
|||
} |
|||
|
|||
// 根据ImsiToUeId映射表更新IMSI信息
|
|||
UpdateImsiFromMapping(ueId, ueInfo); |
|||
|
|||
return ueInfo.Caps; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 根据ImsiToUeId映射表更新UE的IMSI信息
|
|||
/// 性能优化:使用LINQ提高查找效率
|
|||
/// </summary>
|
|||
/// <param name="ueId">UE ID</param>
|
|||
/// <param name="ueInfo">UE信息对象</param>
|
|||
private void UpdateImsiFromMapping(int ueId, UEInfo ueInfo) |
|||
{ |
|||
var imsiMapping = ImsiToUeId.FirstOrDefault(kvp => kvp.Value.Contains(ueId)); |
|||
if (!string.IsNullOrEmpty(imsiMapping.Key)) |
|||
{ |
|||
ueInfo.Imsi = imsiMapping.Key; |
|||
_logger?.LogDebug("更新UE {UeId} 的IMSI信息: {Imsi}", ueId, imsiMapping.Key); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 从现有UE ID同步IMSI信息到新的UE ID
|
|||
/// </summary>
|
|||
/// <param name="existingUeId">现有UE ID</param>
|
|||
/// <param name="newUeId">新的UE ID</param>
|
|||
private void SyncImsiFromExistingUe(int existingUeId, int newUeId) |
|||
{ |
|||
var imsiMapping = ImsiToUeId.FirstOrDefault(kvp => kvp.Value.Contains(existingUeId)); |
|||
if (!string.IsNullOrEmpty(imsiMapping.Key)) |
|||
{ |
|||
SetImsi(newUeId, imsiMapping.Key); |
|||
_logger?.LogDebug("同步IMSI信息: 从UE {ExistingUeId} 到 UE {NewUeId}: {Imsi}", |
|||
existingUeId, newUeId, imsiMapping.Key); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 从现有UE ID同步PLMN信息到新的UE ID
|
|||
/// </summary>
|
|||
/// <param name="existingUeId">现有UE ID</param>
|
|||
/// <param name="newUeId">新的UE ID</param>
|
|||
private void SyncPlmnFromExistingUe(int existingUeId, int newUeId) |
|||
{ |
|||
var plmnMapping = PlmnToUeId.FirstOrDefault(kvp => kvp.Value.Contains(existingUeId)); |
|||
if (!string.IsNullOrEmpty(plmnMapping.Key)) |
|||
{ |
|||
SetPlmn(newUeId, plmnMapping.Key); |
|||
_logger?.LogDebug("同步PLMN信息: 从UE {ExistingUeId} 到 UE {NewUeId}: {Plmn}", |
|||
existingUeId, newUeId, plmnMapping.Key); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取UeList中UEInfo.UeId与Caps.UeId组合的字典
|
|||
/// </summary>
|
|||
/// <returns>包含UE ID和Caps UeId组合的字典,键为UEInfo.UeId,值为Caps.UeId</returns>
|
|||
public Dictionary<int, int> GetUeIdToCapsUeIdMapping() |
|||
{ |
|||
lock (_lockObject) |
|||
{ |
|||
var mapping = new Dictionary<int, int>(); |
|||
|
|||
foreach (var kvp in UeList) |
|||
{ |
|||
var ueId = kvp.Key; |
|||
var ueInfo = kvp.Value; |
|||
|
|||
if (ueInfo?.Caps != null) |
|||
{ |
|||
mapping[ueId] = ueInfo.Caps.UeId; |
|||
} |
|||
} |
|||
|
|||
return mapping; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 批量获取UE信息
|
|||
/// 性能优化:批量处理减少锁开销
|
|||
/// </summary>
|
|||
/// <param name="ueIds">UE ID列表</param>
|
|||
/// <returns>UE信息字典</returns>
|
|||
public Dictionary<int, UEInfo> GetUeInfoBatch(IEnumerable<int> ueIds) |
|||
{ |
|||
if (ueIds == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(ueIds)); |
|||
} |
|||
|
|||
var result = new Dictionary<int, UEInfo>(); |
|||
lock (_lockObject) |
|||
{ |
|||
foreach (var ueId in ueIds) |
|||
{ |
|||
if (UeList.TryGetValue(ueId, out var ueInfo)) |
|||
{ |
|||
result[ueId] = ueInfo; |
|||
} |
|||
} |
|||
} |
|||
|
|||
_logger?.LogDebug("批量获取UE信息,共获取 {Count} 个UE的信息", result.Count); |
|||
return result; |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
|||
@ -0,0 +1,509 @@ |
|||
# MessageIdManager.cs (自动转换为Markdown) |
|||
|
|||
```csharp |
|||
// 以下内容为原始C#代码,含详细注释 |
|||
// 文件原路径:Managers/MessageIdManager.cs |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using CoreAgent.ProtocolClient.HandlerEventArgs; |
|||
using CoreAgent.ProtocolClient.Models; |
|||
using Microsoft.Extensions.Logging; |
|||
using Newtonsoft.Json.Linq; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Managers |
|||
{ |
|||
/// <summary> |
|||
/// 消息ID管理器 - 改进版 |
|||
/// |
|||
/// 修复的问题: |
|||
/// 1. 使用long类型防止ID溢出 |
|||
/// 2. 完善消息处理器清理机制 |
|||
/// 3. 改进LogGet ID管理逻辑 |
|||
/// 4. 增强参数验证和异常处理 |
|||
/// 5. 优化性能,减少字符串操作 |
|||
/// 6. 添加线程安全保护 |
|||
/// 7. 改进日志记录格式 |
|||
/// |
|||
/// 设计原则: |
|||
/// - 单一职责:专门负责消息ID管理 |
|||
/// - 开闭原则:支持扩展不同类型的消息ID管理 |
|||
/// - 线程安全:所有操作都是线程安全的 |
|||
/// - 性能优化:减少不必要的内存分配 |
|||
/// - 错误处理:完善的异常处理和参数验证 |
|||
/// </summary> |
|||
public class MessageIdManager : IDisposable |
|||
{ |
|||
#region 私有字段 |
|||
|
|||
private readonly ILogger _logger; |
|||
private readonly string _clientName; |
|||
private readonly ConcurrentDictionary<long, MessageHandler> _messageHandlers; |
|||
private readonly ConcurrentDictionary<string, MessageHandler> _messageHandlersByName; |
|||
|
|||
// 使用long类型防止溢出 |
|||
private long _generalMessageId; |
|||
private long _logGetMessageId; |
|||
|
|||
// 状态管理 |
|||
private bool _disposed; |
|||
|
|||
// 性能优化:缓存字符串构建器 |
|||
private readonly StringBuilder _logBuilder = new StringBuilder(256); |
|||
|
|||
#endregion |
|||
|
|||
#region 事件 |
|||
|
|||
/// <summary> |
|||
/// 日志获取ID变化事件 |
|||
/// </summary> |
|||
public event EventHandler<LogGetIdChangedEventArgs>? LogGetIdChanged; |
|||
|
|||
/// <summary> |
|||
/// 消息处理器清理事件 |
|||
/// </summary> |
|||
public event EventHandler<HandlerCleanupEventArgs>? HandlerCleanup; |
|||
|
|||
#endregion |
|||
|
|||
#region 属性 |
|||
|
|||
/// <summary> |
|||
/// 当前通用消息ID |
|||
/// </summary> |
|||
public long CurrentGeneralMessageId => Interlocked.Read(ref _generalMessageId); |
|||
|
|||
/// <summary> |
|||
/// 当前日志获取消息ID |
|||
/// </summary> |
|||
public long CurrentLogGetMessageId => Interlocked.Read(ref _logGetMessageId); |
|||
|
|||
/// <summary> |
|||
/// 是否已释放 |
|||
/// </summary> |
|||
public bool IsDisposed => _disposed; |
|||
|
|||
/// <summary> |
|||
/// 消息处理器数量 |
|||
/// </summary> |
|||
public int MessageHandlerCount => _messageHandlers.Count; |
|||
|
|||
/// <summary> |
|||
/// 按名称的消息处理器数量 |
|||
/// </summary> |
|||
public int NamedMessageHandlerCount => _messageHandlersByName.Count; |
|||
|
|||
/// <summary> |
|||
/// 总处理器数量 |
|||
/// </summary> |
|||
public int TotalHandlerCount => MessageHandlerCount + NamedMessageHandlerCount; |
|||
|
|||
#endregion |
|||
|
|||
#region 构造函数 |
|||
|
|||
/// <summary> |
|||
/// 构造函数 |
|||
/// </summary> |
|||
/// <param name="clientName">客户端名称</param> |
|||
/// <param name="logger">日志记录器</param> |
|||
public MessageIdManager(string clientName, ILogger logger) |
|||
{ |
|||
_clientName = clientName ?? throw new ArgumentNullException(nameof(clientName)); |
|||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); |
|||
|
|||
_messageHandlers = new ConcurrentDictionary<long, MessageHandler>(); |
|||
_messageHandlersByName = new ConcurrentDictionary<string, MessageHandler>(); |
|||
|
|||
_generalMessageId = 0; |
|||
_logGetMessageId = -1; // 初始化为-1,表示未开始 |
|||
|
|||
_logger.LogInformation("[{ClientName}] 创建消息ID管理器,初始LogGet ID: {LogGetId}", _clientName, _logGetMessageId); |
|||
} |
|||
|
|||
#endregion |
|||
|
|||
#region 公共方法 |
|||
|
|||
/// <summary> |
|||
/// 生成通用消息ID |
|||
/// </summary> |
|||
/// <param name="message">消息对象</param> |
|||
/// <param name="callback">回调函数</param> |
|||
/// <param name="errorHandler">是否为错误处理器</param> |
|||
/// <returns>消息ID</returns> |
|||
public long GenerateGeneralMessageId(JObject message, Action<JObject>? callback = null, bool errorHandler = false) |
|||
{ |
|||
ThrowIfDisposed(); |
|||
ValidateMessage(message); |
|||
|
|||
var id = GetNextMessageId(); |
|||
message["message_id"] = id; |
|||
|
|||
// 记录log_get消息的发送 |
|||
var messageType = message["message"]?.ToString(); |
|||
if (messageType == "log_get") |
|||
{ |
|||
LogLogGetMessage(id, message); |
|||
} |
|||
|
|||
// 注册回调处理器 |
|||
if (callback != null) |
|||
{ |
|||
_messageHandlers[id] = new MessageHandler |
|||
{ |
|||
Callback = callback, |
|||
ErrorHandler = errorHandler, |
|||
CreatedAt = DateTime.UtcNow |
|||
}; |
|||
} |
|||
|
|||
_logger.LogDebug("[{ClientName}] 生成通用消息ID: {MessageId}", _clientName, id); |
|||
return id; |
|||
} |
|||
|
|||
/// <summary> |
|||
/// 生成日志获取消息ID - 改进版 |
|||
/// </summary> |
|||
/// <param name="message">消息对象</param> |
|||
/// <param name="callback">回调函数</param> |
|||
/// <returns>消息ID</returns> |
|||
public long GenerateLogGetMessageId(JObject message, Action<JObject> callback) |
|||
{ |
|||
ThrowIfDisposed(); |
|||
ValidateMessage(message); |
|||
|
|||
if (callback == null) |
|||
throw new ArgumentNullException(nameof(callback)); |
|||
|
|||
// 生成新的消息ID |
|||
var newLogGetId = GetNextMessageId(); |
|||
message["message_id"] = newLogGetId; |
|||
|
|||
// 注册回调处理器 |
|||
_messageHandlers[newLogGetId] = new MessageHandler |
|||
{ |
|||
Callback = callback, |
|||
ErrorHandler = false, |
|||
CreatedAt = DateTime.UtcNow, |
|||
IsLogGetHandler = true |
|||
}; |
|||
|
|||
// 设置新的LogGet ID |
|||
var oldLogGetId = Interlocked.Exchange(ref _logGetMessageId, newLogGetId); |
|||
|
|||
// 触发事件 |
|||
LogGetIdChanged?.Invoke(this, new LogGetIdChangedEventArgs(oldLogGetId, newLogGetId)); |
|||
|
|||
_logger.LogDebug("[{ClientName}] LogGet ID变化: {OldId} -> {NewId}", _clientName, oldLogGetId, newLogGetId); |
|||
return newLogGetId; |
|||
} |
|||
|
|||
/// <summary> |
|||
/// 处理消息响应 - 改进版 |
|||
/// </summary> |
|||
/// <param name="response">响应消息</param> |
|||
/// <param name="errorHandler">错误处理回调</param> |
|||
/// <returns>是否找到并处理了消息处理器</returns> |
|||
public bool HandleMessageResponse(JObject response, Action<string>? errorHandler = null) |
|||
{ |
|||
ThrowIfDisposed(); |
|||
|
|||
if (response == null) |
|||
return false; |
|||
|
|||
// 检查消息处理器 |
|||
var id = response["message_id"]?.Value<long>(); |
|||
if (id.HasValue && _messageHandlers.TryGetValue(id.Value, out var handler)) |
|||
{ |
|||
return HandleMessageHandler(id.Value, handler, response, errorHandler); |
|||
} |
|||
|
|||
// 检查按名称的消息处理器 |
|||
var name = response["message"]?.ToString(); |
|||
if (!string.IsNullOrEmpty(name) && _messageHandlersByName.TryGetValue(name, out var nameHandler)) |
|||
{ |
|||
return HandleNamedMessageHandler(name, nameHandler, response); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary> |
|||
/// 设置消息处理器 |
|||
/// </summary> |
|||
/// <param name="names">消息名称数组</param> |
|||
/// <param name="handler">处理器</param> |
|||
public void SetMessageHandler(string[] names, MessageHandler handler) |
|||
{ |
|||
ThrowIfDisposed(); |
|||
|
|||
if (names == null || names.Length == 0) |
|||
throw new ArgumentException("消息名称不能为空", nameof(names)); |
|||
|
|||
if (handler == null) |
|||
throw new ArgumentNullException(nameof(handler)); |
|||
|
|||
foreach (var name in names) |
|||
{ |
|||
if (!string.IsNullOrEmpty(name)) |
|||
{ |
|||
_messageHandlersByName[name] = handler; |
|||
} |
|||
} |
|||
|
|||
_logger.LogDebug("[{ClientName}] 设置消息处理器: {Names}", _clientName, string.Join(", ", names)); |
|||
} |
|||
|
|||
/// <summary> |
|||
/// 取消消息处理器 |
|||
/// </summary> |
|||
/// <param name="names">消息名称数组</param> |
|||
public void UnsetMessageHandler(string[] names) |
|||
{ |
|||
ThrowIfDisposed(); |
|||
|
|||
if (names == null || names.Length == 0) |
|||
return; |
|||
|
|||
foreach (var name in names) |
|||
{ |
|||
if (!string.IsNullOrEmpty(name)) |
|||
{ |
|||
_messageHandlersByName.TryRemove(name, out _); |
|||
} |
|||
} |
|||
|
|||
_logger.LogDebug("[{ClientName}] 取消消息处理器: {Names}", _clientName, string.Join(", ", names)); |
|||
} |
|||
|
|||
/// <summary> |
|||
/// 检查是否为当前日志获取消息 |
|||
/// </summary> |
|||
/// <param name="messageId">消息ID</param> |
|||
/// <returns>是否为当前日志获取消息</returns> |
|||
public bool IsCurrentLogGetMessage(long messageId) |
|||
{ |
|||
var currentLogGetId = Interlocked.Read(ref _logGetMessageId); |
|||
return messageId == currentLogGetId; |
|||
} |
|||
|
|||
/// <summary> |
|||
/// 重置日志获取ID |
|||
/// </summary> |
|||
public void ResetLogGetId() |
|||
{ |
|||
var oldLogGetId = Interlocked.Exchange(ref _logGetMessageId, -1); |
|||
LogGetIdChanged?.Invoke(this, new LogGetIdChangedEventArgs(oldLogGetId, -1)); |
|||
_logger.LogDebug("[{ClientName}] 重置LogGet ID: {OldId} -> -1", _clientName, oldLogGetId); |
|||
} |
|||
|
|||
/// <summary> |
|||
/// 清理过期的消息处理器 - 改进版 |
|||
/// </summary> |
|||
/// <param name="maxAge">最大年龄(毫秒)</param> |
|||
/// <returns>清理的处理器数量</returns> |
|||
public int CleanupExpiredHandlers(int maxAge = 30000) // 默认30秒 |
|||
{ |
|||
ThrowIfDisposed(); |
|||
|
|||
var now = DateTime.UtcNow; |
|||
var expiredKeys = new List<long>(); |
|||
|
|||
foreach (var kvp in _messageHandlers) |
|||
{ |
|||
if (kvp.Value.CreatedAt.AddMilliseconds(maxAge) < now) |
|||
{ |
|||
expiredKeys.Add(kvp.Key); |
|||
} |
|||
} |
|||
|
|||
var cleanedCount = 0; |
|||
foreach (var key in expiredKeys) |
|||
{ |
|||
if (_messageHandlers.TryRemove(key, out _)) |
|||
{ |
|||
cleanedCount++; |
|||
} |
|||
} |
|||
|
|||
if (cleanedCount > 0) |
|||
{ |
|||
_logger.LogDebug("[{ClientName}] 清理了 {CleanedCount} 个过期的消息处理器", _clientName, cleanedCount); |
|||
|
|||
// 触发清理事件 |
|||
HandlerCleanup?.Invoke(this, new HandlerCleanupEventArgs( |
|||
_messageHandlers.Count, |
|||
_messageHandlersByName.Count, |
|||
cleanedCount)); |
|||
} |
|||
|
|||
return cleanedCount; |
|||
} |
|||
|
|||
#endregion |
|||
|
|||
#region 私有方法 |
|||
|
|||
/// <summary> |
|||
/// 获取下一个消息ID |
|||
/// </summary> |
|||
/// <returns>消息ID</returns> |
|||
private long GetNextMessageId() |
|||
{ |
|||
var id = Interlocked.Increment(ref _generalMessageId); |
|||
|
|||
// 检查溢出 |
|||
if (id <= 0) |
|||
{ |
|||
_logger.LogWarning("[{ClientName}] 消息ID溢出,重置为1", _clientName); |
|||
Interlocked.Exchange(ref _generalMessageId, 1); |
|||
return 1; |
|||
} |
|||
|
|||
return id; |
|||
} |
|||
|
|||
/// <summary> |
|||
/// 验证消息对象 |
|||
/// </summary> |
|||
/// <param name="message">消息对象</param> |
|||
private void ValidateMessage(JObject message) |
|||
{ |
|||
if (message == null) |
|||
throw new ArgumentNullException(nameof(message)); |
|||
} |
|||
|
|||
/// <summary> |
|||
/// 处理消息处理器 |
|||
/// </summary> |
|||
/// <param name="id">消息ID</param> |
|||
/// <param name="handler">处理器</param> |
|||
/// <param name="response">响应消息</param> |
|||
/// <param name="errorHandler">错误处理回调</param> |
|||
/// <returns>是否处理成功</returns> |
|||
private bool HandleMessageHandler(long id, MessageHandler handler, JObject response, Action<string>? errorHandler) |
|||
{ |
|||
// 如果不是通知消息,则移除处理器 |
|||
if (response["notification"]?.Value<bool>() != true) |
|||
{ |
|||
_messageHandlers.TryRemove(id, out _); |
|||
} |
|||
|
|||
// 处理错误 |
|||
if (response["error"] != null) |
|||
{ |
|||
if (!handler.ErrorHandler) |
|||
{ |
|||
errorHandler?.Invoke(response["error"]?.ToString() ?? "未知错误"); |
|||
} |
|||
else |
|||
{ |
|||
handler.Callback?.Invoke(response); |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
// 正常处理 |
|||
handler.Callback?.Invoke(response); |
|||
return true; |
|||
} |
|||
|
|||
/// <summary> |
|||
/// 处理按名称的消息处理器 |
|||
/// </summary> |
|||
/// <param name="name">消息名称</param> |
|||
/// <param name="handler">处理器</param> |
|||
/// <param name="response">响应消息</param> |
|||
/// <returns>是否处理成功</returns> |
|||
private bool HandleNamedMessageHandler(string name, MessageHandler handler, JObject response) |
|||
{ |
|||
handler.Callback?.Invoke(response); |
|||
return true; |
|||
} |
|||
|
|||
/// <summary> |
|||
/// 记录LogGet消息 |
|||
/// </summary> |
|||
/// <param name="id">消息ID</param> |
|||
/// <param name="message">消息对象</param> |
|||
private void LogLogGetMessage(long id, JObject message) |
|||
{ |
|||
_logBuilder.Clear(); |
|||
_logBuilder.AppendFormat("[{0}] 发送log_get消息: message_id={1}", _clientName, id); |
|||
|
|||
if (message["timeout"] != null) |
|||
_logBuilder.AppendFormat(", timeout={0}", message["timeout"]); |
|||
|
|||
if (message["headers"] != null) |
|||
_logBuilder.AppendFormat(", headers={0}", message["headers"]); |
|||
|
|||
_logBuilder.AppendFormat(", ThreadId={0}", Thread.CurrentThread.ManagedThreadId); |
|||
|
|||
_logger.LogDebug(_logBuilder.ToString()); |
|||
} |
|||
|
|||
/// <summary> |
|||
/// 检查是否已释放 |
|||
/// </summary> |
|||
private void ThrowIfDisposed() |
|||
{ |
|||
if (_disposed) |
|||
{ |
|||
throw new ObjectDisposedException(nameof(MessageIdManager)); |
|||
} |
|||
} |
|||
|
|||
#endregion |
|||
|
|||
#region IDisposable |
|||
|
|||
/// <summary> |
|||
/// 释放资源 |
|||
/// </summary> |
|||
public void Dispose() |
|||
{ |
|||
if (_disposed) return; |
|||
|
|||
_disposed = true; |
|||
|
|||
// 清理所有消息处理器 |
|||
_messageHandlers.Clear(); |
|||
_messageHandlersByName.Clear(); |
|||
|
|||
_logger.LogInformation("[{ClientName}] 释放消息ID管理器", _clientName); |
|||
} |
|||
|
|||
#endregion |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## 文档说明 |
|||
|
|||
### 类概述 |
|||
`MessageIdManager` 是一个专门负责消息ID管理的类,用于替代原始实现中的 `_messageId` 和 `_logGetId` 字段,提供更统一和强大的消息ID管理功能。 |
|||
|
|||
### 主要功能 |
|||
1. **消息ID生成**:统一管理通用消息ID和日志获取消息ID |
|||
2. **消息处理器管理**:管理消息回调处理器,支持按ID和按名称两种方式 |
|||
3. **线程安全**:所有操作都是线程安全的 |
|||
4. **自动清理**:支持自动清理过期的消息处理器 |
|||
5. **事件通知**:提供LogGet ID变化和处理器清理的事件通知 |
|||
|
|||
### 设计改进 |
|||
- 使用long类型防止ID溢出 |
|||
- 完善的异常处理和参数验证 |
|||
- 性能优化,减少字符串操作 |
|||
- 标准的Dispose模式实现 |
|||
- 详细的日志记录 |
|||
|
|||
### 使用场景 |
|||
- 在WebSocket消息管理器中统一管理消息ID |
|||
- 处理消息响应和回调 |
|||
- 管理日志获取流程 |
|||
- 提供消息处理器的生命周期管理 |
|||
@ -0,0 +1,29 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace CoreAgent.ProtocolClient.Enums |
|||
{ |
|||
/// <summary>
|
|||
/// TMSI类型
|
|||
/// </summary>
|
|||
public enum TmsiType |
|||
{ |
|||
/// <summary>
|
|||
/// 接受类型
|
|||
/// </summary>
|
|||
Accept, |
|||
|
|||
/// <summary>
|
|||
/// 请求类型
|
|||
/// </summary>
|
|||
Request, |
|||
|
|||
/// <summary>
|
|||
/// 服务请求类型
|
|||
/// </summary>
|
|||
ServiceRequest |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue