diff --git a/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj b/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj index 311732a6..ec3b5606 100644 --- a/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj +++ b/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj @@ -1,7 +1,7 @@  - 9.0.3 + 9.0.4 diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverter.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverter.cs index be7e3736..9af5aa74 100644 --- a/src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverter.cs @@ -17,7 +17,6 @@ public class DataConverter(DataConverterCollections converters) : IData /// public DataConverter() : this(new()) { - } /// @@ -28,9 +27,19 @@ public DataConverter() : this(new()) /// public virtual bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out TEntity? entity) { - var v = CreateEntity(); - var ret = Parse(data, v); - entity = ret ? v : default; + var ret = false; + entity = default; + try + { + var v = CreateEntity(); + if (Parse(data, v)) + { + entity = v; + ret = true; + } + } + catch { } + return ret; } @@ -57,8 +66,7 @@ protected virtual bool Parse(ReadOnlyMemory data, TEntity entity) var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList(); foreach (var p in properties) { - var attr = p.GetCustomAttribute(false) - ?? GetPropertyConverterAttribute(p); + var attr = p.GetCustomAttribute(false) ?? GetPropertyConverterAttribute(p); if (attr is { Type: not null }) { var value = attr.ConvertTo(data); @@ -69,8 +77,10 @@ protected virtual bool Parse(ReadOnlyMemory data, TEntity entity) } } } + ret = true; } + return ret; } @@ -81,6 +91,7 @@ protected virtual bool Parse(ReadOnlyMemory data, TEntity entity) { attr = v; } + return attr; } } diff --git a/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj b/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj index da28547c..e94abb08 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj +++ b/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj @@ -1,7 +1,7 @@  - 9.0.2 + 9.0.4 diff --git a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs index b3814fbf..a2d21b50 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs @@ -20,7 +20,7 @@ public static class ITcpSocketClientExtensions /// Sends the specified string content to the connected TCP socket client asynchronously. /// /// This method converts the provided string content into a byte array using the specified - /// encoding (or UTF-8 by default) and sends it to the connected TCP socket client. Ensure the client is connected + /// encoding (or UTF-8 by default) and sends it to the connected TCP socket client. Ensure the client is connected /// before calling this method. /// The TCP socket client to which the content will be sent. Cannot be . /// The string content to send. Cannot be or empty. @@ -51,7 +51,7 @@ public static ValueTask ConnectAsync(this ITcpSocketClient client, string return client.ConnectAsync(endPoint, token); } - private static readonly Dictionary, ValueTask> Callback)>> _cache = []; + private static readonly Dictionary, ValueTask> Callback)>> Cache = []; /// /// 增加 数据适配器及其对应的回调方法 @@ -61,25 +61,25 @@ public static ValueTask ConnectAsync(this ITcpSocketClient client, string /// public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, Func, ValueTask> callback) { - if (_cache.TryGetValue(client, out var list)) + async ValueTask ReceivedCallback(ReadOnlyMemory buffer) { - list.Add((adapter, cb)); + // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调 + await adapter.HandlerAsync(buffer); + } + + if (Cache.TryGetValue(client, out var list)) + { + list.Add((adapter, ReceivedCallback)); } else { - _cache.Add(client, [(adapter, cb)]); + Cache.Add(client, [(adapter, ReceivedCallback)]); } - client.ReceivedCallBack += cb; + client.ReceivedCallBack += ReceivedCallback; // 设置 DataPackageAdapter 的回调函数 adapter.ReceivedCallBack = callback; - - async ValueTask cb(ReadOnlyMemory buffer) - { - // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调 - await adapter.HandlerAsync(buffer); - } } /// @@ -89,7 +89,7 @@ async ValueTask cb(ReadOnlyMemory buffer) /// public static void RemoveDataPackageAdapter(this ITcpSocketClient client, Func, ValueTask> callback) { - if (_cache.TryGetValue(client, out var list)) + if (Cache.TryGetValue(client, out var list)) { var items = list.Where(i => i.Adapter.ReceivedCallBack == callback).ToList(); foreach (var c in items) @@ -101,55 +101,21 @@ public static void RemoveDataPackageAdapter(this ITcpSocketClient client, Func - /// Configures the specified to use the provided - /// for processing received data and sets a callback to handle processed data. - /// - /// This method sets up a two-way data processing pipeline: - /// The is configured to pass received data to the - /// for processing. The is configured to invoke - /// the provided with the processed data. Use this method - /// to integrate a custom data processing adapter with a TCP socket client. - /// The instance to configure. - /// The used to process incoming data. - /// A callback function invoked with the processed data. The function receives a - /// containing the processed data and returns a . - public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, Func, ValueTask> callback) - { - // 释放缓存 - if (_cache.TryGetValue(client, out var list)) - { - foreach (var (Adapter, Callback) in list) - { - client.ReceivedCallBack -= Callback; - } - list.Clear(); - } - - // 设置 ITcpSocketClient 的回调函数 - client.ReceivedCallBack = async buffer => - { - // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调 - await adapter.HandlerAsync(buffer); - }; - - // 设置 DataPackageAdapter 的回调函数 - adapter.ReceivedCallBack = callback; - } - - /// - /// 通过指定 数据处理实例,设置数据适配器并配置回调方法 + /// 通过指定 数据处理实例,设置数据适配器并配置回调方法,切记使用 移除数据处理委托防止内存泄露 /// /// 实例 /// 数据处理实例 /// 回调方法 - public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageHandler handler, Func, ValueTask> callback) + public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageHandler handler, Func, ValueTask> callback) { - client.SetDataPackageAdapter(new DataPackageAdapter(handler), callback); + client.AddDataPackageAdapter(new DataPackageAdapter(handler), callback); } + private static readonly Dictionary, ValueTask> ReceivedCallback, Delegate EntityCallback)>> EntityCache = []; + /// /// Configures the specified to use a data package adapter and a callback function - /// for processing received data. + /// for processing received data. 切记使用 移除数据处理委托防止内存泄露 /// /// This method sets up the to process incoming data using the /// specified and . The The data package adapter responsible for handling incoming data. /// The converter used to transform the received data into the specified entity type. /// The callback function to be invoked with the converted entity. - public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, IDataConverter socketDataConverter, Func callback) + public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, IDataConverter socketDataConverter, Func callback) { - // 释放缓存 - if (_cache.TryGetValue(client, out var list)) + async ValueTask ReceivedCallback(ReadOnlyMemory buffer) { - foreach (var (Adapter, Callback) in list) - { - client.ReceivedCallBack -= Callback; - } - list.Clear(); + // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调 + await adapter.HandlerAsync(buffer); } - // 设置 ITcpSocketClient 的回调函数 - client.ReceivedCallBack = async buffer => + if (EntityCache.TryGetValue(client, out var list)) { - // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调 - await adapter.HandlerAsync(buffer); - }; + list.Add((ReceivedCallback, callback)); + } + else + { + EntityCache.Add(client, [(ReceivedCallback, callback)]); + } + + client.ReceivedCallBack += ReceivedCallback; // 设置 DataPackageAdapter 的回调函数 adapter.ReceivedCallBack = async buffer => @@ -191,21 +157,39 @@ public static void SetDataPackageAdapter(this ITcpSocketClient client, } /// - /// 通过指定 数据处理实例,设置数据适配器并配置回调方法 + /// 移除 数据适配器及其对应的回调方法 + /// + /// + /// + public static void RemoveDataPackageAdapter(this ITcpSocketClient client, Func callback) + { + if (EntityCache.TryGetValue(client, out var list)) + { + var items = list.Where(i => i.EntityCallback.Equals(callback)).ToList(); + foreach (var c in items) + { + client.ReceivedCallBack -= c.ReceivedCallback; + list.Remove(c); + } + } + } + + /// + /// 通过指定 数据处理实例,设置数据适配器并配置回调方法。切记使用 移除数据处理委托防止内存泄露 /// /// /// /// /// /// - public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageHandler handler, IDataConverter socketDataConverter, Func callback) + public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageHandler handler, IDataConverter socketDataConverter, Func callback) { - client.SetDataPackageAdapter(new DataPackageAdapter(handler), socketDataConverter, callback); + client.AddDataPackageAdapter(new DataPackageAdapter(handler), socketDataConverter, callback); } /// /// Configures the specified to use a custom data package adapter and callback - /// function. + /// function. 切记使用 移除数据处理委托防止内存泄露 /// /// This method sets up the to use the specified for handling incoming data. If the type is decorated with a (this ITcpSocketClient client, /// The TCP socket client to configure. /// The data package adapter responsible for processing incoming data. /// The callback function to invoke with the processed entity of type . - public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, Func callback) + public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, Func callback) { - // 释放缓存 - if (_cache.TryGetValue(client, out var list)) - { - foreach (var (Adapter, Callback) in list) - { - client.ReceivedCallBack -= Callback; - } - list.Clear(); - } - - // 设置 ITcpSocketClient 的回调函数 - client.ReceivedCallBack = async buffer => + async ValueTask ReceivedCallback(ReadOnlyMemory buffer) { // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调 await adapter.HandlerAsync(buffer); - }; - - IDataConverter? converter = null; + } - var type = typeof(TEntity); - var converterType = type.GetCustomAttribute(); - if (converterType is { Type: not null }) + if (EntityCache.TryGetValue(client, out var list)) { - // 如果类型上有 SocketDataTypeConverterAttribute 特性则使用特性中指定的转换器 - converter = converterType.Type.CreateInstance>(); + list.Add((ReceivedCallback, callback)); } else { - // 如果没有特性则从 ITcpSocketClient 中的服务容器获取转换器 - converter = client.GetSocketDataConverter(); + EntityCache.Add(client, [(ReceivedCallback, callback)]); } + client.ReceivedCallBack += ReceivedCallback; + + IDataConverter? converter = null; + + var type = typeof(TEntity); + var converterType = type.GetCustomAttribute(); + + // 如果类型上有 SocketDataTypeConverterAttribute 特性则使用特性中指定的转换器 + // 如果没有特性则从 ITcpSocketClient 中的服务容器获取转换器 + converter = converterType is { Type: not null } + ? converterType.Type.CreateInstance>() + : client.GetSocketDataConverter(); + if (converter == null) { - // 设置正常回调 + // 未设置数据转换器返回 default 值 adapter.ReceivedCallBack = async buffer => await callback(default); } else { // 设置转化器 - adapter.SetDataAdapterCallback(converter, callback); + adapter.ReceivedCallBack = async buffer => + { + TEntity? ret = default; + if (converter.TryConvertTo(buffer, out var t)) + { + ret = t; + } + await callback(ret); + }; } } /// - /// 通过指定 数据处理实例,设置数据适配器并配置回调方法 + /// 通过指定 数据处理实例,设置数据适配器并配置回调方法。切记使用 移除数据处理委托防止内存泄露 /// /// 实例 /// 数据处理实例 /// 回调方法 - public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageHandler handler, Func callback) + public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageHandler handler, Func callback) { - client.SetDataPackageAdapter(new DataPackageAdapter(handler), callback); - } - - private static void SetDataAdapterCallback(this IDataPackageAdapter adapter, IDataConverter converter, Func callback) - { - adapter.ReceivedCallBack = async buffer => - { - TEntity? ret = default; - if (converter.TryConvertTo(buffer, out var t)) - { - ret = t; - } - await callback(ret); - }; + client.AddDataPackageAdapter(new DataPackageAdapter(handler), callback); } private static IDataConverter? GetSocketDataConverter(this ITcpSocketClient client) diff --git a/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs b/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs index bbe8a6f6..0b524042 100644 --- a/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs +++ b/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs @@ -497,7 +497,7 @@ public async Task FixLengthDataPackageHandler_Ok() // 设置数据适配器 var adapter = new DataPackageAdapter(new FixLengthDataPackageHandler(7)); - client.SetDataPackageAdapter(adapter, buffer => + var callback = new Func, ValueTask>(buffer => { // buffer 即是接收到的数据 buffer.CopyTo(receivedBuffer); @@ -505,6 +505,7 @@ public async Task FixLengthDataPackageHandler_Ok() tcs.SetResult(); return ValueTask.CompletedTask; }); + client.AddDataPackageAdapter(adapter, callback); // 测试 ConnectAsync 方法 var connect = await client.ConnectAsync("localhost", port); @@ -538,7 +539,7 @@ public async Task FixLengthDataPackageHandler_Sticky() // 设置数据适配器 var adapter = new DataPackageAdapter(new FixLengthDataPackageHandler(7)); - client.SetDataPackageAdapter(adapter, buffer => + client.AddDataPackageAdapter(adapter, buffer => { // buffer 即是接收到的数据 buffer.CopyTo(receivedBuffer); @@ -588,7 +589,7 @@ public async Task DelimiterDataPackageHandler_Ok() // 设置数据适配器 var adapter = new DataPackageAdapter(new DelimiterDataPackageHandler([13, 10])); - client.SetDataPackageAdapter(adapter, buffer => + client.AddDataPackageAdapter(adapter, buffer => { // buffer 即是接收到的数据 buffer.CopyTo(receivedBuffer); @@ -641,12 +642,13 @@ public async Task TryConvertTo_Ok() // 设置数据适配器 var adapter = new DataPackageAdapter(new FixLengthDataPackageHandler(29)); - client.SetDataPackageAdapter(adapter, new DataConverter(), t => + var callback = new Func(t => { entity = t; tcs.SetResult(); return Task.CompletedTask; }); + client.AddDataPackageAdapter(adapter, new DataConverter(), callback); // 连接 TCP Server var connect = await client.ConnectAsync("localhost", port); @@ -703,10 +705,11 @@ public async Task TryConvertTo_Ok() // no attribute Assert.Null(entity.Value13); + client.RemoveDataPackageAdapter(callback); // 测试 SocketDataConverter 标签功能 tcs = new TaskCompletionSource(); - client.SetDataPackageAdapter(adapter, t => + client.AddDataPackageAdapter(adapter, t => { entity = t; tcs.SetResult(); @@ -728,7 +731,7 @@ public async Task TryConvertTo_Ok() // 测试 SetDataPackageAdapter 泛型无标签情况 tcs = new TaskCompletionSource(); NoConvertEntity? noConvertEntity = null; - client.SetDataPackageAdapter(adapter, t => + client.AddDataPackageAdapter(adapter, t => { noConvertEntity = t; tcs.SetResult(); @@ -779,7 +782,7 @@ public async Task TryGetTypeConverter_Ok() var adapter = new DataPackageAdapter(new FixLengthDataPackageHandler(7)); OptionConvertEntity? entity = null; - client.SetDataPackageAdapter(adapter, data => + client.AddDataPackageAdapter(adapter, data => { // buffer 即是接收到的数据 entity = data; @@ -857,7 +860,7 @@ public async Task SetDataPackageAdapter_Ok() var connect = await client.ConnectAsync("localhost", port); client.AddDataPackageAdapter(new DataPackageAdapter(new FixLengthDataPackageHandler(7)), ReceivedCallBack); - client.SetDataPackageAdapter(new FixLengthDataPackageHandler(7), ReceivedCallBack); + client.AddDataPackageAdapter(new FixLengthDataPackageHandler(7), ReceivedCallBack); var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); await client.SendAsync(data); @@ -889,10 +892,10 @@ public async Task SetDataPackageAdapter_Generic() var connect = await client.ConnectAsync("localhost", port); client.AddDataPackageAdapter(new DataPackageAdapter(new FixLengthDataPackageHandler(7)), ReceivedCallBack); - client.SetDataPackageAdapter(new FixLengthDataPackageHandler(7), ReceivedEntityCallBack); + client.AddDataPackageAdapter(new FixLengthDataPackageHandler(7), ReceivedEntityCallBack); client.AddDataPackageAdapter(new DataPackageAdapter(new FixLengthDataPackageHandler(7)), ReceivedCallBack); - client.SetDataPackageAdapter(new FixLengthDataPackageHandler(7), new MockSocketDataConverter(), ReceivedEntityCallBack); + client.AddDataPackageAdapter(new FixLengthDataPackageHandler(7), new MockSocketDataConverter(), ReceivedEntityCallBack); var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); await client.SendAsync(data); @@ -930,7 +933,7 @@ public async Task Convert_Ok() // 连接 TCP Server var connect = await client.ConnectAsync("localhost", port); - client.SetDataPackageAdapter(new FixLengthDataPackageHandler(7), ReceivedCallBack); + client.AddDataPackageAdapter(new FixLengthDataPackageHandler(7), ReceivedCallBack); var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); await client.SendAsync(data);