From 06724138b9b6c4da536159839fbfb03ea62ebdf2 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 23 Jul 2025 16:10:59 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20Socket=20?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BootstrapBlazor.Extensions.sln | 35 +- .../BootstrapBlazor.Socket.csproj | 12 + .../DataConverter/ISocketDataConverter.cs | 31 + .../DataConverter/SocketDataConverter.cs | 81 ++ .../SocketDataConverterCollections.cs | 101 ++ .../SocketDataPropertyConverterAttribute.cs | 45 + .../SocketDataTypeConverterAttribute.cs | 17 + .../DataPackageAdapter/DataPackageAdapter.cs | 80 ++ .../DataPackageAdapter/IDataPackageAdapter.cs | 54 + .../DataPackageHandlerBase.cs | 68 + .../DelimiterDataPackageHandler.cs | 85 ++ .../FixLengthDataPackageHandler.cs | 53 + .../DataPackageHandler/IDataPackageHandler.cs | 32 + .../SocketDataPropertyExtensions.cs | 105 ++ .../ISocketDataPropertyConverter.cs | 18 + .../SocketDataBoolConverter.cs | 25 + .../SocketDataByteArrayConverter.cs | 20 + .../SocketDataDoubleBigEndianConverter.cs | 32 + .../SocketDataDoubleLittleEndianConverter.cs | 32 + .../SocketDataEnumConverter.cs | 32 + .../SocketDataInt16BigEndianConverter.cs | 32 + .../SocketDataInt16LittleEndianConverter.cs | 32 + .../SocketDataInt32BigEndianConverter.cs | 33 + .../SocketDataInt32LittleEndianConverter.cs | 33 + .../SocketDataInt64BigEndianConverter.cs | 33 + .../SocketDataInt64LittleEndianConverter.cs | 33 + .../SocketDataSingleBigEndianConverter.cs | 32 + .../SocketDataSingleLittleEndianConverter.cs | 32 + .../SocketDataStringConverter.cs | 23 + .../SocketDataUInt16BigEndianConverter.cs | 32 + .../SocketDataUInt16LittleEndianConverter.cs | 32 + .../SocketDataUInt32BigEndianConverter.cs | 32 + .../SocketDataUInt32LittleEndianConverter.cs | 32 + .../SocketDataUInt64BigEndianConverter.cs | 32 + .../SocketDataUInt64LittleEndianConverter.cs | 32 + .../BootstrapBlazor.TcpSocket.csproj | 41 + .../DefaultTcpSocketClient.cs | 440 ++++++ .../DefaultTcpSocketClientProvider.cs | 92 ++ .../DefaultTcpSocketFactory.cs | 60 + .../Extensions/ITcpSocketClientExtensions.cs | 192 +++ .../Extensions/TcpSocketExtensions.cs | 61 + .../Extensions/Utility.cs | 74 + .../ITcpSocketClient.cs | 92 ++ .../ITcpSocketClientProvider.cs | 72 + .../ITcpSocketFactory.cs | 29 + .../TcpSocketClientOptions.cs | 67 + test/Directory.Build.props | 2 +- .../AntDesign/filled.svg | 0 .../AntDesign/filled.zip | Bin .../AntDesign/outlined.svg | 0 .../AntDesign/outlined.zip | Bin .../AntDesign/twotone.svg | 0 .../AntDesign/twotone.zip | Bin .../Element/element.zip | Bin .../IconPark/download.zip | Bin .../OctIcon/octicons.zip | Bin .../UnitTest.cs | 0 .../UnitTestSvgIcon.csproj} | 0 .../Univer/univer.zip | Bin .../DefaultSocketClientProviderTest.cs | 108 ++ test/UnitTestTcpSocket/Foo.cs | 94 ++ .../SocketDataConverterCollectionsTest.cs | 102 ++ .../UnitTestTcpSocket/TcpSocketFactoryTest.cs | 1267 +++++++++++++++++ .../TcpSocketPropertyConverterTest.cs | 72 + .../UnitTestTcpSocket.csproj | 24 + test/UnitTestTcpSocket/UtiityTest.cs | 32 + 66 files changed, 4346 insertions(+), 8 deletions(-) create mode 100644 src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj create mode 100644 src/extensions/BootstrapBlazor.Socket/DataConverter/ISocketDataConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataConverterCollections.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataPropertyConverterAttribute.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataTypeConverterAttribute.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/DataPackageAdapter/DataPackageAdapter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/DataPackageAdapter/IDataPackageAdapter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/DataPackageHandler/DataPackageHandlerBase.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/DataPackageHandler/DelimiterDataPackageHandler.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/DataPackageHandler/FixLengthDataPackageHandler.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/DataPackageHandler/IDataPackageHandler.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/Extensions/SocketDataPropertyExtensions.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/ISocketDataPropertyConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataBoolConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataByteArrayConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataDoubleBigEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataDoubleLittleEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataEnumConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt16BigEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt16LittleEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt32BigEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt32LittleEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt64BigEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt64LittleEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataSingleBigEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataSingleLittleEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataStringConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt16BigEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt16LittleEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt32BigEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt32LittleEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt64BigEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt64LittleEndianConverter.cs create mode 100644 src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj create mode 100644 src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClient.cs create mode 100644 src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClientProvider.cs create mode 100644 src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketFactory.cs create mode 100644 src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs create mode 100644 src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs create mode 100644 src/extensions/BootstrapBlazor.TcpSocket/Extensions/Utility.cs create mode 100644 src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClient.cs create mode 100644 src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClientProvider.cs create mode 100644 src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketFactory.cs create mode 100644 src/extensions/BootstrapBlazor.TcpSocket/TcpSocketClientOptions.cs rename test/{UniTestSvgIcon => UnitTestSvgIcon}/AntDesign/filled.svg (100%) rename test/{UniTestSvgIcon => UnitTestSvgIcon}/AntDesign/filled.zip (100%) rename test/{UniTestSvgIcon => UnitTestSvgIcon}/AntDesign/outlined.svg (100%) rename test/{UniTestSvgIcon => UnitTestSvgIcon}/AntDesign/outlined.zip (100%) rename test/{UniTestSvgIcon => UnitTestSvgIcon}/AntDesign/twotone.svg (100%) rename test/{UniTestSvgIcon => UnitTestSvgIcon}/AntDesign/twotone.zip (100%) rename test/{UniTestSvgIcon => UnitTestSvgIcon}/Element/element.zip (100%) rename test/{UniTestSvgIcon => UnitTestSvgIcon}/IconPark/download.zip (100%) rename test/{UniTestSvgIcon => UnitTestSvgIcon}/OctIcon/octicons.zip (100%) rename test/{UniTestSvgIcon => UnitTestSvgIcon}/UnitTest.cs (100%) rename test/{UniTestSvgIcon/UniTestSvgIcon.csproj => UnitTestSvgIcon/UnitTestSvgIcon.csproj} (100%) rename test/{UniTestSvgIcon => UnitTestSvgIcon}/Univer/univer.zip (100%) create mode 100644 test/UnitTestTcpSocket/DefaultSocketClientProviderTest.cs create mode 100644 test/UnitTestTcpSocket/Foo.cs create mode 100644 test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs create mode 100644 test/UnitTestTcpSocket/TcpSocketFactoryTest.cs create mode 100644 test/UnitTestTcpSocket/TcpSocketPropertyConverterTest.cs create mode 100644 test/UnitTestTcpSocket/UnitTestTcpSocket.csproj create mode 100644 test/UnitTestTcpSocket/UtiityTest.cs diff --git a/BootstrapBlazor.Extensions.sln b/BootstrapBlazor.Extensions.sln index 2850f3cb..2af8a1dd 100644 --- a/BootstrapBlazor.Extensions.sln +++ b/BootstrapBlazor.Extensions.sln @@ -156,8 +156,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Mermaid", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.MeiliSearch", "src\components\BootstrapBlazor.MeiliSearch\BootstrapBlazor.MeiliSearch.csproj", "{4B086A62-5F5A-47BC-921F-35803F26DD68}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniTestSvgIcon", "test\UniTestSvgIcon\UniTestSvgIcon.csproj", "{F965576B-A801-4473-85FE-E100125FDEF5}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.RDKit", "src\components\BootstrapBlazor.RDKit\BootstrapBlazor.RDKit.csproj", "{7328E464-AE3C-4277-BEC3-422C56637066}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.SmilesDrawer", "src\components\BootstrapBlazor.SmilesDrawer\BootstrapBlazor.SmilesDrawer.csproj", "{84823875-1B07-4CCE-A009-29AEF90C6C10}" @@ -190,6 +188,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Vditor", "s EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.OfficeViewer", "src\components\BootstrapBlazor.OfficeViewer\BootstrapBlazor.OfficeViewer.csproj", "{2436940C-5920-D801-8A81-721F4C20A355}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Socket", "src\extensions\BootstrapBlazor.Socket\BootstrapBlazor.Socket.csproj", "{965F1512-57DC-4621-9C74-E059A14BB866}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.TcpSocket", "src\extensions\BootstrapBlazor.TcpSocket\BootstrapBlazor.TcpSocket.csproj", "{3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestTcpSocket", "test\UnitTestTcpSocket\UnitTestTcpSocket.csproj", "{10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestSvgIcon", "test\UnitTestSvgIcon\UnitTestSvgIcon.csproj", "{7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -448,10 +454,6 @@ Global {4B086A62-5F5A-47BC-921F-35803F26DD68}.Debug|Any CPU.Build.0 = Debug|Any CPU {4B086A62-5F5A-47BC-921F-35803F26DD68}.Release|Any CPU.ActiveCfg = Release|Any CPU {4B086A62-5F5A-47BC-921F-35803F26DD68}.Release|Any CPU.Build.0 = Release|Any CPU - {F965576B-A801-4473-85FE-E100125FDEF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F965576B-A801-4473-85FE-E100125FDEF5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F965576B-A801-4473-85FE-E100125FDEF5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F965576B-A801-4473-85FE-E100125FDEF5}.Release|Any CPU.Build.0 = Release|Any CPU {7328E464-AE3C-4277-BEC3-422C56637066}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7328E464-AE3C-4277-BEC3-422C56637066}.Debug|Any CPU.Build.0 = Debug|Any CPU {7328E464-AE3C-4277-BEC3-422C56637066}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -516,6 +518,22 @@ Global {2436940C-5920-D801-8A81-721F4C20A355}.Debug|Any CPU.Build.0 = Debug|Any CPU {2436940C-5920-D801-8A81-721F4C20A355}.Release|Any CPU.ActiveCfg = Release|Any CPU {2436940C-5920-D801-8A81-721F4C20A355}.Release|Any CPU.Build.0 = Release|Any CPU + {965F1512-57DC-4621-9C74-E059A14BB866}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {965F1512-57DC-4621-9C74-E059A14BB866}.Debug|Any CPU.Build.0 = Debug|Any CPU + {965F1512-57DC-4621-9C74-E059A14BB866}.Release|Any CPU.ActiveCfg = Release|Any CPU + {965F1512-57DC-4621-9C74-E059A14BB866}.Release|Any CPU.Build.0 = Release|Any CPU + {3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}.Release|Any CPU.Build.0 = Release|Any CPU + {10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}.Release|Any CPU.Build.0 = Release|Any CPU + {7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -588,7 +606,6 @@ Global {29E47F4A-CE03-42B5-BDAA-FB4B40D4C897} = {FF1089BE-C704-4374-B629-C57C08E1798F} {DA2198E1-2CA9-EE53-926B-7950AB4B5EBF} = {FF1089BE-C704-4374-B629-C57C08E1798F} {4B086A62-5F5A-47BC-921F-35803F26DD68} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {F965576B-A801-4473-85FE-E100125FDEF5} = {B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE} {7328E464-AE3C-4277-BEC3-422C56637066} = {FF1089BE-C704-4374-B629-C57C08E1798F} {84823875-1B07-4CCE-A009-29AEF90C6C10} = {FF1089BE-C704-4374-B629-C57C08E1798F} {AA4EDA37-1D81-4235-A7F6-F1C112B364EF} = {FF1089BE-C704-4374-B629-C57C08E1798F} @@ -605,6 +622,10 @@ Global {4757B038-70E4-40B0-9B73-700EE5632B07} = {FF1089BE-C704-4374-B629-C57C08E1798F} {D417E1B9-D146-4983-81D0-79F3193B322B} = {FF1089BE-C704-4374-B629-C57C08E1798F} {2436940C-5920-D801-8A81-721F4C20A355} = {FF1089BE-C704-4374-B629-C57C08E1798F} + {965F1512-57DC-4621-9C74-E059A14BB866} = {7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2} + {3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C} = {7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2} + {10D35EE5-FA31-4C80-B113-CD7A0FB76B4E} = {B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE} + {7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3} = {B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D5EB1960-6F30-4CE1-B375-EAE1F787D6FF} diff --git a/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj b/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj new file mode 100644 index 00000000..b353d142 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj @@ -0,0 +1,12 @@ + + + + 9.0.0 + + + + BootstrapBlazor Socket + BootstrapBlazor extensions of Socket + + + diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/ISocketDataConverter.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/ISocketDataConverter.cs new file mode 100644 index 00000000..d8eff1ad --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/DataConverter/ISocketDataConverter.cs @@ -0,0 +1,31 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +/// +/// Socket 数据转换器接口 +/// +public interface ISocketDataConverter +{ + +} + +/// +/// Defines a method to convert raw socket data into a specified entity type. +/// +/// The type of entity to convert the data into. +public interface ISocketDataConverter : ISocketDataConverter +{ + /// + /// Attempts to convert the specified data to an instance of . + /// + /// This method does not throw an exception if the conversion fails. Instead, it returns and sets to . + /// The data to be converted, represented as a read-only memory block of bytes. + /// When this method returns, contains the converted if the conversion succeeded; + /// otherwise, . + /// if the conversion was successful; otherwise, . + bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out TEntity? entity); +} diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataConverter.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataConverter.cs new file mode 100644 index 00000000..248fbf3c --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataConverter.cs @@ -0,0 +1,81 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Reflection; + +namespace BootstrapBlazor.Components; + +/// +/// Provides a base class for converting socket data into a specified entity type. +/// +/// The type of entity to convert the socket data into. +public class SocketDataConverter(SocketDataConverterCollections converters) : ISocketDataConverter +{ + /// + /// 构造函数 + /// + public SocketDataConverter() : 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; + return ret; + } + + /// + /// 创建实体实例方法 + /// + /// + protected virtual TEntity CreateEntity() => Activator.CreateInstance(); + + /// + /// 将字节数据转换为指定实体类型的实例。 + /// + /// + /// + protected virtual bool Parse(ReadOnlyMemory data, TEntity entity) + { + // 使用 SocketDataPropertyAttribute 特性获取数据转换规则 + var ret = false; + if (entity != null) + { + var unuseProperties = new List(32); + + // 通过 SocketDataPropertyConverterAttribute 特性获取属性转换器 + var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList(); + foreach (var p in properties) + { + var attr = p.GetCustomAttribute(false) + ?? GetPropertyConverterAttribute(p); + if (attr != null) + { + p.SetValue(entity, attr.ConvertTo(data)); + } + } + ret = true; + } + return ret; + } + + private SocketDataPropertyConverterAttribute? GetPropertyConverterAttribute(PropertyInfo propertyInfo) + { + SocketDataPropertyConverterAttribute? attr = null; + if (converters.TryGetPropertyConverter(propertyInfo, out var v)) + { + attr = v; + } + return attr; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataConverterCollections.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataConverterCollections.cs new file mode 100644 index 00000000..521d5e36 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataConverterCollections.cs @@ -0,0 +1,101 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; + +namespace BootstrapBlazor.Components; + +/// +/// 数据转换器集合类 +/// +public sealed class SocketDataConverterCollections +{ + readonly ConcurrentDictionary _converters = new(); + readonly ConcurrentDictionary _propertyConverters = new(); + + /// + /// 增加指定 数据类型转换器方法 + /// + /// + /// + public void AddTypeConverter(ISocketDataConverter converter) + { + var type = typeof(TEntity); + _converters.AddOrUpdate(type, t => converter, (t, v) => converter); + } + + /// + /// 增加默认数据类型转换器方法 转换器使用 + /// + /// + public void AddTypeConverter() => AddTypeConverter(new SocketDataConverter(this)); + + /// + /// 添加属性类型转化器方法 + /// + /// + /// + /// + public void AddPropertyConverter(Expression> propertyExpression, SocketDataPropertyConverterAttribute attribute) + { + if (propertyExpression.Body is MemberExpression memberExpression) + { + if (attribute.Type == null) + { + attribute.Type = memberExpression.Type; + } + _propertyConverters.AddOrUpdate(memberExpression.Member, m => attribute, (m, v) => attribute); + } + } + + /// + /// 获得指定数据类型转换器方法 + /// + /// + public bool TryGetTypeConverter([NotNullWhen(true)] out ISocketDataConverter? converter) + { + converter = null; + var ret = false; + if (_converters.TryGetValue(typeof(TEntity), out var v) && v is ISocketDataConverter c) + { + converter = c; + ret = true; + } + return ret; + } + + /// + /// 获得指定数据类型属性转换器方法 + /// + /// + public bool TryGetPropertyConverter(Expression> propertyExpression, [NotNullWhen(true)] out SocketDataPropertyConverterAttribute? converterAttribute) + { + converterAttribute = null; + var ret = false; + if (propertyExpression.Body is MemberExpression memberExpression && TryGetPropertyConverter(memberExpression.Member, out var v)) + { + converterAttribute = v; + ret = true; + } + return ret; + } + + /// + /// 获得指定数据类型属性转换器方法 + /// + /// + public bool TryGetPropertyConverter(MemberInfo memberInfo, [NotNullWhen(true)] out SocketDataPropertyConverterAttribute? converterAttribute) + { + converterAttribute = null; + var ret = false; + if (_propertyConverters.TryGetValue(memberInfo, out var v)) + { + converterAttribute = v; + ret = true; + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataPropertyConverterAttribute.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataPropertyConverterAttribute.cs new file mode 100644 index 00000000..2cc5c802 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataPropertyConverterAttribute.cs @@ -0,0 +1,45 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +/// +/// Represents an attribute used to mark a field as a socket data field. +/// +/// This attribute can be applied to fields to indicate that they are part of the data transmitted over a +/// socket connection. It is intended for use in scenarios where socket communication requires specific fields to be +/// identified for processing. +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class SocketDataPropertyConverterAttribute : Attribute +{ + /// + /// 获得/设置 数据类型 + /// + public Type? Type { get; set; } + + /// + /// 获得/设置 数据偏移量 + /// + public int Offset { get; set; } + + /// + /// 获得/设置 数据长度 + /// + public int Length { get; set; } + + /// + /// 获得/设置 数据编码名称 + /// + public string? EncodingName { get; set; } + + /// + /// 获得/设置 数据转换器类型 + /// + public Type? ConverterType { get; set; } + + /// + /// 获得/设置 数据转换器构造函数参数 + /// + public object?[]? ConverterParameters { get; set; } +} diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataTypeConverterAttribute.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataTypeConverterAttribute.cs new file mode 100644 index 00000000..af27f279 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataTypeConverterAttribute.cs @@ -0,0 +1,17 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +/// +/// +/// +[AttributeUsage(AttributeTargets.Class)] +public class SocketDataTypeConverterAttribute : Attribute +{ + /// + /// Gets or sets the type of the . + /// + public Type? Type { get; set; } +} diff --git a/src/extensions/BootstrapBlazor.Socket/DataPackageAdapter/DataPackageAdapter.cs b/src/extensions/BootstrapBlazor.Socket/DataPackageAdapter/DataPackageAdapter.cs new file mode 100644 index 00000000..3d35953a --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/DataPackageAdapter/DataPackageAdapter.cs @@ -0,0 +1,80 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +/// +/// Provides a base implementation for adapting data packages between different systems or formats. +/// +/// This abstract class serves as a foundation for implementing custom data package adapters. It defines +/// common methods for sending, receiving, and handling data packages, as well as a property for accessing the +/// associated data package handler. Derived classes should override the virtual methods to provide specific behavior +/// for handling data packages. +public class DataPackageAdapter : IDataPackageAdapter +{ + /// + /// + /// + public Func, ValueTask>? ReceivedCallBack { get; set; } + + /// + /// + /// + public IDataPackageHandler? DataPackageHandler { get; set; } + + /// + /// + /// + /// + /// + /// + public virtual async ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default) + { + if (DataPackageHandler != null) + { + if (DataPackageHandler.ReceivedCallBack == null) + { + DataPackageHandler.ReceivedCallBack = OnHandlerReceivedCallBack; + } + + // 如果存在数据处理器则调用其处理方法 + await DataPackageHandler.HandlerAsync(data, token); + } + } + + /// + /// + /// + /// + /// + /// + /// + public virtual bool TryConvertTo(ReadOnlyMemory data, ISocketDataConverter socketDataConverter, out TEntity? entity) + { + entity = default; + var ret = socketDataConverter.TryConvertTo(data, out var v); + if (ret) + { + entity = v; + } + return ret; + } + + /// + /// Handles incoming data by invoking a callback method, if one is defined. + /// + /// This method is designed to be overridden in derived classes to provide custom handling of + /// incoming data. If a callback method is assigned, it will be invoked asynchronously with the provided + /// data. + /// The incoming data to be processed, represented as a read-only memory block of bytes. + /// + protected virtual async ValueTask OnHandlerReceivedCallBack(ReadOnlyMemory data) + { + if (ReceivedCallBack != null) + { + // 调用接收回调方法处理数据 + await ReceivedCallBack(data); + } + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/DataPackageAdapter/IDataPackageAdapter.cs b/src/extensions/BootstrapBlazor.Socket/DataPackageAdapter/IDataPackageAdapter.cs new file mode 100644 index 00000000..ab1b2995 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/DataPackageAdapter/IDataPackageAdapter.cs @@ -0,0 +1,54 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +/// +/// Defines an adapter for handling and transmitting data packages to a target destination. +/// +/// This interface provides methods for sending data asynchronously and configuring a data handler. +/// Implementations of this interface are responsible for managing the interaction between the caller and the underlying +/// data transmission mechanism. +public interface IDataPackageAdapter +{ + /// + /// Gets or sets the callback function to be invoked when data is received. + /// + /// The callback function is expected to handle the received data asynchronously. Ensure that the + /// implementation of the callback does not block the calling thread and completes promptly to avoid performance + /// issues. + Func, ValueTask>? ReceivedCallBack { get; set; } + + /// + /// Gets the handler responsible for processing data packages. + /// + IDataPackageHandler? DataPackageHandler { get; } + + /// + /// Asynchronously receives data from a source and processes it. + /// + /// This method does not return any result directly. It is intended for scenarios where data is received + /// and processed asynchronously. Ensure that the parameter contains valid data before calling + /// this method. + /// A read-only memory region containing the data to be received. The caller must ensure the memory is valid and + /// populated. + /// An optional cancellation token that can be used to cancel the operation. Defaults to if + /// not provided. + /// A representing the asynchronous operation. The task completes when the data has been + /// successfully received and processed. + ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default); + + /// + /// Attempts to convert the specified byte data into an entity of type . + /// + /// This method does not throw an exception if the conversion fails. Instead, it returns and sets to its default value. + /// The type of the entity to convert the data to. + /// The byte data to be converted. + /// The converter used to transform the byte data into an entity. + /// When this method returns, contains the converted entity if the conversion was successful; otherwise, the default + /// value for the type of the entity. + /// if the conversion was successful; otherwise, . + bool TryConvertTo(ReadOnlyMemory data, ISocketDataConverter socketDataConverter, out TEntity? entity); +} diff --git a/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/DataPackageHandlerBase.cs b/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/DataPackageHandlerBase.cs new file mode 100644 index 00000000..90935c2b --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/DataPackageHandlerBase.cs @@ -0,0 +1,68 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +/// +/// Provides a base implementation for handling data packages in a communication system. +/// +/// This abstract class defines the core contract for receiving and sending data packages. Derived +/// classes should override and extend its functionality to implement specific data handling logic. The default +/// implementation simply returns the provided data. +public abstract class DataPackageHandlerBase : IDataPackageHandler +{ + private Memory _lastReceiveBuffer = Memory.Empty; + + /// + /// + /// + public Func, ValueTask>? ReceivedCallBack { get; set; } + + /// + /// + /// + /// + /// + /// + public abstract ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default); + + /// + /// Handles the processing of a sticky package by adjusting the provided buffer and length. + /// + /// This method processes the portion of the buffer beyond the specified length and updates the + /// internal state accordingly. The caller must ensure that the contains sufficient data + /// for the specified . + /// The memory buffer containing the data to process. + /// The length of the valid data within the buffer. + protected void SlicePackage(ReadOnlyMemory buffer, int length) + { + _lastReceiveBuffer = buffer[length..].ToArray().AsMemory(); + } + + /// + /// Concatenates the provided buffer with any previously stored data and returns the combined result. + /// + /// This method combines the provided buffer with any data stored in the internal buffer. After + /// concatenation, the internal buffer is cleared. The returned memory block is allocated from a shared memory pool + /// and should be used promptly to avoid holding onto pooled resources. + /// The buffer to concatenate with the previously stored data. Must not be empty. + /// A instance containing the concatenated data. If no previously stored data exists, the + /// method returns the input . + protected ReadOnlyMemory ConcatBuffer(ReadOnlyMemory buffer) + { + if (_lastReceiveBuffer.IsEmpty) + { + return buffer; + } + + // 计算缓存区长度 + Memory merged = new byte[_lastReceiveBuffer.Length + buffer.Length]; + _lastReceiveBuffer.CopyTo(merged); + buffer.CopyTo(merged[_lastReceiveBuffer.Length..]); + + // Clear the sticky buffer + _lastReceiveBuffer = Memory.Empty; + return merged; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/DelimiterDataPackageHandler.cs b/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/DelimiterDataPackageHandler.cs new file mode 100644 index 00000000..04097461 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/DelimiterDataPackageHandler.cs @@ -0,0 +1,85 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers; +using System.Text; + +namespace BootstrapBlazor.Components; + +/// +/// Handles data packages that are delimited by a specific sequence of bytes or characters. +/// +/// This class provides functionality for processing data packages that are separated by a defined +/// delimiter. The delimiter can be specified as a string with an optional encoding or as a byte array. +public class DelimiterDataPackageHandler : DataPackageHandlerBase +{ + private readonly ReadOnlyMemory _delimiter; + + /// + /// Initializes a new instance of the class with the specified delimiter + /// and optional encoding. + /// + /// The string delimiter used to separate data packages. This value cannot be null or empty. + /// The character encoding used to convert the delimiter to bytes. If null, is used as + /// the default. + /// Thrown if is null or empty. + public DelimiterDataPackageHandler(string delimiter, Encoding? encoding = null) + { + if (string.IsNullOrEmpty(delimiter)) + { + throw new ArgumentNullException(nameof(delimiter), "Delimiter cannot be null or empty."); + } + + encoding ??= Encoding.UTF8; + _delimiter = encoding.GetBytes(delimiter); + } + + /// + /// Initializes a new instance of the class with the specified delimiters. + /// + /// An array of bytes representing the delimiters used to parse data packages. Cannot be . + /// Thrown if is . + public DelimiterDataPackageHandler(byte[] delimiter) + { + _delimiter = delimiter ?? throw new ArgumentNullException(nameof(delimiter), "Delimiter cannot be null."); + } + + /// + /// + /// + /// + /// + /// + public override async ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default) + { + data = ConcatBuffer(data); + + while (data.Length > 0) + { + var index = data.Span.IndexOfAny(_delimiter.Span); + var segment = index == -1 ? data : data[..index]; + var length = segment.Length + _delimiter.Length; + using var buffer = MemoryPool.Shared.Rent(length); + segment.CopyTo(buffer.Memory); + + if (index != -1) + { + SlicePackage(data, index + _delimiter.Length); + + _delimiter.CopyTo(buffer.Memory[index..]); + if (ReceivedCallBack != null) + { + await ReceivedCallBack(buffer.Memory[..length].ToArray()); + } + + data = data[(index + _delimiter.Length)..]; + } + else + { + SlicePackage(data, 0); + break; + } + } + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/FixLengthDataPackageHandler.cs b/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/FixLengthDataPackageHandler.cs new file mode 100644 index 00000000..21038a8e --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/FixLengthDataPackageHandler.cs @@ -0,0 +1,53 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +/// +/// Handles fixed-length data packages by processing incoming data of a specified length. +/// +/// This class is designed to handle data packages with a fixed length, as specified during +/// initialization. It extends and overrides its behavior to process fixed-length +/// data. +/// The data package total data length. +public class FixLengthDataPackageHandler(int length) : DataPackageHandlerBase +{ + private readonly Memory _data = new byte[length]; + + private int _receivedLength; + + /// + /// + /// + /// + /// + /// + public override async ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default) + { + while (data.Length > 0) + { + // 拷贝数据 + var len = length - _receivedLength; + var segment = data.Length > len ? data[..len] : data; + segment.CopyTo(_data[_receivedLength..]); + + // 更新数据 + data = data[segment.Length..]; + + // 更新已接收长度 + _receivedLength += segment.Length; + + // 如果已接收长度等于总长度则触发回调 + if (_receivedLength == length) + { + // 重置已接收长度 + _receivedLength = 0; + if (ReceivedCallBack != null) + { + await ReceivedCallBack(_data); + } + } + } + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/IDataPackageHandler.cs b/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/IDataPackageHandler.cs new file mode 100644 index 00000000..4b443807 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/IDataPackageHandler.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +/// +/// Defines an interface for adapting data packages to and from a TCP socket connection. +/// +/// Implementations of this interface are responsible for converting raw data received from a TCP socket +/// into structured data packages and vice versa. This allows for custom serialization and deserialization logic +/// tailored to specific application protocols. +public interface IDataPackageHandler +{ + /// + /// Gets or sets the callback function to be invoked when data is received asynchronously. + /// + Func, ValueTask>? ReceivedCallBack { get; set; } + + /// + /// Asynchronously receives data and processes it. + /// + /// The method is designed for asynchronous operations and may be used in scenarios where + /// efficient handling of data streams is required. Ensure that the parameter contains valid + /// data for processing, and handle potential cancellation using the . + /// The data to be received, represented as a read-only memory block of bytes. + /// A cancellation token that can be used to cancel the operation. Defaults to if not + /// provided. + /// A containing if the data was successfully received and + /// processed; otherwise, . + ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default); +} diff --git a/src/extensions/BootstrapBlazor.Socket/Extensions/SocketDataPropertyExtensions.cs b/src/extensions/BootstrapBlazor.Socket/Extensions/SocketDataPropertyExtensions.cs new file mode 100644 index 00000000..77d57b4f --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/Extensions/SocketDataPropertyExtensions.cs @@ -0,0 +1,105 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +static class SocketDataPropertyExtensions +{ + public static ISocketDataPropertyConverter? GetConverter(this SocketDataPropertyConverterAttribute attribute) + { + return attribute.GetConverterByType() ?? attribute.GetDefaultConverter(); + } + + private static ISocketDataPropertyConverter? GetConverterByType(this SocketDataPropertyConverterAttribute attribute) + { + ISocketDataPropertyConverter? converter = null; + var converterType = attribute.ConverterType; + if (converterType != null) + { + var converterParameters = attribute.ConverterParameters; + var c = Activator.CreateInstance(converterType, converterParameters); + if(c is ISocketDataPropertyConverter v) + { + converter = v; + } + } + return converter; + } + + private static ISocketDataPropertyConverter? GetDefaultConverter(this SocketDataPropertyConverterAttribute attribute) + { + ISocketDataPropertyConverter? converter = null; + var type = attribute.Type; + if (type != null) + { + if (type == typeof(byte[])) + { + converter = new SocketDataByteArrayConverter(); + } + else if (type == typeof(string)) + { + converter = new SocketDataStringConverter(attribute.EncodingName); + } + else if (type.IsEnum) + { + converter = new SocketDataEnumConverter(attribute.Type); + } + else if (type == typeof(bool)) + { + converter = new SocketDataBoolConverter(); + } + else if (type == typeof(short)) + { + converter = new SocketDataInt16BigEndianConverter(); + } + else if (type == typeof(int)) + { + converter = new SocketDataInt32BigEndianConverter(); + } + else if (type == typeof(long)) + { + converter = new SocketDataInt64BigEndianConverter(); + } + else if (type == typeof(float)) + { + converter = new SocketDataSingleBigEndianConverter(); + } + else if (type == typeof(double)) + { + converter = new SocketDataDoubleBigEndianConverter(); + } + else if (type == typeof(ushort)) + { + converter = new SocketDataUInt16BigEndianConverter(); + } + else if (type == typeof(uint)) + { + converter = new SocketDataUInt32BigEndianConverter(); + } + else if (type == typeof(ulong)) + { + converter = new SocketDataUInt64BigEndianConverter(); + } + } + return converter; + } + + public static object? ConvertTo(this SocketDataPropertyConverterAttribute attribute, ReadOnlyMemory data) + { + object? ret = null; + var start = attribute.Offset; + var length = attribute.Length; + + if (data.Length >= start + length) + { + var buffer = data.Slice(start, length); + var converter = attribute.GetConverter(); + if (converter != null) + { + ret = converter.Convert(buffer); + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/ISocketDataPropertyConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/ISocketDataPropertyConverter.cs new file mode 100644 index 00000000..1cf8675c --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/ISocketDataPropertyConverter.cs @@ -0,0 +1,18 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +/// +/// Socket 数据转换器接口 +/// +public interface ISocketDataPropertyConverter +{ + /// + /// 将数据转换为指定类型的对象 + /// + /// + /// + object? Convert(ReadOnlyMemory data); +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataBoolConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataBoolConverter.cs new file mode 100644 index 00000000..c2ee5dd7 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataBoolConverter.cs @@ -0,0 +1,25 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 bool 数据转换器 +/// +public class SocketDataBoolConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + var ret = false; + if (data.Length == 1) + { + ret = data.Span[0] != 0x00; + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataByteArrayConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataByteArrayConverter.cs new file mode 100644 index 00000000..4d29893c --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataByteArrayConverter.cs @@ -0,0 +1,20 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 byte[] 数组转换器 +/// +public class SocketDataByteArrayConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + return data.ToArray(); + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataDoubleBigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataDoubleBigEndianConverter.cs new file mode 100644 index 00000000..81a6ad02 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataDoubleBigEndianConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 double 数据大端转换器 +/// +public class SocketDataDoubleBigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + double ret = 0; + if (data.Length <= 8) + { + Span paddedSpan = stackalloc byte[8]; + data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); + if (BinaryPrimitives.TryReadDoubleBigEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataDoubleLittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataDoubleLittleEndianConverter.cs new file mode 100644 index 00000000..22f9cbf0 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataDoubleLittleEndianConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 double 数据小端转换器 +/// +public class SocketDataDoubleLittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + double ret = 0; + if (data.Length <= 8) + { + Span paddedSpan = stackalloc byte[8]; + data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); + if (BinaryPrimitives.TryReadDoubleLittleEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataEnumConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataEnumConverter.cs new file mode 100644 index 00000000..990395e9 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataEnumConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 Enum 数据转换器 +/// +public class SocketDataEnumConverter(Type? type) : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + object? ret = null; + if (type != null) + { + if (data.Length == 1) + { + var v = data.Span[0]; + if (Enum.TryParse(type, v.ToString(), out var enumValue)) + { + ret = enumValue; + } + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt16BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt16BigEndianConverter.cs new file mode 100644 index 00000000..084df04f --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt16BigEndianConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 short 数据大端转换器 +/// +public class SocketDataInt16BigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + short ret = 0; + if (data.Length <= 2) + { + Span paddedSpan = stackalloc byte[2]; + data.Span.CopyTo(paddedSpan[(2 - data.Length)..]); + if (BinaryPrimitives.TryReadInt16BigEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt16LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt16LittleEndianConverter.cs new file mode 100644 index 00000000..9a4c9212 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt16LittleEndianConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 short 数据小端转换器 +/// +public class SocketDataInt16LittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + short ret = 0; + if (data.Length <= 2) + { + Span paddedSpan = stackalloc byte[2]; + data.Span.CopyTo(paddedSpan[(2 - data.Length)..]); + if (BinaryPrimitives.TryReadInt16LittleEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt32BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt32BigEndianConverter.cs new file mode 100644 index 00000000..16bc58e2 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt32BigEndianConverter.cs @@ -0,0 +1,33 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 int 数据大端转换器 +/// +public class SocketDataInt32BigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + var ret = 0; + if (data.Length <= 4) + { + Span paddedSpan = stackalloc byte[4]; + data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); + + if (BinaryPrimitives.TryReadInt32BigEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt32LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt32LittleEndianConverter.cs new file mode 100644 index 00000000..28836dfc --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt32LittleEndianConverter.cs @@ -0,0 +1,33 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 int 数据小端转换器 +/// +public class SocketDataInt32LittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + var ret = 0; + if (data.Length <= 4) + { + Span paddedSpan = stackalloc byte[4]; + data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); + + if (BinaryPrimitives.TryReadInt32LittleEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt64BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt64BigEndianConverter.cs new file mode 100644 index 00000000..a6f2d11f --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt64BigEndianConverter.cs @@ -0,0 +1,33 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 long 数据大端转换器 +/// +public class SocketDataInt64BigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + long ret = 0; + if (data.Length <= 8) + { + Span paddedSpan = stackalloc byte[8]; + data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); + + if (BinaryPrimitives.TryReadInt64BigEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt64LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt64LittleEndianConverter.cs new file mode 100644 index 00000000..34d17007 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt64LittleEndianConverter.cs @@ -0,0 +1,33 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 long 数据小端转换器 +/// +public class SocketDataInt64LittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + long ret = 0; + if (data.Length <= 8) + { + Span paddedSpan = stackalloc byte[8]; + data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); + + if (BinaryPrimitives.TryReadInt64LittleEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataSingleBigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataSingleBigEndianConverter.cs new file mode 100644 index 00000000..d89ae241 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataSingleBigEndianConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 float 数据大端转换器 +/// +public class SocketDataSingleBigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + float ret = 0; + if (data.Length <= 4) + { + Span paddedSpan = stackalloc byte[4]; + data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); + if (BinaryPrimitives.TryReadSingleBigEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataSingleLittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataSingleLittleEndianConverter.cs new file mode 100644 index 00000000..ae0834ec --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataSingleLittleEndianConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 float 数据小端转换器 +/// +public class SocketDataSingleLittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + float ret = 0; + if (data.Length <= 4) + { + Span paddedSpan = stackalloc byte[4]; + data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); + if (BinaryPrimitives.TryReadSingleLittleEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataStringConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataStringConverter.cs new file mode 100644 index 00000000..2aa9b547 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataStringConverter.cs @@ -0,0 +1,23 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Text; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 string 数据转换器 +/// +public class SocketDataStringConverter(string? encodingName) : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + var encoding = string.IsNullOrEmpty(encodingName) ? Encoding.UTF8 : Encoding.GetEncoding(encodingName); + return encoding.GetString(data.Span); + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt16BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt16BigEndianConverter.cs new file mode 100644 index 00000000..dedce80a --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt16BigEndianConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 ushort 数据大端转换器 +/// +public class SocketDataUInt16BigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + ushort ret = 0; + if (data.Length <= 2) + { + Span paddedSpan = stackalloc byte[2]; + data.Span.CopyTo(paddedSpan[(2 - data.Length)..]); + if (BinaryPrimitives.TryReadUInt16BigEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt16LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt16LittleEndianConverter.cs new file mode 100644 index 00000000..b4344f64 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt16LittleEndianConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 ushort 数据小端转换器 +/// +public class SocketDataUInt16LittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + ushort ret = 0; + if (data.Length <= 2) + { + Span paddedSpan = stackalloc byte[2]; + data.Span.CopyTo(paddedSpan[(2 - data.Length)..]); + if (BinaryPrimitives.TryReadUInt16LittleEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt32BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt32BigEndianConverter.cs new file mode 100644 index 00000000..cd8829f0 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt32BigEndianConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 uint 数据大端转换器 +/// +public class SocketDataUInt32BigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + uint ret = 0; + if (data.Length <= 4) + { + Span paddedSpan = stackalloc byte[4]; + data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); + if (BinaryPrimitives.TryReadUInt32BigEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt32LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt32LittleEndianConverter.cs new file mode 100644 index 00000000..80e5e27b --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt32LittleEndianConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 uint 数据小端转换器 +/// +public class SocketDataUInt32LittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + uint ret = 0; + if (data.Length <= 4) + { + Span paddedSpan = stackalloc byte[4]; + data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); + if (BinaryPrimitives.TryReadUInt32LittleEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt64BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt64BigEndianConverter.cs new file mode 100644 index 00000000..e85609d9 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt64BigEndianConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 ulong 数据大端转换器 +/// +public class SocketDataUInt64BigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + ulong ret = 0; + if (data.Length <= 8) + { + Span paddedSpan = stackalloc byte[8]; + data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); + if (BinaryPrimitives.TryReadUInt64BigEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt64LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt64LittleEndianConverter.cs new file mode 100644 index 00000000..0b546006 --- /dev/null +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt64LittleEndianConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 ulong 数据小端转换器 +/// +public class SocketDataUInt64LittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + ulong ret = 0; + if (data.Length <= 8) + { + Span paddedSpan = stackalloc byte[8]; + data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); + if (BinaryPrimitives.TryReadUInt64LittleEndian(paddedSpan, out var v)) + { + ret = v; + } + } + return ret; + } +} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj b/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj new file mode 100644 index 00000000..6d29677a --- /dev/null +++ b/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj @@ -0,0 +1,41 @@ + + + + 9.0.0 + + + + BootstrapBlazor Socket + BootstrapBlazor extensions of TcpSocket + + + + 8.0.* + 9.0.* + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClient.cs b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClient.cs new file mode 100644 index 00000000..ce6fe1d0 --- /dev/null +++ b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClient.cs @@ -0,0 +1,440 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System.Buffers; +using System.Net; +using System.Runtime.Versioning; + +namespace BootstrapBlazor.Components; + +[UnsupportedOSPlatform("browser")] +class DefaultTcpSocketClient(TcpSocketClientOptions options) : IServiceProvider, ITcpSocketClient +{ + /// + /// Gets or sets the socket client provider used for managing socket connections. + /// + private ITcpSocketClientProvider? SocketClientProvider { get; set; } + + /// + /// Gets or sets the logger instance used for logging messages and events. + /// + private ILogger? Logger { get; set; } + + /// + /// Gets or sets the service provider used to resolve dependencies. + /// + [NotNull] + public IServiceProvider? ServiceProvider { get; set; } + + /// + /// + /// + public TcpSocketClientOptions Options => options; + + /// + /// + /// + public bool IsConnected => SocketClientProvider?.IsConnected ?? false; + + /// + /// + /// + public IPEndPoint LocalEndPoint => SocketClientProvider?.LocalEndPoint ?? new IPEndPoint(IPAddress.Any, 0); + + /// + /// + /// + public Func, ValueTask>? ReceivedCallBack { get; set; } + + /// + /// + /// + public Func? OnConnecting { get; set; } + + /// + /// + /// + public Func? OnConnected { get; set; } + + private IPEndPoint? _remoteEndPoint; + private IPEndPoint? _localEndPoint; + private CancellationTokenSource? _receiveCancellationTokenSource; + private CancellationTokenSource? _autoConnectTokenSource; + + private readonly SemaphoreSlim _semaphoreSlim = new(1, 1); + + /// + /// + /// + /// + /// + /// + public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) + { + if (IsConnected) + { + return true; + } + + var connectionToken = GenerateConnectionToken(token); + try + { + await _semaphoreSlim.WaitAsync(connectionToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // 如果信号量等待被取消,则直接返回 IsConnected + // 不管是超时还是被取消,都不需要重连,肯定有其他线程在连接中 + return IsConnected; + } + + if (IsConnected) + { + _semaphoreSlim.Release(); + return true; + } + + var reconnect = true; + var ret = false; + SocketClientProvider = ServiceProvider?.GetRequiredService() + ?? throw new InvalidOperationException("SocketClientProvider is not registered in the service provider."); + + try + { + if (OnConnecting != null) + { + await OnConnecting(); + } + ret = await ConnectCoreAsync(SocketClientProvider, endPoint, connectionToken); + if (OnConnected != null) + { + await OnConnected(); + } + } + catch (OperationCanceledException ex) + { + if (token.IsCancellationRequested) + { + Log(LogLevel.Warning, ex, $"TCP Socket connect operation was canceled from {LocalEndPoint} to {endPoint}"); + reconnect = false; + } + else + { + Log(LogLevel.Warning, ex, $"TCP Socket connect operation timed out from {LocalEndPoint} to {endPoint}"); + } + } + catch (Exception ex) + { + Log(LogLevel.Error, ex, $"TCP Socket connection failed from {LocalEndPoint} to {endPoint}"); + } + + // 释放信号量 + _semaphoreSlim.Release(); + + if (reconnect) + { + _autoConnectTokenSource = new(); + + if (!ret) + { + Reconnect(); + } + } + return ret; + } + + private void Reconnect() + { + if (_autoConnectTokenSource != null && options.IsAutoReconnect && _remoteEndPoint != null) + { + Task.Run(async () => + { + try + { + await Task.Delay(options.ReconnectInterval, _autoConnectTokenSource.Token).ConfigureAwait(false); + await ConnectAsync(_remoteEndPoint, _autoConnectTokenSource.Token).ConfigureAwait(false); + } + catch { } + }, CancellationToken.None).ConfigureAwait(false); + } + } + + private async ValueTask ConnectCoreAsync(ITcpSocketClientProvider provider, IPEndPoint endPoint, CancellationToken token) + { + // 释放资源 + await CloseCoreAsync(); + + // 创建新的 TcpClient 实例 + provider.LocalEndPoint = Options.LocalEndPoint; + + _localEndPoint = Options.LocalEndPoint; + _remoteEndPoint = endPoint; + + var ret = await provider.ConnectAsync(endPoint, token); + + if (ret) + { + _localEndPoint = provider.LocalEndPoint; + + if (options.IsAutoReceive) + { + _ = Task.Run(AutoReceiveAsync, CancellationToken.None).ConfigureAwait(false); + } + } + return ret; + } + + private CancellationToken GenerateConnectionToken(CancellationToken token) + { + var connectionToken = token; + if (Options.ConnectTimeout > 0) + { + // 设置连接超时时间 + var connectTokenSource = new CancellationTokenSource(options.ConnectTimeout); + connectionToken = CancellationTokenSource.CreateLinkedTokenSource(token, connectTokenSource.Token).Token; + } + return connectionToken; + } + + /// + /// + /// + /// + /// + /// + public async ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) + { + if (SocketClientProvider is not { IsConnected: true }) + { + throw new InvalidOperationException($"TCP Socket is not connected {LocalEndPoint}"); + } + + var ret = false; + var reconnect = true; + try + { + var sendToken = token; + if (options.SendTimeout > 0) + { + // 设置发送超时时间 + var sendTokenSource = new CancellationTokenSource(options.SendTimeout); + sendToken = CancellationTokenSource.CreateLinkedTokenSource(token, sendTokenSource.Token).Token; + } + ret = await SocketClientProvider.SendAsync(data, sendToken); + } + catch (OperationCanceledException ex) + { + if (token.IsCancellationRequested) + { + reconnect = false; + Log(LogLevel.Warning, ex, $"TCP Socket send operation was canceled from {_localEndPoint} to {_remoteEndPoint}"); + } + else + { + Log(LogLevel.Warning, ex, $"TCP Socket send operation timed out from {_localEndPoint} to {_remoteEndPoint}"); + } + } + catch (Exception ex) + { + Log(LogLevel.Error, ex, $"TCP Socket send failed from {_localEndPoint} to {_remoteEndPoint}"); + } + + Log(LogLevel.Information, null, $"Sending data from {_localEndPoint} to {_remoteEndPoint}, Data Length: {data.Length} Data Content: {BitConverter.ToString(data.ToArray())} Result: {ret}"); + + if (!ret && reconnect) + { + // 如果发送失败并且需要重连则尝试重连 + Reconnect(); + } + return ret; + } + + /// + /// + /// + /// + /// + public async ValueTask> ReceiveAsync(CancellationToken token = default) + { + if (SocketClientProvider is not { IsConnected: true }) + { + throw new InvalidOperationException($"TCP Socket is not connected {LocalEndPoint}"); + } + + if (options.IsAutoReceive) + { + throw new InvalidOperationException("Cannot call ReceiveAsync when IsAutoReceive is true. Use the auto-receive mechanism instead."); + } + + using var block = MemoryPool.Shared.Rent(options.ReceiveBufferSize); + var buffer = block.Memory; + var len = await ReceiveCoreAsync(SocketClientProvider, buffer, token); + if (len == 0) + { + Reconnect(); + } + return buffer[..len]; + } + + private async ValueTask AutoReceiveAsync() + { + // 自动接收方法 + _receiveCancellationTokenSource ??= new(); + while (_receiveCancellationTokenSource is { IsCancellationRequested: false }) + { + if (SocketClientProvider is not { IsConnected: true }) + { + throw new InvalidOperationException($"TCP Socket is not connected {LocalEndPoint}"); + } + + using var block = MemoryPool.Shared.Rent(options.ReceiveBufferSize); + var buffer = block.Memory; + var len = await ReceiveCoreAsync(SocketClientProvider, buffer, _receiveCancellationTokenSource.Token); + if (len == 0) + { + // 远端关闭或者 DisposeAsync 方法被调用时退出 + break; + } + } + + Reconnect(); + } + + private async ValueTask ReceiveCoreAsync(ITcpSocketClientProvider client, Memory buffer, CancellationToken token) + { + var reconnect = true; + var len = 0; + try + { + var receiveToken = token; + if (options.ReceiveTimeout > 0) + { + // 设置接收超时时间 + var receiveTokenSource = new CancellationTokenSource(options.ReceiveTimeout); + receiveToken = CancellationTokenSource.CreateLinkedTokenSource(receiveToken, receiveTokenSource.Token).Token; + } + + len = await client.ReceiveAsync(buffer, receiveToken); + if (len == 0) + { + // 远端主机关闭链路 + Log(LogLevel.Information, null, $"TCP Socket {_localEndPoint} received 0 data closed by {_remoteEndPoint}"); + buffer = Memory.Empty; + } + else + { + buffer = buffer[..len]; + } + + if (ReceivedCallBack != null) + { + // 如果订阅回调则触发回调 + await ReceivedCallBack(buffer); + } + } + catch (OperationCanceledException ex) + { + if (token.IsCancellationRequested) + { + Log(LogLevel.Warning, ex, $"TCP Socket receive operation canceled from {_localEndPoint} to {_remoteEndPoint}"); + reconnect = false; + } + else + { + Log(LogLevel.Warning, ex, $"TCP Socket receive operation timed out from {_localEndPoint} to {_remoteEndPoint}"); + } + } + catch (Exception ex) + { + Log(LogLevel.Error, ex, $"TCP Socket receive failed from {_localEndPoint} to {_remoteEndPoint}"); + } + + Log(LogLevel.Information, null, $"Receiving data from {_localEndPoint} to {_remoteEndPoint}, Data Length: {len} Data Content: {BitConverter.ToString(buffer.ToArray())}"); + + if (len == 0 && reconnect) + { + // 如果接收数据长度为 0 并且需要重连则尝试重连 + Reconnect(); + } + return len; + } + + /// + /// Logs a message with the specified log level, exception, and additional context. + /// + private void Log(LogLevel logLevel, Exception? ex, string? message) + { + if (options.EnableLog) + { + Logger ??= ServiceProvider?.GetRequiredService>(); + Logger?.Log(logLevel, ex, "{Message}", message); + } + } + + /// + /// + /// + public async ValueTask CloseAsync() + { + // 取消重连任务 + if (_autoConnectTokenSource != null) + { + _autoConnectTokenSource.Cancel(); + _autoConnectTokenSource.Dispose(); + _autoConnectTokenSource = null; + } + + await CloseCoreAsync(); + } + + private async ValueTask CloseCoreAsync() + { + // 取消接收数据的任务 + if (_receiveCancellationTokenSource != null) + { + _receiveCancellationTokenSource.Cancel(); + _receiveCancellationTokenSource.Dispose(); + _receiveCancellationTokenSource = null; + } + + if (SocketClientProvider != null) + { + await SocketClientProvider.CloseAsync(); + } + } + + /// + /// + /// + /// + /// + public object? GetService(Type serviceType) => ServiceProvider.GetService(serviceType); + + /// + /// Releases the resources used by the current instance of the class. + /// + /// This method is called to free both managed and unmanaged resources. If the parameter is , the method releases managed resources in addition to + /// unmanaged resources. Override this method in a derived class to provide custom cleanup logic. + /// to release both managed and unmanaged resources; to release only + /// unmanaged resources. + private async ValueTask DisposeAsync(bool disposing) + { + if (disposing) + { + await CloseAsync(); + } + } + + /// + /// + /// + public async ValueTask DisposeAsync() + { + await DisposeAsync(true); + GC.SuppressFinalize(this); + } +} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClientProvider.cs b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClientProvider.cs new file mode 100644 index 00000000..1c3bb319 --- /dev/null +++ b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClientProvider.cs @@ -0,0 +1,92 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Net; +using System.Net.Sockets; +using System.Runtime.Versioning; + +namespace BootstrapBlazor.Components; + +/// +/// TcpSocket 客户端默认实现 +/// +[UnsupportedOSPlatform("browser")] +class DefaultTcpSocketClientProvider : ITcpSocketClientProvider +{ + private TcpClient? _client; + + /// + /// + /// + public bool IsConnected => _client?.Connected ?? false; + + /// + /// + /// + public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); + + /// + /// + /// + public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) + { + _client = new TcpClient(LocalEndPoint); + await _client.ConnectAsync(endPoint, token).ConfigureAwait(false); + if (_client.Connected) + { + if (_client.Client.LocalEndPoint is IPEndPoint localEndPoint) + { + LocalEndPoint = localEndPoint; + } + } + return _client.Connected; + } + + /// + /// + /// + public async ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) + { + var ret = false; + if (_client != null) + { + var stream = _client.GetStream(); + await stream.WriteAsync(data, token).ConfigureAwait(false); + ret = true; + } + return ret; + } + + /// + /// + /// + public async ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) + { + var len = 0; + if (_client is { Connected: true }) + { + var stream = _client.GetStream(); + len = await stream.ReadAsync(buffer, token).ConfigureAwait(false); + + if (len == 0) + { + _client.Close(); + } + } + return len; + } + + /// + /// + /// + public ValueTask CloseAsync() + { + if (_client != null) + { + _client.Close(); + _client = null; + } + return ValueTask.CompletedTask; + } +} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketFactory.cs b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketFactory.cs new file mode 100644 index 00000000..62b62817 --- /dev/null +++ b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketFactory.cs @@ -0,0 +1,60 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Collections.Concurrent; +using System.Runtime.Versioning; + +namespace BootstrapBlazor.Components; + +[UnsupportedOSPlatform("browser")] +sealed class DefaultTcpSocketFactory(IServiceProvider provider) : ITcpSocketFactory +{ + private readonly ConcurrentDictionary _pool = new(); + + public ITcpSocketClient GetOrCreate(string name, Action valueFactory) + { + return _pool.GetOrAdd(name, key => + { + var options = new TcpSocketClientOptions(); + valueFactory(options); + var client = new DefaultTcpSocketClient(options) + { + ServiceProvider = provider, + }; + return client; + }); + } + + public ITcpSocketClient? Remove(string name) + { + ITcpSocketClient? client = null; + if (_pool.TryRemove(name, out var c)) + { + client = c; + } + return client; + } + + private async ValueTask DisposeAsync(bool disposing) + { + if (disposing) + { + // 释放托管资源 + foreach (var socket in _pool.Values) + { + await socket.DisposeAsync(); + } + _pool.Clear(); + } + } + + /// + /// + /// + public async ValueTask DisposeAsync() + { + await DisposeAsync(true); + GC.SuppressFinalize(this); + } +} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs new file mode 100644 index 00000000..ee61009a --- /dev/null +++ b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs @@ -0,0 +1,192 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System.Reflection; +using System.Runtime.Versioning; +using System.Text; + +namespace BootstrapBlazor.Components; + +/// +/// 扩展方法类 +/// +[UnsupportedOSPlatform("browser")] +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 + /// 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. + /// The character encoding to use for converting the string content to bytes. If , UTF-8 + /// encoding is used by default. + /// A to observe while waiting for the operation to complete. + /// A that represents the asynchronous operation. The result is if the content was sent successfully; otherwise, . + public static ValueTask SendAsync(this ITcpSocketClient client, string content, Encoding? encoding = null, CancellationToken token = default) + { + var buffer = encoding?.GetBytes(content) ?? Encoding.UTF8.GetBytes(content); + return client.SendAsync(buffer, token); + } + + /// + /// Establishes an asynchronous connection to the specified host and port. + /// + /// The TCP socket client to which the content will be sent. Cannot be . + /// The hostname or IP address of the server to connect to. Cannot be null or empty. + /// The port number on the server to connect to. Must be a valid port number between 0 and 65535. + /// An optional to cancel the connection attempt. Defaults to if not provided. + /// A task that represents the asynchronous operation. The task result is if the connection + /// is successfully established; otherwise, . + public static ValueTask ConnectAsync(this ITcpSocketClient client, string ipString, int port, CancellationToken token = default) + { + var endPoint = Utility.ConvertToIpEndPoint(ipString, port); + return client.ConnectAsync(endPoint, token); + } + + /// + /// 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) + { + // 设置 ITcpSocketClient 的回调函数 + client.ReceivedCallBack = async buffer => + { + // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调 + await adapter.HandlerAsync(buffer); + }; + + // 设置 DataPackageAdapter 的回调函数 + adapter.ReceivedCallBack = buffer => callback(buffer); + } + + /// + /// Configures the specified to use a data package adapter and a callback function + /// for processing received data. + /// + /// This method sets up the to process incoming data using the + /// specified and . The is called with the converted entity whenever data is received. + /// The type of the entity that the data will be converted to. + /// The TCP socket client to configure. + /// 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, ISocketDataConverter socketDataConverter, Func callback) + { + // 设置 ITcpSocketClient 的回调函数 + client.ReceivedCallBack = async buffer => + { + // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调 + await adapter.HandlerAsync(buffer); + }; + + // 设置 DataPackageAdapter 的回调函数 + adapter.ReceivedCallBack = async buffer => + { + TEntity? ret = default; + if (socketDataConverter.TryConvertTo(buffer, out var t)) + { + ret = t; + } + await callback(ret); + }; + } + + /// + /// Configures the specified to use a custom data package adapter and callback + /// function. + /// + /// This method sets up the to use the specified for handling incoming data. If the type is decorated with a , the associated converter is used to transform the data before invoking + /// the . The callback is called with the converted entity or if + /// conversion fails. + /// The type of entity that the data package adapter will handle. + /// 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) + { + // 设置 ITcpSocketClient 的回调函数 + client.ReceivedCallBack = async buffer => + { + // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调 + await adapter.HandlerAsync(buffer); + }; + + ISocketDataConverter? converter = null; + + var type = typeof(TEntity); + var converterType = type.GetCustomAttribute(); + if (converterType is { Type: not null }) + { + // 如果类型上有 SocketDataTypeConverterAttribute 特性则使用特性中指定的转换器 + if (Activator.CreateInstance(converterType.Type) is ISocketDataConverter socketDataConverter) + { + converter = socketDataConverter; + } + } + else + { + // 如果没有特性则从 ITcpSocketClient 中的服务容器获取转换器 + converter = client.GetSocketDataConverter(); + } + + if (converter == null) + { + // 设置正常回调 + adapter.ReceivedCallBack = async buffer => await callback(default); + } + else + { + // 设置转化器 + adapter.SetDataAdapterCallback(converter, callback); + } + } + + private static void SetDataAdapterCallback(this IDataPackageAdapter adapter, ISocketDataConverter converter, Func callback) + { + adapter.ReceivedCallBack = async buffer => + { + TEntity? ret = default; + if (converter.TryConvertTo(buffer, out var t)) + { + ret = t; + } + await callback(ret); + }; + } + + private static ISocketDataConverter? GetSocketDataConverter(this ITcpSocketClient client) + { + ISocketDataConverter? converter = null; + if (client is IServiceProvider provider) + { + var converters = provider.GetRequiredService>().Value; + if (converters.TryGetTypeConverter(out var v)) + { + converter = v; + } + } + return converter; + } +} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs new file mode 100644 index 00000000..baddeca0 --- /dev/null +++ b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs @@ -0,0 +1,61 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using System.Runtime.Versioning; + +namespace BootstrapBlazor.Components; + +/// +/// TcpSocket 扩展方法 +/// +[UnsupportedOSPlatform("browser")] +public static class TcpSocketExtensions +{ + /// + /// 增加 ITcpSocketFactory 服务 + /// + /// + /// + public static IServiceCollection AddBootstrapBlazorTcpSocketFactory(this IServiceCollection services) + { + // 添加 ITcpSocketFactory 服务 + services.AddSingleton(); + + // 增加 ISocketClientProvider 服务 + services.TryAddTransient(); + + // 增加转换器集合配置服务 + services.AddSocketDataConverters(); + return services; + } + + /// + /// 增加 Socket 数据转换器集合配置项服务 + /// + /// + /// + static IServiceCollection AddSocketDataConverters(this IServiceCollection services) + { + services.AddOptions(); + services.TryAddSingleton, ConfigurationChangeTokenSource>(); + services.TryAddSingleton, ConfigureOptions>(); + + return services; + } + + /// + /// 配置第三方数据模型与 数据转换器集合配置扩展方法 + /// + /// + /// + /// + public static IServiceCollection ConfigureSocketDataConverters(this IServiceCollection services, Action configureOptions) + { + services.Configure(configureOptions); + return services; + } +} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/Utility.cs b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/Utility.cs new file mode 100644 index 00000000..15730185 --- /dev/null +++ b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/Utility.cs @@ -0,0 +1,74 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Net; +using System.Net.Sockets; +using System.Runtime.Versioning; + +namespace BootstrapBlazor.Components; + +/// +/// Utility 帮助类 +/// +public static class Utility +{ + /// + /// Converts a string representation of an IP address or hostname into an object. + /// + /// This method handles common special cases for IP address strings, such as "localhost" and + /// "any". For other inputs, it attempts to parse the string as an IP address using . If parsing fails, the method resolves the input as a + /// hostname. + /// A string containing the IP address or hostname to convert. Special values include: + /// "localhost" returns the loopback address (). "any" returns the wildcard address + /// (). For other values, the method attempts to parse the + /// string as an IP address or resolve it as a hostname. + /// An object representing the parsed or resolved IP address. If the input cannot be parsed + /// or resolved, the method returns a default IP address. + [UnsupportedOSPlatform("browser")] + public static IPAddress ConvertToIPAddress(string ipString) + { + if (string.IsNullOrEmpty(ipString)) + { + throw new ArgumentNullException(nameof(ipString), "IP address cannot be null or empty."); + } + + if (ipString.Equals("localhost", StringComparison.OrdinalIgnoreCase)) + { + return IPAddress.Loopback; + } + if (ipString.Equals("any", StringComparison.OrdinalIgnoreCase)) + { + return IPAddress.Any; + } + + return IPAddress.TryParse(ipString, out var ip) ? ip : IPAddressByHostName; + } + + [ExcludeFromCodeCoverage] + + [UnsupportedOSPlatform("browser")] + private static IPAddress IPAddressByHostName => Dns.GetHostAddresses(Dns.GetHostName(), AddressFamily.InterNetwork).FirstOrDefault() ?? IPAddress.Any; + + /// + /// Converts a string representation of an IP address and a port number into an instance. + /// + /// This method is not supported on browser platforms. + /// The string representation of the IP address. Must be a valid IPv4 or IPv6 address. + /// The port number associated with the endpoint. Must be between 0 and 65535. + /// An representing the specified IP address and port. + /// Thrown if is less than 0 or greater than 65535. + [UnsupportedOSPlatform("browser")] + public static IPEndPoint ConvertToIpEndPoint(string ipString, int port) + { + if (port < 0 || port > 65535) + { + throw new ArgumentOutOfRangeException(nameof(port), "Port must be between 0 and 65535."); + } + + var address = ConvertToIPAddress(ipString); + return new IPEndPoint(address, port); + } +} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClient.cs b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClient.cs new file mode 100644 index 00000000..17e1a6c6 --- /dev/null +++ b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClient.cs @@ -0,0 +1,92 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Net; + +namespace BootstrapBlazor.Components; + +/// +/// Represents a TCP socket for network communication. +/// +public interface ITcpSocketClient : IAsyncDisposable +{ + /// + /// Gets a value indicating whether the system is currently connected. Default is false. + /// + bool IsConnected { get; } + + /// + /// Gets or sets the configuration options for the socket client. + /// + TcpSocketClientOptions Options { get; } + + /// + /// Gets the local network endpoint that the socket is bound to. + /// + /// This property provides information about the local endpoint of the socket, which is typically + /// used to identify the local address and port being used for communication. If the socket is not bound to a + /// specific local endpoint, this property may return . + IPEndPoint LocalEndPoint { get; } + + /// + /// Gets or sets the callback function to handle received data. + /// + /// The callback function should be designed to handle the received data efficiently and + /// asynchronously. Ensure that the implementation does not block or perform long-running operations, as this may + /// impact performance. + Func, ValueTask>? ReceivedCallBack { get; set; } + + /// + /// Gets or sets the callback function that is invoked when a connection attempt is initiated. + /// + Func? OnConnecting { get; set; } + + /// + /// Gets or sets the delegate to be invoked when a connection is successfully established. + /// + Func? OnConnected { get; set; } + + /// + /// Establishes an asynchronous connection to the specified endpoint. + /// + /// This method attempts to establish a connection to the specified endpoint. If the connection + /// cannot be established, the method returns rather than throwing an exception. + /// The representing the remote endpoint to connect to. Cannot be null. + /// A that can be used to cancel the connection attempt. Defaults to if not provided. + /// A task that represents the asynchronous operation. The task result is if the connection + /// is successfully established; otherwise, . + ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default); + + /// + /// Sends the specified data asynchronously to the target endpoint. + /// + /// This method performs a non-blocking operation to send data. If the operation is canceled via + /// the , the task will complete with a canceled state. Ensure the connection is properly + /// initialized before calling this method. + /// The byte array containing the data to be sent. Cannot be null or empty. + /// An optional to observe while waiting for the operation to complete. + /// A task that represents the asynchronous operation. The task result is if the data was + /// sent successfully; otherwise, . + ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default); + + /// + /// Asynchronously receives a block of data from the underlying source. + /// + /// This method is non-blocking and completes when data is available or the operation is + /// canceled. If the operation is canceled, the returned task will be in a faulted state with a . + /// A cancellation token that can be used to cancel the operation. The default value is . + /// A containing a of bytes representing the received data. + /// The returned memory may be empty if no data is available. + ValueTask> ReceiveAsync(CancellationToken token = default); + + /// + /// Closes the current connection or resource, releasing any associated resources. + /// + /// Once the connection or resource is closed, it cannot be reopened. Ensure that all necessary + /// operations are completed before calling this method. This method is typically used to clean up resources when + /// they are no longer needed. + ValueTask CloseAsync(); +} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClientProvider.cs b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClientProvider.cs new file mode 100644 index 00000000..93d34814 --- /dev/null +++ b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClientProvider.cs @@ -0,0 +1,72 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Net; + +namespace BootstrapBlazor.Components; + +/// +/// Defines the contract for a socket client that provides asynchronous methods for connecting, sending, receiving, and +/// closing network connections. +/// +/// This interface is designed to facilitate network communication using sockets. It provides methods for +/// establishing connections, transmitting data, and receiving data asynchronously. Implementations of this interface +/// should ensure proper resource management, including closing connections and releasing resources when no longer +/// needed. +public interface ITcpSocketClientProvider +{ + /// + /// Gets a value indicating whether the connection is currently active. + /// + bool IsConnected { get; } + + /// + /// Gets the local network endpoint that the socket is bound to. + /// + IPEndPoint LocalEndPoint { get; set; } + + /// + /// Establishes an asynchronous connection to the specified endpoint. + /// + /// This method attempts to establish a connection to the specified endpoint. If the connection + /// fails, the method returns rather than throwing an exception. Ensure the endpoint is + /// valid and reachable before calling this method. + /// The representing the remote endpoint to connect to. + /// An optional to observe while waiting for the connection to complete. + /// A that represents the asynchronous operation. The result is if the connection was successfully established; otherwise, . + ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default); + + /// + /// Sends the specified data asynchronously to the connected endpoint. + /// + /// This method performs a non-blocking operation to send data. If the operation is canceled via + /// the , the returned task will not complete successfully. Ensure the connected endpoint + /// is ready to receive data before calling this method. + /// The data to send, represented as a read-only memory block of bytes. + /// An optional cancellation token that can be used to cancel the operation. + /// A representing the asynchronous operation. The result is if the data was sent successfully; otherwise, . + ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default); + + /// + /// Asynchronously receives data from a source and writes it into the specified buffer. + /// + /// This method does not guarantee that the buffer will be completely filled. The caller should + /// check the return value to determine the number of bytes received. + /// The memory buffer where the received data will be stored. Must be large enough to hold the incoming data. + /// A cancellation token that can be used to cancel the operation. Defaults to if not + /// provided. + /// A representing the asynchronous operation. The result is the number of bytes + /// successfully received and written into the buffer. Returns 0 if the end of the data stream is reached. + ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default); + + /// + /// Closes the current connection or resource, releasing any associated resources. + /// + /// Once the connection or resource is closed, it cannot be reopened. Ensure that all necessary + /// operations are completed before calling this method. This method is typically used to clean up resources when + /// they are no longer needed. + ValueTask CloseAsync(); +} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketFactory.cs b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketFactory.cs new file mode 100644 index 00000000..05c9ec9c --- /dev/null +++ b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketFactory.cs @@ -0,0 +1,29 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +/// +/// ITcpSocketFactory Interface +/// +public interface ITcpSocketFactory : IAsyncDisposable +{ + /// + /// Retrieves an existing TCP socket client by name or creates a new one using the specified configuration. + /// + /// The unique name of the TCP socket client to retrieve or create. Cannot be null or empty. + /// A delegate used to configure the for the new TCP socket client if it does not + /// already exist. This delegate is invoked only when a new client is created. + /// An instance of corresponding to the specified name. If the client already exists, + /// the existing instance is returned; otherwise, a new instance is created and returned. + ITcpSocketClient GetOrCreate(string name, Action valueFactory); + + /// + /// Removes the TCP socket client associated with the specified name. + /// + /// The name of the TCP socket client to remove. Cannot be or empty. + /// The removed instance if a client with the specified name exists; otherwise, . + ITcpSocketClient? Remove(string name); +} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/TcpSocketClientOptions.cs b/src/extensions/BootstrapBlazor.TcpSocket/TcpSocketClientOptions.cs new file mode 100644 index 00000000..f91b1025 --- /dev/null +++ b/src/extensions/BootstrapBlazor.TcpSocket/TcpSocketClientOptions.cs @@ -0,0 +1,67 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Net; + +namespace BootstrapBlazor.Components; + +/// +/// Represents configuration options for a socket client, including buffer sizes, timeouts, and endpoints. +/// +/// Use this class to configure various settings for a socket client, such as connection timeouts, +/// buffer sizes, and local or remote endpoints. These options allow fine-tuning of socket behavior to suit specific +/// networking scenarios. +public class TcpSocketClientOptions +{ + /// + /// Gets or sets the size, in bytes, of the receive buffer used by the connection. + /// + public int ReceiveBufferSize { get; set; } = 1024 * 64; + + /// + /// Gets or sets a value indicating whether automatic receiving data is enabled. Default is true. + /// + public bool IsAutoReceive { get; set; } = true; + + /// + /// Gets or sets the timeout duration, in milliseconds, for establishing a connection. + /// + public int ConnectTimeout { get; set; } + + /// + /// Gets or sets the duration, in milliseconds, to wait for a send operation to complete before timing out. + /// + /// If the send operation does not complete within the specified timeout period, an exception may + /// be thrown. + public int SendTimeout { get; set; } + + /// + /// Gets or sets the amount of time, in milliseconds, that the receiver will wait for a response before timing out. + /// + /// Use this property to configure the maximum wait time for receiving a response. Setting an + /// appropriate timeout can help prevent indefinite blocking in scenarios where responses may be delayed or + /// unavailable. + public int ReceiveTimeout { get; set; } + + /// + /// Gets or sets the local endpoint for the socket client. Default value is + /// + /// This property specifies the local network endpoint that the socket client will bind to when establishing a connection. + public IPEndPoint LocalEndPoint { get; set; } = new(IPAddress.Any, 0); + + /// + /// Gets or sets a value indicating whether logging is enabled. Default value is false. + /// + public bool EnableLog { get; set; } + + /// + /// Gets or sets a value indicating whether the system should automatically attempt to reconnect after a connection is lost. Default value is false. + /// + public bool IsAutoReconnect { get; set; } + + /// + /// Gets or sets the interval, in milliseconds, between reconnection attempts. Default value is 5000. + /// + public int ReconnectInterval { get; set; } = 5000; +} diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 5cfa559e..3cc85025 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -8,7 +8,7 @@ - net8.0 + net9.0 diff --git a/test/UniTestSvgIcon/AntDesign/filled.svg b/test/UnitTestSvgIcon/AntDesign/filled.svg similarity index 100% rename from test/UniTestSvgIcon/AntDesign/filled.svg rename to test/UnitTestSvgIcon/AntDesign/filled.svg diff --git a/test/UniTestSvgIcon/AntDesign/filled.zip b/test/UnitTestSvgIcon/AntDesign/filled.zip similarity index 100% rename from test/UniTestSvgIcon/AntDesign/filled.zip rename to test/UnitTestSvgIcon/AntDesign/filled.zip diff --git a/test/UniTestSvgIcon/AntDesign/outlined.svg b/test/UnitTestSvgIcon/AntDesign/outlined.svg similarity index 100% rename from test/UniTestSvgIcon/AntDesign/outlined.svg rename to test/UnitTestSvgIcon/AntDesign/outlined.svg diff --git a/test/UniTestSvgIcon/AntDesign/outlined.zip b/test/UnitTestSvgIcon/AntDesign/outlined.zip similarity index 100% rename from test/UniTestSvgIcon/AntDesign/outlined.zip rename to test/UnitTestSvgIcon/AntDesign/outlined.zip diff --git a/test/UniTestSvgIcon/AntDesign/twotone.svg b/test/UnitTestSvgIcon/AntDesign/twotone.svg similarity index 100% rename from test/UniTestSvgIcon/AntDesign/twotone.svg rename to test/UnitTestSvgIcon/AntDesign/twotone.svg diff --git a/test/UniTestSvgIcon/AntDesign/twotone.zip b/test/UnitTestSvgIcon/AntDesign/twotone.zip similarity index 100% rename from test/UniTestSvgIcon/AntDesign/twotone.zip rename to test/UnitTestSvgIcon/AntDesign/twotone.zip diff --git a/test/UniTestSvgIcon/Element/element.zip b/test/UnitTestSvgIcon/Element/element.zip similarity index 100% rename from test/UniTestSvgIcon/Element/element.zip rename to test/UnitTestSvgIcon/Element/element.zip diff --git a/test/UniTestSvgIcon/IconPark/download.zip b/test/UnitTestSvgIcon/IconPark/download.zip similarity index 100% rename from test/UniTestSvgIcon/IconPark/download.zip rename to test/UnitTestSvgIcon/IconPark/download.zip diff --git a/test/UniTestSvgIcon/OctIcon/octicons.zip b/test/UnitTestSvgIcon/OctIcon/octicons.zip similarity index 100% rename from test/UniTestSvgIcon/OctIcon/octicons.zip rename to test/UnitTestSvgIcon/OctIcon/octicons.zip diff --git a/test/UniTestSvgIcon/UnitTest.cs b/test/UnitTestSvgIcon/UnitTest.cs similarity index 100% rename from test/UniTestSvgIcon/UnitTest.cs rename to test/UnitTestSvgIcon/UnitTest.cs diff --git a/test/UniTestSvgIcon/UniTestSvgIcon.csproj b/test/UnitTestSvgIcon/UnitTestSvgIcon.csproj similarity index 100% rename from test/UniTestSvgIcon/UniTestSvgIcon.csproj rename to test/UnitTestSvgIcon/UnitTestSvgIcon.csproj diff --git a/test/UniTestSvgIcon/Univer/univer.zip b/test/UnitTestSvgIcon/Univer/univer.zip similarity index 100% rename from test/UniTestSvgIcon/Univer/univer.zip rename to test/UnitTestSvgIcon/Univer/univer.zip diff --git a/test/UnitTestTcpSocket/DefaultSocketClientProviderTest.cs b/test/UnitTestTcpSocket/DefaultSocketClientProviderTest.cs new file mode 100644 index 00000000..859dfd64 --- /dev/null +++ b/test/UnitTestTcpSocket/DefaultSocketClientProviderTest.cs @@ -0,0 +1,108 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Net; +using System.Net.Sockets; + +namespace UnitTestTcpSocket; + +public class DefaultSocketClientProviderTest +{ + [Fact] + public async Task DefaultSocketClient_Ok() + { + var sc = new ServiceCollection(); + sc.AddBootstrapBlazorTcpSocketFactory(); + var provider = sc.BuildServiceProvider(); + var clientProvider = provider.GetRequiredService(); + + // 未建立连接时 IsConnected 应为 false + Assert.False(clientProvider.IsConnected); + + // 未建立连接直接调用 ReceiveAsync 方法 + var buffer = new byte[1024]; + var len = await clientProvider.ReceiveAsync(buffer); + Assert.Equal(0, len); + } + + [Fact] + public async Task ReceiveAsync_Ok() + { + var port = 8100; + // 测试接收数据时服务器断开未连接的情况 + StartTcpServer(port); + + var sc = new ServiceCollection(); + sc.AddBootstrapBlazorTcpSocketFactory(); + var provider = sc.BuildServiceProvider(); + var factory = provider.GetRequiredService(); + var client = factory.GetOrCreate("provider", op => + { + op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0); + op.IsAutoReceive = false; + op.EnableLog = false; + }); + + await client.ConnectAsync("127.0.0.1", port); + Assert.True(client.IsConnected); + + var buffer = await client.ReceiveAsync(); + Assert.Equal(2, buffer.Length); + + await Task.Delay(50); + buffer = await client.ReceiveAsync(); + Assert.False(client.IsConnected); + } + + [Fact] + public void SocketClientOptions_Ok() + { + var options = new TcpSocketClientOptions + { + ReceiveBufferSize = 1024 * 64, + IsAutoReceive = true, + ConnectTimeout = 1000, + SendTimeout = 500, + ReceiveTimeout = 500, + LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0) + }; + Assert.Equal(1024 * 64, options.ReceiveBufferSize); + Assert.True(options.IsAutoReceive); + Assert.Equal(1000, options.ConnectTimeout); + Assert.Equal(500, options.SendTimeout); + Assert.Equal(500, options.ReceiveTimeout); + Assert.Equal(new IPEndPoint(IPAddress.Loopback, 0), options.LocalEndPoint); + } + + private static TcpListener StartTcpServer(int port) + { + var server = new TcpListener(IPAddress.Loopback, port); + server.Start(); + Task.Run(() => AcceptClientsAsync(server)); + return server; + } + + private static async Task AcceptClientsAsync(TcpListener server) + { + while (true) + { + var client = await server.AcceptTcpClientAsync(); + _ = Task.Run(async () => + { + using var stream = client.GetStream(); + while (true) + { + var buffer = new byte[1024]; + + // 模拟拆包发送第二段数据 + await stream.WriteAsync(new byte[] { 0x3, 0x4 }, CancellationToken.None); + + // 等待 20ms + await Task.Delay(20); + client.Close(); + } + }); + } + } +} diff --git a/test/UnitTestTcpSocket/Foo.cs b/test/UnitTestTcpSocket/Foo.cs new file mode 100644 index 00000000..e244b923 --- /dev/null +++ b/test/UnitTestTcpSocket/Foo.cs @@ -0,0 +1,94 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.ComponentModel.DataAnnotations; + +namespace UnitTestTcpSocket; + +/// +/// Demo示例数据 +/// Demo sample data +/// +public class Foo +{ + // 列头信息支持 Display DisplayName 两种标签 + + /// + /// 主键 + /// + [Key] + [Display(Name = "主键")] + public int Id { get; set; } + + /// + /// 姓名 + /// + [Required(ErrorMessage = "{0}不能为空")] + [Display(Name = "姓名")] + public string? Name { get; set; } + + /// + /// 日期 + /// + [Display(Name = "日期")] + public DateTime? DateTime { get; set; } + + /// + /// 地址 + /// + [Display(Name = "地址")] + [Required(ErrorMessage = "{0}不能为空")] + public string? Address { get; set; } + + /// + /// 数量 + /// + [Display(Name = "数量")] + [Required] + public int Count { get; set; } + + /// + /// 是/否 + /// + [Display(Name = "是/否")] + public bool Complete { get; set; } + + /// + /// 学历 + /// + [Required(ErrorMessage = "请选择学历")] + [Display(Name = "学历")] + public EnumEducation? Education { get; set; } + + /// + /// 爱好 + /// + [Required(ErrorMessage = "请选择一种{0}")] + [Display(Name = "爱好")] + public IEnumerable Hobby { get; set; } = new List(); + + /// + /// 只读列,模拟数据库计算列 + /// + [Display(Name = "只读列")] + public int ReadonlyColumn { get; init; } +} + +/// +/// +/// +public enum EnumEducation +{ + /// + /// + /// + [Display(Name = "小学")] + Primary, + + /// + /// + /// + [Display(Name = "中学")] + Middle +} diff --git a/test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs b/test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs new file mode 100644 index 00000000..7ad0b42c --- /dev/null +++ b/test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace UnitTestTcpSocket; + +public class SocketDataConverterCollectionsTest +{ + [Fact] + public void TryGetConverter_Ok() + { + var sc = new ServiceCollection(); + sc.ConfigureSocketDataConverters(options => + { + options.AddTypeConverter(); + options.AddPropertyConverter(entity => entity.Header, new SocketDataPropertyConverterAttribute() + { + Offset = 0, + Length = 5 + }); + options.AddPropertyConverter(entity => entity.Body, new SocketDataPropertyConverterAttribute() + { + Offset = 5, + Length = 2 + }); + + // 为提高代码覆盖率 重复添加转换器以后面的为准 + options.AddTypeConverter(); + options.AddPropertyConverter(entity => entity.Header, new SocketDataPropertyConverterAttribute() + { + Offset = 0, + Length = 5 + }); + }); + + var provider = sc.BuildServiceProvider(); + var service = provider.GetRequiredService>(); + Assert.NotNull(service.Value); + + var ret = service.Value.TryGetTypeConverter(out var converter); + Assert.True(ret); + Assert.NotNull(converter); + + var fakeConverter = service.Value.TryGetTypeConverter(out var fooConverter); + Assert.False(fakeConverter); + Assert.Null(fooConverter); + + ret = service.Value.TryGetPropertyConverter(entity => entity.Header, out var propertyConverterAttribute); + Assert.True(ret); + Assert.NotNull(propertyConverterAttribute); + Assert.True(propertyConverterAttribute is { Offset: 0, Length: 5 }); + + ret = service.Value.TryGetPropertyConverter(entity => entity.Name, out var fooPropertyConverterAttribute); + Assert.False(ret); + Assert.Null(fooPropertyConverterAttribute); + + ret = service.Value.TryGetPropertyConverter(entity => entity.ToString(), out _); + Assert.False(ret); + } + + class MockEntity + { + public byte[]? Header { get; set; } + + public byte[]? Body { get; set; } + } + + class MockLoggerProvider : ILoggerProvider + { + public ILogger CreateLogger(string categoryName) + { + return new MockLogger(); + } + + public void Dispose() + { + + } + } + + class MockLogger : ILogger + { + public IDisposable? BeginScope(TState state) where TState : notnull + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + + } + } +} diff --git a/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs b/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs new file mode 100644 index 00000000..f6760b57 --- /dev/null +++ b/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs @@ -0,0 +1,1267 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using Microsoft.Extensions.Logging; +using System.Buffers; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Text; + +namespace UnitTestTcpSocket; + +public class TcpSocketFactoryTest +{ + [Fact] + public async Task GetOrCreate_Ok() + { + // 测试 GetOrCreate 方法创建的 Client 销毁后继续 GetOrCreate 得到的对象是否可用 + var sc = new ServiceCollection(); + sc.AddLogging(builder => + { + builder.AddProvider(new MockLoggerProvider()); + }); + sc.AddBootstrapBlazorTcpSocketFactory(); + var provider = sc.BuildServiceProvider(); + var factory = provider.GetRequiredService(); + var client1 = factory.GetOrCreate("demo", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0)); + await client1.CloseAsync(); + + var client2 = factory.GetOrCreate("demo", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0)); + Assert.Equal(client1, client2); + + var ip = Dns.GetHostAddresses(Dns.GetHostName(), AddressFamily.InterNetwork).FirstOrDefault() ?? IPAddress.Loopback; + var client3 = factory.GetOrCreate("demo1", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint(ip.ToString(), 0)); + + // 测试不合格 IP 地址 + var client4 = factory.GetOrCreate("demo2", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint("256.0.0.1", 0)); + + var client5 = factory.Remove("demo2"); + Assert.Equal(client4, client5); + Assert.NotNull(client5); + + await client5.DisposeAsync(); + await factory.DisposeAsync(); + } + + [Fact] + public async Task ConnectAsync_Timeout() + { + var client = CreateClient(builder => + { + // 增加发送报错 MockSocket + builder.AddTransient(); + }); + client.Options.ConnectTimeout = 10; + + var connect = await client.ConnectAsync("localhost", 9999); + Assert.False(connect); + } + + [Fact] + public async Task ConnectAsync_Cancel() + { + var client = CreateClient(builder => + { + builder.AddTransient(); + }, + options => + { + options.ConnectTimeout = 500; + }); + + // 测试 ConnectAsync 方法连接取消逻辑 + var cst = new CancellationTokenSource(); + cst.Cancel(); + var connect = await client.ConnectAsync("localhost", 9999, cst.Token); + + // 由于信号量被取消,所以连接会失败 + Assert.False(connect); + + // 测试真正的连接被取消逻辑 + cst = new CancellationTokenSource(200); + connect = await client.ConnectAsync("localhost", 9999, cst.Token); + Assert.False(connect); + } + + [Fact] + public async Task ConnectAsync_Failed() + { + var client = CreateClient(); + + // 测试 ConnectAsync 方法连接失败 + var connect = await client.ConnectAsync("localhost", 9999); + Assert.False(connect); + } + + [Fact] + public async Task ConnectAsync_Error() + { + var client = CreateClient(); + + // 反射设置 SocketClientProvider 为空 + var propertyInfo = client.GetType().GetProperty("ServiceProvider", BindingFlags.Public | BindingFlags.Instance); + Assert.NotNull(propertyInfo); + propertyInfo.SetValue(client, null); + + // 测试 ConnectAsync 方法连接失败 + var ex = await Assert.ThrowsAsync(async () => await client.ConnectAsync("localhost", 9999)); + Assert.NotNull(ex); + + // 反射测试 Log 方法 + var methodInfo = client.GetType().GetMethod("Log", BindingFlags.NonPublic | BindingFlags.Instance); + Assert.NotNull(methodInfo); + methodInfo.Invoke(client, [LogLevel.Error, null!, "Test error log"]); + } + + [Fact] + public async Task ConnectAsync_Lock() + { + // 测试并发锁问题 + var provider = new MockAutoReconnectLockSocketProvider(); + var client = CreateClient(builder => + { + builder.AddTransient(p => provider); + }); + + // 开 5 个线程同时连接 + _ = Task.Run(async () => + { + // 延时 150 保证有一个连接失败 + await Task.Delay(150); + provider.SetConnected(true); + }); + var results = await Task.WhenAll(Enumerable.Range(1, 5).Select(i => client.ConnectAsync("localhost", 0).AsTask())); + // 期望结果是 1个 false 4个 true + Assert.Equal(1, results.Count(r => !r)); + Assert.Equal(4, results.Count(r => r)); + } + + [Fact] + public async Task Send_Timeout() + { + var port = 8887; + var server = StartTcpServer(port, MockSplitPackageAsync); + + var client = CreateClient(builder => + { + // 增加发送报错 MockSocket + builder.AddTransient(); + }); + client.Options.SendTimeout = 10; + + await client.ConnectAsync("localhost", port); + + var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); + var result = await client.SendAsync(data); + Assert.False(result); + } + + [Fact] + public async Task SendAsync_Error() + { + var client = CreateClient(builder => + { + // 增加发送报错 MockSocket + builder.AddTransient(); + }); + + // 测试未建立连接前调用 SendAsync 方法报异常逻辑 + var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); + var ex = await Assert.ThrowsAsync(async () => await client.SendAsync(data)); + Assert.NotNull(ex); + + // 测试发送失败 + var port = 8892; + var server = StartTcpServer(port, MockSplitPackageAsync); + + await client.ConnectAsync("localhost", port); + Assert.True(client.IsConnected); + + // 内部生成异常日志 + await client.SendAsync(data); + } + + [Fact] + public async Task SendAsync_Cancel() + { + var port = 8881; + var server = StartTcpServer(port, MockSplitPackageAsync); + + var client = CreateClient(); + Assert.False(client.IsConnected); + + // 连接 TCP Server + await client.ConnectAsync("localhost", port); + Assert.True(client.IsConnected); + + // 测试 SendAsync 方法发送取消逻辑 + var cst = new CancellationTokenSource(); + cst.Cancel(); + + var result = await client.SendAsync("test", null, cst.Token); + Assert.False(result); + + result = await client.SendAsync("test", Encoding.UTF8, cst.Token); + Assert.False(result); + + // 关闭连接 + StopTcpServer(server); + } + + [Fact] + public async Task ReceiveAsync_Timeout() + { + var port = 8888; + var server = StartTcpServer(port, MockSplitPackageAsync); + + var client = CreateClient(); + client.Options.ReceiveTimeout = 100; + + await client.ConnectAsync("localhost", port); + + var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); + await client.SendAsync(data); + await Task.Delay(220); // 等待接收超时 + } + + [Fact] + public async Task ReceiveAsync_Cancel() + { + var port = 8889; + var server = StartTcpServer(port, MockSplitPackageAsync); + + var client = CreateClient(); + await client.ConnectAsync("localhost", port); + + var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); + await client.SendAsync(data); + + // 通过反射取消令牌 + var type = client.GetType(); + Assert.NotNull(type); + + var fieldInfo = type.GetField("_receiveCancellationTokenSource", BindingFlags.NonPublic | BindingFlags.Instance); + Assert.NotNull(fieldInfo); + var tokenSource = fieldInfo.GetValue(client) as CancellationTokenSource; + Assert.NotNull(tokenSource); + tokenSource.Cancel(); + await Task.Delay(50); + } + + [Fact] + public async Task ReceiveAsync_InvalidOperationException() + { + // 未连接时调用 ReceiveAsync 方法会抛出 InvalidOperationException 异常 + var client = CreateClient(); + var ex = await Assert.ThrowsAsync(async () => await client.ReceiveAsync()); + Assert.NotNull(ex); + + // 已连接但是启用了自动接收功能时调用 ReceiveAsync 方法会抛出 InvalidOperationException 异常 + var port = 8893; + var server = StartTcpServer(port, MockSplitPackageAsync); + + client.Options.IsAutoReceive = true; + var connected = await client.ConnectAsync("localhost", port); + Assert.True(connected); + + ex = await Assert.ThrowsAsync(async () => await client.ReceiveAsync()); + Assert.NotNull(ex); + } + + [Fact] + public async Task ReceiveAsync_Ok() + { + var onConnecting = false; + var onConnected = false; + var port = 8891; + var server = StartTcpServer(port, MockSplitPackageAsync); + + var client = CreateClient(); + client.Options.IsAutoReceive = false; + client.OnConnecting = () => + { + onConnecting = true; + return Task.CompletedTask; + }; + client.OnConnected = () => + { + onConnected = true; + return Task.CompletedTask; + }; + var connected = await client.ConnectAsync("localhost", port); + Assert.True(connected); + Assert.True(onConnecting); + Assert.True(onConnected); + + var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); + var send = await client.SendAsync(data); + Assert.True(send); + + // 未设置数据处理器未开启自动接收时,调用 ReceiveAsync 方法获取数据 + // 需要自己处理粘包分包和业务问题 + var payload = await client.ReceiveAsync(); + Assert.Equal([1, 2, 3, 4, 5], payload.ToArray()); + + // 由于服务器端模拟了拆包发送第二段数据,所以这里可以再次调用 ReceiveAsync 方法获取第二段数据 + payload = await client.ReceiveAsync(); + Assert.Equal([3, 4], payload.ToArray()); + } + + [Fact] + public async Task ReceiveAsync_Error() + { + var client = CreateClient(); + + // 测试未建立连接前调用 ReceiveAsync 方法报异常逻辑 + var type = client.GetType(); + Assert.NotNull(type); + + var methodInfo = type.GetMethod("AutoReceiveAsync", BindingFlags.NonPublic | BindingFlags.Instance); + Assert.NotNull(methodInfo); + + var task = (ValueTask)methodInfo.Invoke(client, null)!; + var ex = await Assert.ThrowsAsync(async () => await task); + Assert.NotNull(ex); + + var port = 8882; + var server = StartTcpServer(port, MockSplitPackageAsync); + + Assert.Equal(1024 * 64, client.Options.ReceiveBufferSize); + + client.Options.ReceiveBufferSize = 1024 * 20; + Assert.Equal(1024 * 20, client.Options.ReceiveBufferSize); + + ReadOnlyMemory buffer = ReadOnlyMemory.Empty; + var tcs = new TaskCompletionSource(); + + // 增加接收回调方法 + client.ReceivedCallBack = b => + { + buffer = b; + tcs.SetResult(); + return ValueTask.CompletedTask; + }; + + await client.ConnectAsync("localhost", port); + + // 发送数据导致接收数据异常 + var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); + await client.SendAsync(data); + + await tcs.Task; + Assert.Equal([1, 2, 3, 4, 5], buffer.ToArray()); + + // 关闭连接 + StopTcpServer(server); + } + + [Fact] + public async Task AutoReconnect_Ok() + { + var client = CreateClient(optionConfigure: options => + { + options.IsAutoReconnect = true; + options.ReconnectInterval = 200; + options.IsAutoReceive = true; + }); + + // 使用场景自动接收数据,短线后自动重连 + var port = 8894; + var connect = await client.ConnectAsync("localhost", port); + Assert.False(connect); + + // 开启服务端后,可以自动重连上 + var server = StartTcpServer(port, LoopSendPackageAsync); + await Task.Delay(250); + Assert.True(client.IsConnected); + + await client.DisposeAsync(); + } + + [Fact] + public async Task AutoReconnect_False() + { + var provider = new MockAutoReconnectSocketProvider(); + var client = CreateClient(builder => + { + // 增加发送报错 MockSocket + builder.AddTransient(p => provider); + }, + optionConfigure: options => + { + options.IsAutoReconnect = true; + options.ReconnectInterval = 200; + options.IsAutoReceive = true; + }); + + // 使用场景自动接收数据,短线后自动重连 + var connect = await client.ConnectAsync("localhost", 0); + Assert.False(connect); + + provider.SetConnected(true); + await Task.Delay(250); + Assert.True(client.IsConnected); + } + + [Fact] + public async Task AutoReconnect_Send_Ok() + { + // 发送数据时连接断开了,测试重连功能 + var provider = new MockAutoReconnectSocketProvider(); + var client = CreateClient(builder => + { + // 增加发送报错 MockSocket + builder.AddTransient(p => provider); + }, optionConfigure: options => + { + options.IsAutoReconnect = true; + options.ReconnectInterval = 200; + options.IsAutoReceive = true; + }); + + provider.SetConnected(true); + var connect = await client.ConnectAsync("localhost", 0); + Assert.True(connect); + + // 发送时断开连接 + provider.SetSend(false); + var send = await client.SendAsync("test"); + Assert.False(send); + + await Task.Delay(250); + Assert.True(client.IsConnected); + } + + [Fact] + public async Task AutoReconnect_Receive_Ok() + { + // 接收数据时连接断开了,测试重连功能 + var provider = new MockAutoReconnectSocketProvider(); + var client = CreateClient(builder => + { + // 增加发送报错 MockSocket + builder.AddTransient(p => provider); + }, optionConfigure: options => + { + options.IsAutoReconnect = true; + options.ReconnectInterval = 200; + options.IsAutoReceive = false; + }); + + provider.SetConnected(true); + var connect = await client.ConnectAsync("localhost", 0); + Assert.True(connect); + + // 发送时断开连接 + provider.SetReceive(false); + var buffer = await client.ReceiveAsync(); + Assert.Equal(Memory.Empty, buffer); + + await Task.Delay(250); + provider.SetReceive(true); + buffer = await client.ReceiveAsync(); + Assert.Equal(5, buffer.Length); + } + + [Fact] + public async Task AutoReconnect_Cancel() + { + // 测试重连时取消逻辑 + var provider = new MockAutoReconnectSocketProvider(); + var client = CreateClient(builder => + { + // 增加发送报错 MockSocket + builder.AddTransient(p => provider); + }, optionConfigure: options => + { + options.IsAutoReconnect = true; + options.ReconnectInterval = 2000; + options.IsAutoReceive = false; + }); + + await client.ConnectAsync("localhost", 0); + await Task.Delay(100); + await client.DisposeAsync(); + } + + [Fact] + public async Task FixLengthDataPackageHandler_Ok() + { + var port = 8884; + var server = StartTcpServer(port, MockSplitPackageAsync); + var client = CreateClient(); + var tcs = new TaskCompletionSource(); + var receivedBuffer = new byte[1024]; + + // 设置数据适配器 + var adapter = new DataPackageAdapter + { + DataPackageHandler = new FixLengthDataPackageHandler(7) + }; + client.SetDataPackageAdapter(adapter, buffer => + { + // buffer 即是接收到的数据 + buffer.CopyTo(receivedBuffer); + receivedBuffer = receivedBuffer[..buffer.Length]; + tcs.SetResult(); + return ValueTask.CompletedTask; + }); + + // 测试 ConnectAsync 方法 + var connect = await client.ConnectAsync("localhost", port); + Assert.True(connect); + Assert.True(client.IsConnected); + + // 测试 SendAsync 方法 + var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); + var result = await client.SendAsync(data); + Assert.True(result); + + await tcs.Task; + Assert.Equal([1, 2, 3, 4, 5, 3, 4], receivedBuffer.ToArray()); + + // 关闭连接 + await client.CloseAsync(); + StopTcpServer(server); + } + + [Fact] + public async Task FixLengthDataPackageHandler_Sticky() + { + var port = 8885; + var server = StartTcpServer(port, MockStickyPackageAsync); + var client = CreateClient(); + var tcs = new TaskCompletionSource(); + var receivedBuffer = new byte[128]; + + // 连接 TCP Server + var connect = await client.ConnectAsync("localhost", port); + + // 设置数据适配器 + var adapter = new DataPackageAdapter + { + DataPackageHandler = new FixLengthDataPackageHandler(7) + }; + + client.SetDataPackageAdapter(adapter, buffer => + { + // buffer 即是接收到的数据 + buffer.CopyTo(receivedBuffer); + receivedBuffer = receivedBuffer[..buffer.Length]; + tcs.SetResult(); + return ValueTask.CompletedTask; + }); + + // 发送数据 + var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); + await client.SendAsync(data); + + // 等待接收数据处理完成 + await tcs.Task; + + // 验证接收到的数据 + Assert.Equal([1, 2, 3, 4, 5, 3, 4], receivedBuffer.ToArray()); + + // 重置接收缓冲区 + receivedBuffer = new byte[1024]; + tcs = new TaskCompletionSource(); + + // 等待第二次数据 + await tcs.Task; + + // 验证第二次收到的数据 + Assert.Equal([2, 2, 3, 4, 5, 6, 7], receivedBuffer.ToArray()); + tcs = new TaskCompletionSource(); + await tcs.Task; + + // 验证第三次收到的数据 + Assert.Equal([3, 2, 3, 4, 5, 6, 7], receivedBuffer.ToArray()); + + // 关闭连接 + await client.CloseAsync(); + StopTcpServer(server); + } + + [Fact] + public async Task DelimiterDataPackageHandler_Ok() + { + var port = 8883; + var server = StartTcpServer(port, MockDelimiterPackageAsync); + var client = CreateClient(); + var tcs = new TaskCompletionSource(); + var receivedBuffer = new byte[128]; + + // 设置数据适配器 + var adapter = new DataPackageAdapter + { + DataPackageHandler = new DelimiterDataPackageHandler([13, 10]), + }; + client.SetDataPackageAdapter(adapter, buffer => + { + // buffer 即是接收到的数据 + buffer.CopyTo(receivedBuffer); + receivedBuffer = receivedBuffer[..buffer.Length]; + tcs.SetResult(); + return ValueTask.CompletedTask; + }); + + // 连接 TCP Server + var connect = await client.ConnectAsync("localhost", port); + + // 发送数据 + var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); + await client.SendAsync(data); + + // 等待接收数据处理完成 + await tcs.Task; + + // 验证接收到的数据 + Assert.Equal([1, 2, 3, 4, 5, 13, 10], receivedBuffer.ToArray()); + + // 等待第二次数据 + receivedBuffer = new byte[1024]; + tcs = new TaskCompletionSource(); + await tcs.Task; + + // 验证接收到的数据 + Assert.Equal([5, 6, 13, 10], receivedBuffer.ToArray()); + + // 关闭连接 + await client.CloseAsync(); + StopTcpServer(server); + + var handler = new DelimiterDataPackageHandler("\r\n"); + var ex = Assert.Throws(() => new DelimiterDataPackageHandler(string.Empty)); + Assert.NotNull(ex); + + ex = Assert.Throws(() => new DelimiterDataPackageHandler(null!)); + Assert.NotNull(ex); + } + + [Fact] + public async Task TryConvertTo_Ok() + { + var port = 8886; + var server = StartTcpServer(port, MockEntityPackageAsync); + var client = CreateClient(); + var tcs = new TaskCompletionSource(); + MockEntity? entity = null; + + // 设置数据适配器 + var adapter = new DataPackageAdapter + { + DataPackageHandler = new FixLengthDataPackageHandler(29), + }; + client.SetDataPackageAdapter(adapter, new SocketDataConverter(), t => + { + entity = t; + tcs.SetResult(); + return Task.CompletedTask; + }); + + // 连接 TCP Server + var connect = await client.ConnectAsync("localhost", port); + + // 发送数据 + var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); + await client.SendAsync(data); + await tcs.Task; + + Assert.NotNull(entity); + Assert.Equal([1, 2, 3, 4, 5], entity.Header); + Assert.Equal([3, 4], entity.Body); + + // string + Assert.Equal("1", entity.Value1); + + // string + Assert.Equal("1", entity.Value14); + + // int + Assert.Equal(9, entity.Value2); + + // long + Assert.Equal(16, entity.Value3); + + // double + Assert.Equal(3.14, entity.Value4); + + // single + Assert.NotEqual(0, entity.Value5); + + // short + Assert.Equal(0x23, entity.Value6); + + // ushort + Assert.Equal(0x24, entity.Value7); + + // uint + Assert.Equal((uint)0x25, entity.Value8); + + // ulong + Assert.Equal((ulong)0x26, entity.Value9); + + // bool + Assert.True(entity.Value10); + + // enum + Assert.Equal(EnumEducation.Middle, entity.Value11); + + // foo + Assert.NotNull(entity.Value12); + Assert.Equal(0x29, entity.Value12.Id); + Assert.Equal("test", entity.Value12.Name); + + // no attribute + Assert.Null(entity.Value13); + + // 测试 SocketDataConverter 标签功能 + tcs = new TaskCompletionSource(); + client.SetDataPackageAdapter(adapter, t => + { + entity = t; + tcs.SetResult(); + return Task.CompletedTask; + }); + await client.SendAsync(data); + await tcs.Task; + + Assert.NotNull(entity); + Assert.Equal([1, 2, 3, 4, 5], entity.Header); + + // 测试数据适配器直接调用 TryConvertTo 方法转换数据 + var adapter2 = new DataPackageAdapter(); + var result = adapter2.TryConvertTo(data, new SocketDataConverter(), out var t); + Assert.True(result); + Assert.NotNull(t); + Assert.Equal([1, 2, 3, 4, 5], entity.Header); + + // 测试 SetDataPackageAdapter 泛型无标签情况 + tcs = new TaskCompletionSource(); + NoConvertEntity? noConvertEntity = null; + client.SetDataPackageAdapter(adapter, t => + { + noConvertEntity = t; + tcs.SetResult(); + return Task.CompletedTask; + }); + await client.SendAsync(data); + await tcs.Task; + Assert.Null(noConvertEntity); + + var converter = new MockSocketDataConverter(); + result = converter.TryConvertTo(new byte[] { 0x1, 0x2 }, out t); + Assert.False(result); + + server.Stop(); + } + + [Fact] + public async Task TryGetTypeConverter_Ok() + { + // 测试服务配置转换器 + var port = 8895; + var server = StartTcpServer(port, MockSplitPackageAsync); + + var client = CreateClient(); + var tcs = new TaskCompletionSource(); + var receivedBuffer = new byte[128]; + + // 连接 TCP Server + var connect = await client.ConnectAsync("localhost", port); + + // 设置数据适配器 + var adapter = new DataPackageAdapter + { + DataPackageHandler = new FixLengthDataPackageHandler(7) + }; + + OptionConvertEntity? entity = null; + client.SetDataPackageAdapter(adapter, data => + { + // buffer 即是接收到的数据 + entity = data; + tcs.SetResult(); + return Task.CompletedTask; + }); + + // 发送数据 + var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); + await client.SendAsync(data); + + // 等待接收数据处理完成 + await tcs.Task; + Assert.NotNull(entity); + Assert.Equal([1, 2, 3, 4, 5], entity.Header); + Assert.Equal([3, 4], entity.Body); + + server.Stop(); + } + + private static TcpListener StartTcpServer(int port, Func handler) + { + var server = new TcpListener(IPAddress.Loopback, port); + server.Start(); + Task.Run(() => AcceptClientsAsync(server, handler)); + return server; + } + + private static async Task AcceptClientsAsync(TcpListener server, Func handler) + { + while (true) + { + var client = await server.AcceptTcpClientAsync(); + _ = Task.Run(() => handler(client)); + } + } + + private static async Task MockDelimiterPackageAsync(TcpClient client) + { + using var stream = client.GetStream(); + while (true) + { + var buffer = new byte[10240]; + var len = await stream.ReadAsync(buffer); + if (len == 0) + { + break; + } + + // 回写数据到客户端 + var block = new ReadOnlyMemory(buffer, 0, len); + await stream.WriteAsync(block, CancellationToken.None); + + await Task.Delay(20); + + // 模拟拆包发送第二段数据 + await stream.WriteAsync(new byte[] { 13, 10, 0x5, 0x6, 13, 10 }, CancellationToken.None); + } + } + + private static async Task MockSplitPackageAsync(TcpClient client) + { + using var stream = client.GetStream(); + while (true) + { + var buffer = new byte[1024]; + var len = await stream.ReadAsync(buffer); + if (len == 0) + { + break; + } + + // 回写数据到客户端 + var block = new ReadOnlyMemory(buffer, 0, len); + await stream.WriteAsync(block, CancellationToken.None); + + // 模拟延时 + await Task.Delay(50); + + // 模拟拆包发送第二段数据 + await stream.WriteAsync(new byte[] { 0x3, 0x4 }, CancellationToken.None); + } + } + + private static async Task MockEntityPackageAsync(TcpClient client) + { + using var stream = client.GetStream(); + while (true) + { + var buffer = new byte[1024]; + var len = await stream.ReadAsync(buffer); + if (len == 0) + { + break; + } + + // 回写数据到客户端 + await stream.WriteAsync(new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x3, 0x4, 0x31, 0x09, 0x10, 0x40, 0x09, 0x1E, 0xB8, 0x51, 0xEB, 0x85, 0x1F, 0x40, 0x49, 0x0F, 0xDB, 0x23, 0x24, 0x25, 0x26, 0x01, 0x01, 0x29 }, CancellationToken.None); + } + } + + private static async Task MockStickyPackageAsync(TcpClient client) + { + using var stream = client.GetStream(); + while (true) + { + var buffer = new byte[10240]; + var len = await stream.ReadAsync(buffer); + if (len == 0) + { + break; + } + + // 回写数据到客户端 + var block = new ReadOnlyMemory(buffer, 0, len); + await stream.WriteAsync(block, CancellationToken.None); + + // 模拟延时 + await Task.Delay(10); + + // 模拟拆包发送第二段数据 + await stream.WriteAsync(new byte[] { 0x3, 0x4, 0x2, 0x2 }, CancellationToken.None); + + // 模拟延时 + await Task.Delay(10); + + // 模拟粘包发送后续数据 + await stream.WriteAsync(new byte[] { 0x3, 0x4, 0x5, 0x6, 0x7, 0x3, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x1 }, CancellationToken.None); + } + } + + private static async Task LoopSendPackageAsync(TcpClient client) + { + using var stream = client.GetStream(); + while (true) + { + // 模拟发送数据 + var data = new byte[] { 1, 2, 3, 4, 5 }; + await stream.WriteAsync(data, CancellationToken.None); + // 模拟延时 + await Task.Delay(500); + } + } + + private static void StopTcpServer(TcpListener server) + { + server?.Stop(); + } + + private static ITcpSocketClient CreateClient(Action? builder = null, Action? optionConfigure = null) + { + var sc = new ServiceCollection(); + sc.AddLogging(builder => + { + builder.AddProvider(new MockLoggerProvider()); + }); + sc.AddBootstrapBlazorTcpSocketFactory(); + builder?.Invoke(sc); + + var provider = sc.BuildServiceProvider(); + var factory = provider.GetRequiredService(); + var client = factory.GetOrCreate("test", op => + { + op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0); + op.EnableLog = true; + optionConfigure?.Invoke(op); + }); + return client; + } + + class MockLoggerProvider : ILoggerProvider + { + public ILogger CreateLogger(string categoryName) + { + return new MockLogger(); + } + + public void Dispose() + { + + } + } + + class MockLogger : ILogger + { + public IDisposable? BeginScope(TState state) where TState : notnull + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + + } + } + + class MockSendErrorSocketProvider : ITcpSocketClientProvider + { + public bool IsConnected { get; private set; } + + public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); + + public ValueTask CloseAsync() + { + return ValueTask.CompletedTask; + } + + public ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) + { + IsConnected = true; + return ValueTask.FromResult(true); + } + + public ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) + { + return ValueTask.FromResult(0); + } + + public ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) + { + throw new Exception("Mock send error"); + } + } + + class MockConnectTimeoutSocketProvider : ITcpSocketClientProvider + { + public bool IsConnected { get; private set; } + + public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); + + public ValueTask CloseAsync() + { + return ValueTask.CompletedTask; + } + + public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) + { + await Task.Delay(1000, token); + IsConnected = false; + return false; + } + + public ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) + { + return ValueTask.FromResult(0); + } + + public ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) + { + return ValueTask.FromResult(true); + } + } + + class MockConnectCancelSocketProvider : ITcpSocketClientProvider + { + public bool IsConnected { get; private set; } + + public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); + + public ValueTask CloseAsync() + { + return ValueTask.CompletedTask; + } + + public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) + { + await Task.Delay(250, token); + return false; + } + + public ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) + { + return ValueTask.FromResult(0); + } + + public ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) + { + return ValueTask.FromResult(true); + } + } + + class MockSendTimeoutSocketProvider : ITcpSocketClientProvider + { + public bool IsConnected { get; private set; } + + public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); + + public ValueTask CloseAsync() + { + return ValueTask.CompletedTask; + } + + public ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) + { + IsConnected = true; + return ValueTask.FromResult(true); + } + + public ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) + { + return ValueTask.FromResult(0); + } + + public async ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) + { + // 模拟超时发送 + await Task.Delay(100, token); + return false; + } + } + + class MockAutoReconnectLockSocketProvider : ITcpSocketClientProvider + { + public bool IsConnected { get; private set; } + + public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Loopback, 0); + + public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) + { + await Task.Delay(100, token); + return IsConnected; + } + + public ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) + { + return ValueTask.FromResult(true); + } + + public ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) + { + byte[] data = [1, 2, 3, 4, 5]; + data.CopyTo(buffer); + return ValueTask.FromResult(5); + } + + public ValueTask CloseAsync() + { + return ValueTask.CompletedTask; + } + + public void SetConnected(bool state) + { + IsConnected = state; + } + } + + class MockAutoReconnectSocketProvider : ITcpSocketClientProvider + { + public bool IsConnected { get; private set; } + + public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Loopback, 0); + + public ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) + { + return ValueTask.FromResult(IsConnected); + } + + private bool _sendState = true; + public ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) + { + return ValueTask.FromResult(_sendState); + } + + private bool _receiveState = true; + public ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) + { + if (_receiveState) + { + byte[] data = [1, 2, 3, 4, 5]; + data.CopyTo(buffer); + return ValueTask.FromResult(5); + } + else + { + return ValueTask.FromResult(0); + } + } + + public ValueTask CloseAsync() + { + return ValueTask.CompletedTask; + } + + public void SetConnected(bool state) + { + IsConnected = state; + } + + public void SetSend(bool state) + { + _sendState = state; + } + + public void SetReceive(bool state) + { + _receiveState = state; + } + } + + [SocketDataTypeConverter(Type = typeof(SocketDataConverter))] + class MockEntity + { + [SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 0, Length = 5)] + public byte[]? Header { get; set; } + + [SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 5, Length = 2)] + public byte[]? Body { get; set; } + + [SocketDataPropertyConverter(Type = typeof(string), Offset = 7, Length = 1, EncodingName = "utf-8")] + public string? Value1 { get; set; } + + [SocketDataPropertyConverter(Type = typeof(int), Offset = 8, Length = 1)] + public int Value2 { get; set; } + + [SocketDataPropertyConverter(Type = typeof(long), Offset = 9, Length = 1)] + public long Value3 { get; set; } + + [SocketDataPropertyConverter(Type = typeof(double), Offset = 10, Length = 8)] + public double Value4 { get; set; } + + [SocketDataPropertyConverter(Type = typeof(float), Offset = 18, Length = 4)] + public float Value5 { get; set; } + + [SocketDataPropertyConverter(Type = typeof(short), Offset = 22, Length = 1)] + public short Value6 { get; set; } + + [SocketDataPropertyConverter(Type = typeof(ushort), Offset = 23, Length = 1)] + public ushort Value7 { get; set; } + + [SocketDataPropertyConverter(Type = typeof(uint), Offset = 24, Length = 1)] + public uint Value8 { get; set; } + + [SocketDataPropertyConverter(Type = typeof(ulong), Offset = 25, Length = 1)] + public ulong Value9 { get; set; } + + [SocketDataPropertyConverter(Type = typeof(bool), Offset = 26, Length = 1)] + public bool Value10 { get; set; } + + [SocketDataPropertyConverter(Type = typeof(EnumEducation), Offset = 27, Length = 1)] + public EnumEducation Value11 { get; set; } + + [SocketDataPropertyConverter(Type = typeof(Foo), Offset = 28, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])] + public Foo? Value12 { get; set; } + + [SocketDataPropertyConverter(Type = typeof(string), Offset = 7, Length = 1)] + public string? Value14 { get; set; } + + public string? Value13 { get; set; } + } + + class MockSocketDataConverter : SocketDataConverter + { + protected override bool Parse(ReadOnlyMemory data, MockEntity entity) + { + return false; + } + } + + class FooConverter(string name) : ISocketDataPropertyConverter + { + public object? Convert(ReadOnlyMemory data) + { + return new Foo() { Id = data.Span[0], Name = name }; + } + } + + class NoConvertEntity + { + public byte[]? Header { get; set; } + + public byte[]? Body { get; set; } + } + + class OptionConvertEntity + { + public byte[]? Header { get; set; } + + public byte[]? Body { get; set; } + } +} diff --git a/test/UnitTestTcpSocket/TcpSocketPropertyConverterTest.cs b/test/UnitTestTcpSocket/TcpSocketPropertyConverterTest.cs new file mode 100644 index 00000000..d0fb0eb7 --- /dev/null +++ b/test/UnitTestTcpSocket/TcpSocketPropertyConverterTest.cs @@ -0,0 +1,72 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace UnitTestTcpSocket; + +public class TcpSocketPropertyConverterTest +{ + [Fact] + public void UInt16Converter_Ok() + { + var converter = new SocketDataUInt16LittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0xFF, 0x00 }); + Assert.Equal((ushort)0xFF, actual); + } + + [Fact] + public void Int16Converter_Ok() + { + var converter = new SocketDataInt16LittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0xFF, 0x00 }); + Assert.Equal((short)0xFF, actual); + } + + [Fact] + public void UInt32Converter_Ok() + { + var converter = new SocketDataUInt32LittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00 }); + Assert.Equal((uint)0xFF, actual); + } + + [Fact] + public void Int32Converter_Ok() + { + var converter = new SocketDataInt32LittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00 }); + Assert.Equal(0xFF, actual); + } + + [Fact] + public void UInt64Converter_Ok() + { + var converter = new SocketDataUInt64LittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + Assert.Equal((ulong)0xFF, actual); + } + + [Fact] + public void Int64Converter_Ok() + { + var converter = new SocketDataInt64LittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + Assert.Equal((long)0xFF, actual); + } + + [Fact] + public void SingleConverter_Ok() + { + var converter = new SocketDataSingleLittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0xC3, 0xF5, 0x48, 0x40 }); + Assert.Equal((float)3.14, actual); + } + + [Fact] + public void DoubleConverter_Ok() + { + var converter = new SocketDataDoubleLittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }); + Assert.Equal((double)3.14, actual); + } +} diff --git a/test/UnitTestTcpSocket/UnitTestTcpSocket.csproj b/test/UnitTestTcpSocket/UnitTestTcpSocket.csproj new file mode 100644 index 00000000..be223f3d --- /dev/null +++ b/test/UnitTestTcpSocket/UnitTestTcpSocket.csproj @@ -0,0 +1,24 @@ + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/test/UnitTestTcpSocket/UtiityTest.cs b/test/UnitTestTcpSocket/UtiityTest.cs new file mode 100644 index 00000000..15ba0e0f --- /dev/null +++ b/test/UnitTestTcpSocket/UtiityTest.cs @@ -0,0 +1,32 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using System.Net; + +namespace UnitTestTcpSocket; + +public class UtiityTest +{ + + [Fact] + public void ConvertToIPAddress_Ok() + { + var ex = Assert.Throws(() => Utility.ConvertToIPAddress("")); + Assert.NotNull(ex); + + var address = Utility.ConvertToIPAddress("any"); + Assert.Equal(IPAddress.Any, address); + } + + [Fact] + public void ConvertToIpEndPoint_Ok() + { + var ex = Assert.Throws(() => Utility.ConvertToIpEndPoint("localhost", 88990)); + Assert.NotNull(ex); + + ex = null; + ex = Assert.Throws(() => Utility.ConvertToIpEndPoint("localhost", -1000)); + Assert.NotNull(ex); + } +} From 88ac65eb192b90ff309d2e9be0bf9d720aea0d6b Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 23 Jul 2025 16:25:23 +0800 Subject: [PATCH 2/5] =?UTF-8?q?test:=20=E6=9B=B4=E6=96=B0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/TcpSocketExtensions.cs | 16 ---------------- test/UnitTestTcpSocket/TcpSocketFactoryTest.cs | 18 +++++++++++++++++- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs index baddeca0..8725151f 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs @@ -28,22 +28,6 @@ public static IServiceCollection AddBootstrapBlazorTcpSocketFactory(this IServic // 增加 ISocketClientProvider 服务 services.TryAddTransient(); - // 增加转换器集合配置服务 - services.AddSocketDataConverters(); - return services; - } - - /// - /// 增加 Socket 数据转换器集合配置项服务 - /// - /// - /// - static IServiceCollection AddSocketDataConverters(this IServiceCollection services) - { - services.AddOptions(); - services.TryAddSingleton, ConfigurationChangeTokenSource>(); - services.TryAddSingleton, ConfigureOptions>(); - return services; } diff --git a/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs b/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs index f6760b57..0cb56b73 100644 --- a/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs +++ b/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs @@ -765,7 +765,23 @@ public async Task TryGetTypeConverter_Ok() var port = 8895; var server = StartTcpServer(port, MockSplitPackageAsync); - var client = CreateClient(); + var client = CreateClient(builder => + { + builder.Configure(options => + { + options.AddTypeConverter(); + options.AddPropertyConverter(entity => entity.Header, new SocketDataPropertyConverterAttribute() + { + Offset = 0, + Length = 5 + }); + options.AddPropertyConverter(entity => entity.Body, new SocketDataPropertyConverterAttribute() + { + Offset = 5, + Length = 2 + }); + }); + }); var tcs = new TaskCompletionSource(); var receivedBuffer = new byte[128]; From d8d3f7f1b076f196c6de230ce774eba44e5bb9d9 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 23 Jul 2025 17:33:11 +0800 Subject: [PATCH 3/5] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=94=B9=E7=B1=BB?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BootstrapBlazor.Socket.csproj | 6 +++ .../DataPackageAdapter.cs | 4 +- .../IDataPackageAdapter.cs | 4 +- ...ocketDataConverter.cs => DataConverter.cs} | 12 ++--- ...ections.cs => DataConverterCollections.cs} | 26 +++++------ ...e.cs => DataPropertyConverterAttribute.cs} | 4 +- ...ibute.cs => DataTypeConverterAttribute.cs} | 6 +-- ...cketDataConverter.cs => IDataConverter.cs} | 6 +-- .../DataPackageHandlerBase.cs | 2 +- .../DelimiterDataPackageHandler.cs | 2 +- .../FixLengthDataPackageHandler.cs | 2 +- .../IDataPackageHandler.cs | 2 +- ...xtensions.cs => DataPropertyExtensions.cs} | 42 ++++++++--------- ...aBoolConverter.cs => DataBoolConverter.cs} | 4 +- ...Converter.cs => DataByteArrayConverter.cs} | 4 +- ...ter.cs => DataDoubleBigEndianConverter.cs} | 4 +- ....cs => DataDoubleLittleEndianConverter.cs} | 4 +- ...aEnumConverter.cs => DataEnumConverter.cs} | 4 +- ...rter.cs => DataInt16BigEndianConverter.cs} | 4 +- ...r.cs => DataInt16LittleEndianConverter.cs} | 4 +- ...rter.cs => DataInt32BigEndianConverter.cs} | 4 +- ...r.cs => DataInt32LittleEndianConverter.cs} | 4 +- ...rter.cs => DataInt64BigEndianConverter.cs} | 4 +- ...r.cs => DataInt64LittleEndianConverter.cs} | 4 +- ...ter.cs => DataSingleBigEndianConverter.cs} | 4 +- ....cs => DataSingleLittleEndianConverter.cs} | 4 +- ...ingConverter.cs => DataStringConverter.cs} | 4 +- ...ter.cs => DataUInt16BigEndianConverter.cs} | 4 +- ....cs => DataUInt16LittleEndianConverter.cs} | 4 +- ...ter.cs => DataUInt32BigEndianConverter.cs} | 4 +- ....cs => DataUInt32LittleEndianConverter.cs} | 4 +- ...ter.cs => DataUInt64BigEndianConverter.cs} | 4 +- ....cs => DataUInt64LittleEndianConverter.cs} | 4 +- ...Converter.cs => IDataPropertyConverter.cs} | 4 +- .../BootstrapBlazor.TcpSocket.csproj | 6 +++ .../DefaultTcpSocketClient.cs | 2 +- .../DefaultTcpSocketClientProvider.cs | 2 +- .../DefaultTcpSocketFactory.cs | 2 +- .../Extensions/ITcpSocketClientExtensions.cs | 20 ++++---- .../Extensions/TcpSocketExtensions.cs | 7 ++- .../Extensions/Utility.cs | 2 +- .../ITcpSocketClient.cs | 2 +- .../ITcpSocketClientProvider.cs | 2 +- .../ITcpSocketFactory.cs | 2 +- .../TcpSocketClientOptions.cs | 2 +- .../SocketDataConverterCollectionsTest.cs | 8 ++-- .../UnitTestTcpSocket/TcpSocketFactoryTest.cs | 46 +++++++++---------- .../TcpSocketPropertyConverterTest.cs | 16 +++---- 48 files changed, 166 insertions(+), 155 deletions(-) rename src/extensions/BootstrapBlazor.Socket/{DataPackageAdapter => DataAdapter}/DataPackageAdapter.cs (96%) rename src/extensions/BootstrapBlazor.Socket/{DataPackageAdapter => DataAdapter}/IDataPackageAdapter.cs (94%) rename src/extensions/BootstrapBlazor.Socket/DataConverter/{SocketDataConverter.cs => DataConverter.cs} (83%) rename src/extensions/BootstrapBlazor.Socket/DataConverter/{SocketDataConverterCollections.cs => DataConverterCollections.cs} (77%) rename src/extensions/BootstrapBlazor.Socket/DataConverter/{SocketDataPropertyConverterAttribute.cs => DataPropertyConverterAttribute.cs} (93%) rename src/extensions/BootstrapBlazor.Socket/DataConverter/{SocketDataTypeConverterAttribute.cs => DataTypeConverterAttribute.cs} (69%) rename src/extensions/BootstrapBlazor.Socket/DataConverter/{ISocketDataConverter.cs => IDataConverter.cs} (90%) rename src/extensions/BootstrapBlazor.Socket/{DataPackageHandler => DataHandler}/DataPackageHandlerBase.cs (98%) rename src/extensions/BootstrapBlazor.Socket/{DataPackageHandler => DataHandler}/DelimiterDataPackageHandler.cs (98%) rename src/extensions/BootstrapBlazor.Socket/{DataPackageHandler => DataHandler}/FixLengthDataPackageHandler.cs (97%) rename src/extensions/BootstrapBlazor.Socket/{DataPackageHandler => DataHandler}/IDataPackageHandler.cs (97%) rename src/extensions/BootstrapBlazor.Socket/Extensions/{SocketDataPropertyExtensions.cs => DataPropertyExtensions.cs} (57%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataBoolConverter.cs => DataBoolConverter.cs} (85%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataByteArrayConverter.cs => DataByteArrayConverter.cs} (82%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataDoubleBigEndianConverter.cs => DataDoubleBigEndianConverter.cs} (88%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataDoubleLittleEndianConverter.cs => DataDoubleLittleEndianConverter.cs} (87%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataEnumConverter.cs => DataEnumConverter.cs} (87%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataInt16BigEndianConverter.cs => DataInt16BigEndianConverter.cs} (88%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataInt16LittleEndianConverter.cs => DataInt16LittleEndianConverter.cs} (88%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataInt32BigEndianConverter.cs => DataInt32BigEndianConverter.cs} (88%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataInt32LittleEndianConverter.cs => DataInt32LittleEndianConverter.cs} (88%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataInt64BigEndianConverter.cs => DataInt64BigEndianConverter.cs} (88%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataInt64LittleEndianConverter.cs => DataInt64LittleEndianConverter.cs} (88%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataSingleBigEndianConverter.cs => DataSingleBigEndianConverter.cs} (88%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataSingleLittleEndianConverter.cs => DataSingleLittleEndianConverter.cs} (87%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataStringConverter.cs => DataStringConverter.cs} (83%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataUInt16BigEndianConverter.cs => DataUInt16BigEndianConverter.cs} (88%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataUInt16LittleEndianConverter.cs => DataUInt16LittleEndianConverter.cs} (87%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataUInt32BigEndianConverter.cs => DataUInt32BigEndianConverter.cs} (88%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataUInt32LittleEndianConverter.cs => DataUInt32LittleEndianConverter.cs} (87%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataUInt64BigEndianConverter.cs => DataUInt64BigEndianConverter.cs} (88%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{SocketDataUInt64LittleEndianConverter.cs => DataUInt64LittleEndianConverter.cs} (87%) rename src/extensions/BootstrapBlazor.Socket/PropertyConverter/{ISocketDataPropertyConverter.cs => IDataPropertyConverter.cs} (85%) diff --git a/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj b/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj index b353d142..940fb5fe 100644 --- a/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj +++ b/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj @@ -9,4 +9,10 @@ BootstrapBlazor extensions of Socket + + + + + + diff --git a/src/extensions/BootstrapBlazor.Socket/DataPackageAdapter/DataPackageAdapter.cs b/src/extensions/BootstrapBlazor.Socket/DataAdapter/DataPackageAdapter.cs similarity index 96% rename from src/extensions/BootstrapBlazor.Socket/DataPackageAdapter/DataPackageAdapter.cs rename to src/extensions/BootstrapBlazor.Socket/DataAdapter/DataPackageAdapter.cs index 3d35953a..315994c2 100644 --- a/src/extensions/BootstrapBlazor.Socket/DataPackageAdapter/DataPackageAdapter.cs +++ b/src/extensions/BootstrapBlazor.Socket/DataAdapter/DataPackageAdapter.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataAdapters; /// /// Provides a base implementation for adapting data packages between different systems or formats. @@ -50,7 +50,7 @@ public virtual async ValueTask HandlerAsync(ReadOnlyMemory data, Cancellat /// /// /// - public virtual bool TryConvertTo(ReadOnlyMemory data, ISocketDataConverter socketDataConverter, out TEntity? entity) + public virtual bool TryConvertTo(ReadOnlyMemory data, IDataConverter socketDataConverter, out TEntity? entity) { entity = default; var ret = socketDataConverter.TryConvertTo(data, out var v); diff --git a/src/extensions/BootstrapBlazor.Socket/DataPackageAdapter/IDataPackageAdapter.cs b/src/extensions/BootstrapBlazor.Socket/DataAdapter/IDataPackageAdapter.cs similarity index 94% rename from src/extensions/BootstrapBlazor.Socket/DataPackageAdapter/IDataPackageAdapter.cs rename to src/extensions/BootstrapBlazor.Socket/DataAdapter/IDataPackageAdapter.cs index ab1b2995..5e5a0268 100644 --- a/src/extensions/BootstrapBlazor.Socket/DataPackageAdapter/IDataPackageAdapter.cs +++ b/src/extensions/BootstrapBlazor.Socket/DataAdapter/IDataPackageAdapter.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataAdapters; /// /// Defines an adapter for handling and transmitting data packages to a target destination. @@ -50,5 +50,5 @@ public interface IDataPackageAdapter /// When this method returns, contains the converted entity if the conversion was successful; otherwise, the default /// value for the type of the entity. /// if the conversion was successful; otherwise, . - bool TryConvertTo(ReadOnlyMemory data, ISocketDataConverter socketDataConverter, out TEntity? entity); + bool TryConvertTo(ReadOnlyMemory data, IDataConverter socketDataConverter, out TEntity? entity); } diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataConverter.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverter.cs similarity index 83% rename from src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataConverter.cs rename to src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverter.cs index 248fbf3c..0a42c06e 100644 --- a/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverter.cs @@ -4,18 +4,18 @@ using System.Reflection; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Provides a base class for converting socket data into a specified entity type. /// /// The type of entity to convert the socket data into. -public class SocketDataConverter(SocketDataConverterCollections converters) : ISocketDataConverter +public class DataConverter(DataConverterCollections converters) : IDataConverter { /// /// 构造函数 /// - public SocketDataConverter() : this(new()) + public DataConverter() : this(new()) { } @@ -57,7 +57,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) + var attr = p.GetCustomAttribute(false) ?? GetPropertyConverterAttribute(p); if (attr != null) { @@ -69,9 +69,9 @@ protected virtual bool Parse(ReadOnlyMemory data, TEntity entity) return ret; } - private SocketDataPropertyConverterAttribute? GetPropertyConverterAttribute(PropertyInfo propertyInfo) + private DataPropertyConverterAttribute? GetPropertyConverterAttribute(PropertyInfo propertyInfo) { - SocketDataPropertyConverterAttribute? attr = null; + DataPropertyConverterAttribute? attr = null; if (converters.TryGetPropertyConverter(propertyInfo, out var v)) { attr = v; diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataConverterCollections.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverterCollections.cs similarity index 77% rename from src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataConverterCollections.cs rename to src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverterCollections.cs index 521d5e36..d5ee4336 100644 --- a/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataConverterCollections.cs +++ b/src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverterCollections.cs @@ -6,32 +6,32 @@ using System.Linq.Expressions; using System.Reflection; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// 数据转换器集合类 /// -public sealed class SocketDataConverterCollections +public sealed class DataConverterCollections { - readonly ConcurrentDictionary _converters = new(); - readonly ConcurrentDictionary _propertyConverters = new(); + readonly ConcurrentDictionary _converters = new(); + readonly ConcurrentDictionary _propertyConverters = new(); /// - /// 增加指定 数据类型转换器方法 + /// 增加指定 数据类型转换器方法 /// /// /// - public void AddTypeConverter(ISocketDataConverter converter) + public void AddTypeConverter(IDataConverter converter) { var type = typeof(TEntity); _converters.AddOrUpdate(type, t => converter, (t, v) => converter); } /// - /// 增加默认数据类型转换器方法 转换器使用 + /// 增加默认数据类型转换器方法 转换器使用 /// /// - public void AddTypeConverter() => AddTypeConverter(new SocketDataConverter(this)); + public void AddTypeConverter() => AddTypeConverter(new DataConverter(this)); /// /// 添加属性类型转化器方法 @@ -39,7 +39,7 @@ public void AddTypeConverter(ISocketDataConverter converter) /// /// /// - public void AddPropertyConverter(Expression> propertyExpression, SocketDataPropertyConverterAttribute attribute) + public void AddPropertyConverter(Expression> propertyExpression, DataPropertyConverterAttribute attribute) { if (propertyExpression.Body is MemberExpression memberExpression) { @@ -55,11 +55,11 @@ public void AddPropertyConverter(Expression> pro /// 获得指定数据类型转换器方法 /// /// - public bool TryGetTypeConverter([NotNullWhen(true)] out ISocketDataConverter? converter) + public bool TryGetTypeConverter([NotNullWhen(true)] out IDataConverter? converter) { converter = null; var ret = false; - if (_converters.TryGetValue(typeof(TEntity), out var v) && v is ISocketDataConverter c) + if (_converters.TryGetValue(typeof(TEntity), out var v) && v is IDataConverter c) { converter = c; ret = true; @@ -71,7 +71,7 @@ public bool TryGetTypeConverter([NotNullWhen(true)] out ISocketDataConv /// 获得指定数据类型属性转换器方法 /// /// - public bool TryGetPropertyConverter(Expression> propertyExpression, [NotNullWhen(true)] out SocketDataPropertyConverterAttribute? converterAttribute) + public bool TryGetPropertyConverter(Expression> propertyExpression, [NotNullWhen(true)] out DataPropertyConverterAttribute? converterAttribute) { converterAttribute = null; var ret = false; @@ -87,7 +87,7 @@ public bool TryGetPropertyConverter(Expression> /// 获得指定数据类型属性转换器方法 /// /// - public bool TryGetPropertyConverter(MemberInfo memberInfo, [NotNullWhen(true)] out SocketDataPropertyConverterAttribute? converterAttribute) + public bool TryGetPropertyConverter(MemberInfo memberInfo, [NotNullWhen(true)] out DataPropertyConverterAttribute? converterAttribute) { converterAttribute = null; var ret = false; diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataPropertyConverterAttribute.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/DataPropertyConverterAttribute.cs similarity index 93% rename from src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataPropertyConverterAttribute.cs rename to src/extensions/BootstrapBlazor.Socket/DataConverter/DataPropertyConverterAttribute.cs index 2cc5c802..674b27e9 100644 --- a/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataPropertyConverterAttribute.cs +++ b/src/extensions/BootstrapBlazor.Socket/DataConverter/DataPropertyConverterAttribute.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Represents an attribute used to mark a field as a socket data field. @@ -11,7 +11,7 @@ namespace BootstrapBlazor.Components; /// socket connection. It is intended for use in scenarios where socket communication requires specific fields to be /// identified for processing. [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] -public class SocketDataPropertyConverterAttribute : Attribute +public class DataPropertyConverterAttribute : Attribute { /// /// 获得/设置 数据类型 diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataTypeConverterAttribute.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/DataTypeConverterAttribute.cs similarity index 69% rename from src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataTypeConverterAttribute.cs rename to src/extensions/BootstrapBlazor.Socket/DataConverter/DataTypeConverterAttribute.cs index af27f279..545d8b75 100644 --- a/src/extensions/BootstrapBlazor.Socket/DataConverter/SocketDataTypeConverterAttribute.cs +++ b/src/extensions/BootstrapBlazor.Socket/DataConverter/DataTypeConverterAttribute.cs @@ -2,16 +2,16 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// /// [AttributeUsage(AttributeTargets.Class)] -public class SocketDataTypeConverterAttribute : Attribute +public class DataTypeConverterAttribute : Attribute { /// - /// Gets or sets the type of the . + /// Gets or sets the type of the . /// public Type? Type { get; set; } } diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/ISocketDataConverter.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/IDataConverter.cs similarity index 90% rename from src/extensions/BootstrapBlazor.Socket/DataConverter/ISocketDataConverter.cs rename to src/extensions/BootstrapBlazor.Socket/DataConverter/IDataConverter.cs index d8eff1ad..23e6ad52 100644 --- a/src/extensions/BootstrapBlazor.Socket/DataConverter/ISocketDataConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/DataConverter/IDataConverter.cs @@ -2,12 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Socket 数据转换器接口 /// -public interface ISocketDataConverter +public interface IDataConverter { } @@ -16,7 +16,7 @@ public interface ISocketDataConverter /// Defines a method to convert raw socket data into a specified entity type. /// /// The type of entity to convert the data into. -public interface ISocketDataConverter : ISocketDataConverter +public interface IDataConverter : IDataConverter { /// /// Attempts to convert the specified data to an instance of . diff --git a/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/DataPackageHandlerBase.cs b/src/extensions/BootstrapBlazor.Socket/DataHandler/DataPackageHandlerBase.cs similarity index 98% rename from src/extensions/BootstrapBlazor.Socket/DataPackageHandler/DataPackageHandlerBase.cs rename to src/extensions/BootstrapBlazor.Socket/DataHandler/DataPackageHandlerBase.cs index 90935c2b..91b03060 100644 --- a/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/DataPackageHandlerBase.cs +++ b/src/extensions/BootstrapBlazor.Socket/DataHandler/DataPackageHandlerBase.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataHandlers; /// /// Provides a base implementation for handling data packages in a communication system. diff --git a/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/DelimiterDataPackageHandler.cs b/src/extensions/BootstrapBlazor.Socket/DataHandler/DelimiterDataPackageHandler.cs similarity index 98% rename from src/extensions/BootstrapBlazor.Socket/DataPackageHandler/DelimiterDataPackageHandler.cs rename to src/extensions/BootstrapBlazor.Socket/DataHandler/DelimiterDataPackageHandler.cs index 04097461..214467f3 100644 --- a/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/DelimiterDataPackageHandler.cs +++ b/src/extensions/BootstrapBlazor.Socket/DataHandler/DelimiterDataPackageHandler.cs @@ -5,7 +5,7 @@ using System.Buffers; using System.Text; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataHandlers; /// /// Handles data packages that are delimited by a specific sequence of bytes or characters. diff --git a/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/FixLengthDataPackageHandler.cs b/src/extensions/BootstrapBlazor.Socket/DataHandler/FixLengthDataPackageHandler.cs similarity index 97% rename from src/extensions/BootstrapBlazor.Socket/DataPackageHandler/FixLengthDataPackageHandler.cs rename to src/extensions/BootstrapBlazor.Socket/DataHandler/FixLengthDataPackageHandler.cs index 21038a8e..baabe6af 100644 --- a/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/FixLengthDataPackageHandler.cs +++ b/src/extensions/BootstrapBlazor.Socket/DataHandler/FixLengthDataPackageHandler.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataHandlers; /// /// Handles fixed-length data packages by processing incoming data of a specified length. diff --git a/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/IDataPackageHandler.cs b/src/extensions/BootstrapBlazor.Socket/DataHandler/IDataPackageHandler.cs similarity index 97% rename from src/extensions/BootstrapBlazor.Socket/DataPackageHandler/IDataPackageHandler.cs rename to src/extensions/BootstrapBlazor.Socket/DataHandler/IDataPackageHandler.cs index 4b443807..99285265 100644 --- a/src/extensions/BootstrapBlazor.Socket/DataPackageHandler/IDataPackageHandler.cs +++ b/src/extensions/BootstrapBlazor.Socket/DataHandler/IDataPackageHandler.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataHandlers; /// /// Defines an interface for adapting data packages to and from a TCP socket connection. diff --git a/src/extensions/BootstrapBlazor.Socket/Extensions/SocketDataPropertyExtensions.cs b/src/extensions/BootstrapBlazor.Socket/Extensions/DataPropertyExtensions.cs similarity index 57% rename from src/extensions/BootstrapBlazor.Socket/Extensions/SocketDataPropertyExtensions.cs rename to src/extensions/BootstrapBlazor.Socket/Extensions/DataPropertyExtensions.cs index 77d57b4f..76b1e143 100644 --- a/src/extensions/BootstrapBlazor.Socket/Extensions/SocketDataPropertyExtensions.cs +++ b/src/extensions/BootstrapBlazor.Socket/Extensions/DataPropertyExtensions.cs @@ -2,24 +2,24 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; -static class SocketDataPropertyExtensions +static class DataPropertyExtensions { - public static ISocketDataPropertyConverter? GetConverter(this SocketDataPropertyConverterAttribute attribute) + public static IDataPropertyConverter? GetConverter(this DataPropertyConverterAttribute attribute) { return attribute.GetConverterByType() ?? attribute.GetDefaultConverter(); } - private static ISocketDataPropertyConverter? GetConverterByType(this SocketDataPropertyConverterAttribute attribute) + private static IDataPropertyConverter? GetConverterByType(this DataPropertyConverterAttribute attribute) { - ISocketDataPropertyConverter? converter = null; + IDataPropertyConverter? converter = null; var converterType = attribute.ConverterType; if (converterType != null) { var converterParameters = attribute.ConverterParameters; var c = Activator.CreateInstance(converterType, converterParameters); - if(c is ISocketDataPropertyConverter v) + if(c is IDataPropertyConverter v) { converter = v; } @@ -27,65 +27,65 @@ static class SocketDataPropertyExtensions return converter; } - private static ISocketDataPropertyConverter? GetDefaultConverter(this SocketDataPropertyConverterAttribute attribute) + private static IDataPropertyConverter? GetDefaultConverter(this DataPropertyConverterAttribute attribute) { - ISocketDataPropertyConverter? converter = null; + IDataPropertyConverter? converter = null; var type = attribute.Type; if (type != null) { if (type == typeof(byte[])) { - converter = new SocketDataByteArrayConverter(); + converter = new DataByteArrayConverter(); } else if (type == typeof(string)) { - converter = new SocketDataStringConverter(attribute.EncodingName); + converter = new DataStringConverter(attribute.EncodingName); } else if (type.IsEnum) { - converter = new SocketDataEnumConverter(attribute.Type); + converter = new DataEnumConverter(attribute.Type); } else if (type == typeof(bool)) { - converter = new SocketDataBoolConverter(); + converter = new DataBoolConverter(); } else if (type == typeof(short)) { - converter = new SocketDataInt16BigEndianConverter(); + converter = new DataInt16BigEndianConverter(); } else if (type == typeof(int)) { - converter = new SocketDataInt32BigEndianConverter(); + converter = new DataInt32BigEndianConverter(); } else if (type == typeof(long)) { - converter = new SocketDataInt64BigEndianConverter(); + converter = new DataInt64BigEndianConverter(); } else if (type == typeof(float)) { - converter = new SocketDataSingleBigEndianConverter(); + converter = new DataSingleBigEndianConverter(); } else if (type == typeof(double)) { - converter = new SocketDataDoubleBigEndianConverter(); + converter = new DataDoubleBigEndianConverter(); } else if (type == typeof(ushort)) { - converter = new SocketDataUInt16BigEndianConverter(); + converter = new DataUInt16BigEndianConverter(); } else if (type == typeof(uint)) { - converter = new SocketDataUInt32BigEndianConverter(); + converter = new DataUInt32BigEndianConverter(); } else if (type == typeof(ulong)) { - converter = new SocketDataUInt64BigEndianConverter(); + converter = new DataUInt64BigEndianConverter(); } } return converter; } - public static object? ConvertTo(this SocketDataPropertyConverterAttribute attribute, ReadOnlyMemory data) + public static object? ConvertTo(this DataPropertyConverterAttribute attribute, ReadOnlyMemory data) { object? ret = null; var start = attribute.Offset; diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataBoolConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataBoolConverter.cs similarity index 85% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataBoolConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataBoolConverter.cs index c2ee5dd7..b9aa50bc 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataBoolConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataBoolConverter.cs @@ -2,12 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 bool 数据转换器 /// -public class SocketDataBoolConverter : ISocketDataPropertyConverter +public class DataBoolConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataByteArrayConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataByteArrayConverter.cs similarity index 82% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataByteArrayConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataByteArrayConverter.cs index 4d29893c..fb333456 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataByteArrayConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataByteArrayConverter.cs @@ -2,12 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 byte[] 数组转换器 /// -public class SocketDataByteArrayConverter : ISocketDataPropertyConverter +public class DataByteArrayConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataDoubleBigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataDoubleBigEndianConverter.cs similarity index 88% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataDoubleBigEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataDoubleBigEndianConverter.cs index 81a6ad02..1edcb864 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataDoubleBigEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataDoubleBigEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 double 数据大端转换器 /// -public class SocketDataDoubleBigEndianConverter : ISocketDataPropertyConverter +public class DataDoubleBigEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataDoubleLittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataDoubleLittleEndianConverter.cs similarity index 87% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataDoubleLittleEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataDoubleLittleEndianConverter.cs index 22f9cbf0..12e860f1 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataDoubleLittleEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataDoubleLittleEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 double 数据小端转换器 /// -public class SocketDataDoubleLittleEndianConverter : ISocketDataPropertyConverter +public class DataDoubleLittleEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataEnumConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataEnumConverter.cs similarity index 87% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataEnumConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataEnumConverter.cs index 990395e9..4cb53780 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataEnumConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataEnumConverter.cs @@ -2,12 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 Enum 数据转换器 /// -public class SocketDataEnumConverter(Type? type) : ISocketDataPropertyConverter +public class DataEnumConverter(Type? type) : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt16BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt16BigEndianConverter.cs similarity index 88% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt16BigEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt16BigEndianConverter.cs index 084df04f..9019c37c 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt16BigEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt16BigEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 short 数据大端转换器 /// -public class SocketDataInt16BigEndianConverter : ISocketDataPropertyConverter +public class DataInt16BigEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt16LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt16LittleEndianConverter.cs similarity index 88% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt16LittleEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt16LittleEndianConverter.cs index 9a4c9212..88931d9c 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt16LittleEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt16LittleEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 short 数据小端转换器 /// -public class SocketDataInt16LittleEndianConverter : ISocketDataPropertyConverter +public class DataInt16LittleEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt32BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt32BigEndianConverter.cs similarity index 88% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt32BigEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt32BigEndianConverter.cs index 16bc58e2..55263ee6 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt32BigEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt32BigEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 int 数据大端转换器 /// -public class SocketDataInt32BigEndianConverter : ISocketDataPropertyConverter +public class DataInt32BigEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt32LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt32LittleEndianConverter.cs similarity index 88% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt32LittleEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt32LittleEndianConverter.cs index 28836dfc..4a8090f7 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt32LittleEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt32LittleEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 int 数据小端转换器 /// -public class SocketDataInt32LittleEndianConverter : ISocketDataPropertyConverter +public class DataInt32LittleEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt64BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt64BigEndianConverter.cs similarity index 88% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt64BigEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt64BigEndianConverter.cs index a6f2d11f..23821449 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt64BigEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt64BigEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 long 数据大端转换器 /// -public class SocketDataInt64BigEndianConverter : ISocketDataPropertyConverter +public class DataInt64BigEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt64LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt64LittleEndianConverter.cs similarity index 88% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt64LittleEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt64LittleEndianConverter.cs index 34d17007..f8a4e5b4 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataInt64LittleEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt64LittleEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 long 数据小端转换器 /// -public class SocketDataInt64LittleEndianConverter : ISocketDataPropertyConverter +public class DataInt64LittleEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataSingleBigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataSingleBigEndianConverter.cs similarity index 88% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataSingleBigEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataSingleBigEndianConverter.cs index d89ae241..0f43bac9 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataSingleBigEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataSingleBigEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 float 数据大端转换器 /// -public class SocketDataSingleBigEndianConverter : ISocketDataPropertyConverter +public class DataSingleBigEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataSingleLittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataSingleLittleEndianConverter.cs similarity index 87% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataSingleLittleEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataSingleLittleEndianConverter.cs index ae0834ec..a10c36d8 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataSingleLittleEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataSingleLittleEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 float 数据小端转换器 /// -public class SocketDataSingleLittleEndianConverter : ISocketDataPropertyConverter +public class DataSingleLittleEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataStringConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataStringConverter.cs similarity index 83% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataStringConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataStringConverter.cs index 2aa9b547..f10f25f2 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataStringConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataStringConverter.cs @@ -4,12 +4,12 @@ using System.Text; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 string 数据转换器 /// -public class SocketDataStringConverter(string? encodingName) : ISocketDataPropertyConverter +public class DataStringConverter(string? encodingName) : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt16BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt16BigEndianConverter.cs similarity index 88% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt16BigEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt16BigEndianConverter.cs index dedce80a..c11ffaa8 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt16BigEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt16BigEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 ushort 数据大端转换器 /// -public class SocketDataUInt16BigEndianConverter : ISocketDataPropertyConverter +public class DataUInt16BigEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt16LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt16LittleEndianConverter.cs similarity index 87% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt16LittleEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt16LittleEndianConverter.cs index b4344f64..9babd257 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt16LittleEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt16LittleEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 ushort 数据小端转换器 /// -public class SocketDataUInt16LittleEndianConverter : ISocketDataPropertyConverter +public class DataUInt16LittleEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt32BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt32BigEndianConverter.cs similarity index 88% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt32BigEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt32BigEndianConverter.cs index cd8829f0..c374842c 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt32BigEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt32BigEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 uint 数据大端转换器 /// -public class SocketDataUInt32BigEndianConverter : ISocketDataPropertyConverter +public class DataUInt32BigEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt32LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt32LittleEndianConverter.cs similarity index 87% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt32LittleEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt32LittleEndianConverter.cs index 80e5e27b..5c942aea 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt32LittleEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt32LittleEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 uint 数据小端转换器 /// -public class SocketDataUInt32LittleEndianConverter : ISocketDataPropertyConverter +public class DataUInt32LittleEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt64BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt64BigEndianConverter.cs similarity index 88% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt64BigEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt64BigEndianConverter.cs index e85609d9..12833ad7 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt64BigEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt64BigEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 ulong 数据大端转换器 /// -public class SocketDataUInt64BigEndianConverter : ISocketDataPropertyConverter +public class DataUInt64BigEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt64LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt64LittleEndianConverter.cs similarity index 87% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt64LittleEndianConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt64LittleEndianConverter.cs index 0b546006..5d0f654c 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/SocketDataUInt64LittleEndianConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt64LittleEndianConverter.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Sokcet 数据转换为 ulong 数据小端转换器 /// -public class SocketDataUInt64LittleEndianConverter : ISocketDataPropertyConverter +public class DataUInt64LittleEndianConverter : IDataPropertyConverter { /// /// diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/ISocketDataPropertyConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/IDataPropertyConverter.cs similarity index 85% rename from src/extensions/BootstrapBlazor.Socket/PropertyConverter/ISocketDataPropertyConverter.cs rename to src/extensions/BootstrapBlazor.Socket/PropertyConverter/IDataPropertyConverter.cs index 1cf8675c..7bcd794d 100644 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/ISocketDataPropertyConverter.cs +++ b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/IDataPropertyConverter.cs @@ -2,12 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.DataConverters; /// /// Socket 数据转换器接口 /// -public interface ISocketDataPropertyConverter +public interface IDataPropertyConverter { /// /// 将数据转换为指定类型的对象 diff --git a/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj b/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj index 6d29677a..33f5d97a 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj +++ b/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj @@ -38,4 +38,10 @@ + + + + + + diff --git a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClient.cs b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClient.cs index ce6fe1d0..30e0830c 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClient.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClient.cs @@ -8,7 +8,7 @@ using System.Net; using System.Runtime.Versioning; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.TcpSocket; [UnsupportedOSPlatform("browser")] class DefaultTcpSocketClient(TcpSocketClientOptions options) : IServiceProvider, ITcpSocketClient diff --git a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClientProvider.cs b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClientProvider.cs index 1c3bb319..5d31cf4d 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClientProvider.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClientProvider.cs @@ -6,7 +6,7 @@ using System.Net.Sockets; using System.Runtime.Versioning; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.TcpSocket; /// /// TcpSocket 客户端默认实现 diff --git a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketFactory.cs b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketFactory.cs index 62b62817..627a579b 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketFactory.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketFactory.cs @@ -5,7 +5,7 @@ using System.Collections.Concurrent; using System.Runtime.Versioning; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.TcpSocket; [UnsupportedOSPlatform("browser")] sealed class DefaultTcpSocketFactory(IServiceProvider provider) : ITcpSocketFactory diff --git a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs index ee61009a..735524e4 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs @@ -9,7 +9,7 @@ using System.Runtime.Versioning; using System.Text; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.TcpSocket; /// /// 扩展方法类 @@ -90,7 +90,7 @@ public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPack /// 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, ISocketDataConverter socketDataConverter, Func callback) + public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, IDataConverter socketDataConverter, Func callback) { // 设置 ITcpSocketClient 的回调函数 client.ReceivedCallBack = async buffer => @@ -117,7 +117,7 @@ public static void SetDataPackageAdapter(this ITcpSocketClient client, /// /// This method sets up the to use the specified for handling incoming data. If the type is decorated with a , the associated converter is used to transform the data before invoking + /// cref="DataTypeConverterAttribute"/>, the associated converter is used to transform the data before invoking /// the . The callback is called with the converted entity or if /// conversion fails. /// The type of entity that the data package adapter will handle. @@ -133,14 +133,14 @@ public static void SetDataPackageAdapter(this ITcpSocketClient client, await adapter.HandlerAsync(buffer); }; - ISocketDataConverter? converter = null; + IDataConverter? converter = null; var type = typeof(TEntity); - var converterType = type.GetCustomAttribute(); + var converterType = type.GetCustomAttribute(); if (converterType is { Type: not null }) { // 如果类型上有 SocketDataTypeConverterAttribute 特性则使用特性中指定的转换器 - if (Activator.CreateInstance(converterType.Type) is ISocketDataConverter socketDataConverter) + if (Activator.CreateInstance(converterType.Type) is IDataConverter socketDataConverter) { converter = socketDataConverter; } @@ -163,7 +163,7 @@ public static void SetDataPackageAdapter(this ITcpSocketClient client, } } - private static void SetDataAdapterCallback(this IDataPackageAdapter adapter, ISocketDataConverter converter, Func callback) + private static void SetDataAdapterCallback(this IDataPackageAdapter adapter, IDataConverter converter, Func callback) { adapter.ReceivedCallBack = async buffer => { @@ -176,12 +176,12 @@ private static void SetDataAdapterCallback(this IDataPackageAdapter ada }; } - private static ISocketDataConverter? GetSocketDataConverter(this ITcpSocketClient client) + private static IDataConverter? GetSocketDataConverter(this ITcpSocketClient client) { - ISocketDataConverter? converter = null; + IDataConverter? converter = null; if (client is IServiceProvider provider) { - var converters = provider.GetRequiredService>().Value; + var converters = provider.GetRequiredService>().Value; if (converters.TryGetTypeConverter(out var v)) { converter = v; diff --git a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs index 8725151f..0400373f 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs @@ -4,10 +4,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Options; using System.Runtime.Versioning; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.TcpSocket; /// /// TcpSocket 扩展方法 @@ -32,12 +31,12 @@ public static IServiceCollection AddBootstrapBlazorTcpSocketFactory(this IServic } /// - /// 配置第三方数据模型与 数据转换器集合配置扩展方法 + /// 配置第三方数据模型与 数据转换器集合配置扩展方法 /// /// /// /// - public static IServiceCollection ConfigureSocketDataConverters(this IServiceCollection services, Action configureOptions) + public static IServiceCollection ConfigureSocketDataConverters(this IServiceCollection services, Action configureOptions) { services.Configure(configureOptions); return services; diff --git a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/Utility.cs b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/Utility.cs index 15730185..748b2ae5 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/Utility.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/Utility.cs @@ -6,7 +6,7 @@ using System.Net.Sockets; using System.Runtime.Versioning; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.TcpSocket; /// /// Utility 帮助类 diff --git a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClient.cs b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClient.cs index 17e1a6c6..dcf58028 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClient.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClient.cs @@ -4,7 +4,7 @@ using System.Net; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.TcpSocket; /// /// Represents a TCP socket for network communication. diff --git a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClientProvider.cs b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClientProvider.cs index 93d34814..14647fa6 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClientProvider.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClientProvider.cs @@ -4,7 +4,7 @@ using System.Net; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.TcpSocket; /// /// Defines the contract for a socket client that provides asynchronous methods for connecting, sending, receiving, and diff --git a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketFactory.cs b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketFactory.cs index 05c9ec9c..b791809c 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketFactory.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketFactory.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.TcpSocket; /// /// ITcpSocketFactory Interface diff --git a/src/extensions/BootstrapBlazor.TcpSocket/TcpSocketClientOptions.cs b/src/extensions/BootstrapBlazor.TcpSocket/TcpSocketClientOptions.cs index f91b1025..40a43441 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/TcpSocketClientOptions.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/TcpSocketClientOptions.cs @@ -4,7 +4,7 @@ using System.Net; -namespace BootstrapBlazor.Components; +namespace BootstrapBlazor.TcpSocket; /// /// Represents configuration options for a socket client, including buffer sizes, timeouts, and endpoints. diff --git a/test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs b/test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs index 7ad0b42c..a892c587 100644 --- a/test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs +++ b/test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs @@ -17,12 +17,12 @@ public void TryGetConverter_Ok() sc.ConfigureSocketDataConverters(options => { options.AddTypeConverter(); - options.AddPropertyConverter(entity => entity.Header, new SocketDataPropertyConverterAttribute() + options.AddPropertyConverter(entity => entity.Header, new DataPropertyConverterAttribute() { Offset = 0, Length = 5 }); - options.AddPropertyConverter(entity => entity.Body, new SocketDataPropertyConverterAttribute() + options.AddPropertyConverter(entity => entity.Body, new DataPropertyConverterAttribute() { Offset = 5, Length = 2 @@ -30,7 +30,7 @@ public void TryGetConverter_Ok() // 为提高代码覆盖率 重复添加转换器以后面的为准 options.AddTypeConverter(); - options.AddPropertyConverter(entity => entity.Header, new SocketDataPropertyConverterAttribute() + options.AddPropertyConverter(entity => entity.Header, new DataPropertyConverterAttribute() { Offset = 0, Length = 5 @@ -38,7 +38,7 @@ public void TryGetConverter_Ok() }); var provider = sc.BuildServiceProvider(); - var service = provider.GetRequiredService>(); + var service = provider.GetRequiredService>(); Assert.NotNull(service.Value); var ret = service.Value.TryGetTypeConverter(out var converter); diff --git a/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs b/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs index 0cb56b73..6249c8ae 100644 --- a/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs +++ b/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs @@ -654,7 +654,7 @@ public async Task TryConvertTo_Ok() { DataPackageHandler = new FixLengthDataPackageHandler(29), }; - client.SetDataPackageAdapter(adapter, new SocketDataConverter(), t => + client.SetDataPackageAdapter(adapter, new DataConverter(), t => { entity = t; tcs.SetResult(); @@ -733,7 +733,7 @@ public async Task TryConvertTo_Ok() // 测试数据适配器直接调用 TryConvertTo 方法转换数据 var adapter2 = new DataPackageAdapter(); - var result = adapter2.TryConvertTo(data, new SocketDataConverter(), out var t); + var result = adapter2.TryConvertTo(data, new DataConverter(), out var t); Assert.True(result); Assert.NotNull(t); Assert.Equal([1, 2, 3, 4, 5], entity.Header); @@ -767,15 +767,15 @@ public async Task TryGetTypeConverter_Ok() var client = CreateClient(builder => { - builder.Configure(options => + builder.Configure(options => { options.AddTypeConverter(); - options.AddPropertyConverter(entity => entity.Header, new SocketDataPropertyConverterAttribute() + options.AddPropertyConverter(entity => entity.Header, new DataPropertyConverterAttribute() { Offset = 0, Length = 5 }); - options.AddPropertyConverter(entity => entity.Body, new SocketDataPropertyConverterAttribute() + options.AddPropertyConverter(entity => entity.Body, new DataPropertyConverterAttribute() { Offset = 5, Length = 2 @@ -1200,58 +1200,58 @@ public void SetReceive(bool state) } } - [SocketDataTypeConverter(Type = typeof(SocketDataConverter))] + [DataTypeConverter(Type = typeof(DataConverter))] class MockEntity { - [SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 0, Length = 5)] + [DataPropertyConverter(Type = typeof(byte[]), Offset = 0, Length = 5)] public byte[]? Header { get; set; } - [SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 5, Length = 2)] + [DataPropertyConverter(Type = typeof(byte[]), Offset = 5, Length = 2)] public byte[]? Body { get; set; } - [SocketDataPropertyConverter(Type = typeof(string), Offset = 7, Length = 1, EncodingName = "utf-8")] + [DataPropertyConverter(Type = typeof(string), Offset = 7, Length = 1, EncodingName = "utf-8")] public string? Value1 { get; set; } - [SocketDataPropertyConverter(Type = typeof(int), Offset = 8, Length = 1)] + [DataPropertyConverter(Type = typeof(int), Offset = 8, Length = 1)] public int Value2 { get; set; } - [SocketDataPropertyConverter(Type = typeof(long), Offset = 9, Length = 1)] + [DataPropertyConverter(Type = typeof(long), Offset = 9, Length = 1)] public long Value3 { get; set; } - [SocketDataPropertyConverter(Type = typeof(double), Offset = 10, Length = 8)] + [DataPropertyConverter(Type = typeof(double), Offset = 10, Length = 8)] public double Value4 { get; set; } - [SocketDataPropertyConverter(Type = typeof(float), Offset = 18, Length = 4)] + [DataPropertyConverter(Type = typeof(float), Offset = 18, Length = 4)] public float Value5 { get; set; } - [SocketDataPropertyConverter(Type = typeof(short), Offset = 22, Length = 1)] + [DataPropertyConverter(Type = typeof(short), Offset = 22, Length = 1)] public short Value6 { get; set; } - [SocketDataPropertyConverter(Type = typeof(ushort), Offset = 23, Length = 1)] + [DataPropertyConverter(Type = typeof(ushort), Offset = 23, Length = 1)] public ushort Value7 { get; set; } - [SocketDataPropertyConverter(Type = typeof(uint), Offset = 24, Length = 1)] + [DataPropertyConverter(Type = typeof(uint), Offset = 24, Length = 1)] public uint Value8 { get; set; } - [SocketDataPropertyConverter(Type = typeof(ulong), Offset = 25, Length = 1)] + [DataPropertyConverter(Type = typeof(ulong), Offset = 25, Length = 1)] public ulong Value9 { get; set; } - [SocketDataPropertyConverter(Type = typeof(bool), Offset = 26, Length = 1)] + [DataPropertyConverter(Type = typeof(bool), Offset = 26, Length = 1)] public bool Value10 { get; set; } - [SocketDataPropertyConverter(Type = typeof(EnumEducation), Offset = 27, Length = 1)] + [DataPropertyConverter(Type = typeof(EnumEducation), Offset = 27, Length = 1)] public EnumEducation Value11 { get; set; } - [SocketDataPropertyConverter(Type = typeof(Foo), Offset = 28, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])] + [DataPropertyConverter(Type = typeof(Foo), Offset = 28, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])] public Foo? Value12 { get; set; } - [SocketDataPropertyConverter(Type = typeof(string), Offset = 7, Length = 1)] + [DataPropertyConverter(Type = typeof(string), Offset = 7, Length = 1)] public string? Value14 { get; set; } public string? Value13 { get; set; } } - class MockSocketDataConverter : SocketDataConverter + class MockSocketDataConverter : DataConverter { protected override bool Parse(ReadOnlyMemory data, MockEntity entity) { @@ -1259,7 +1259,7 @@ protected override bool Parse(ReadOnlyMemory data, MockEntity entity) } } - class FooConverter(string name) : ISocketDataPropertyConverter + class FooConverter(string name) : IDataPropertyConverter { public object? Convert(ReadOnlyMemory data) { diff --git a/test/UnitTestTcpSocket/TcpSocketPropertyConverterTest.cs b/test/UnitTestTcpSocket/TcpSocketPropertyConverterTest.cs index d0fb0eb7..cfd3efc0 100644 --- a/test/UnitTestTcpSocket/TcpSocketPropertyConverterTest.cs +++ b/test/UnitTestTcpSocket/TcpSocketPropertyConverterTest.cs @@ -9,7 +9,7 @@ public class TcpSocketPropertyConverterTest [Fact] public void UInt16Converter_Ok() { - var converter = new SocketDataUInt16LittleEndianConverter(); + var converter = new DataUInt16LittleEndianConverter(); var actual = converter.Convert(new byte[] { 0xFF, 0x00 }); Assert.Equal((ushort)0xFF, actual); } @@ -17,7 +17,7 @@ public void UInt16Converter_Ok() [Fact] public void Int16Converter_Ok() { - var converter = new SocketDataInt16LittleEndianConverter(); + var converter = new DataInt16LittleEndianConverter(); var actual = converter.Convert(new byte[] { 0xFF, 0x00 }); Assert.Equal((short)0xFF, actual); } @@ -25,7 +25,7 @@ public void Int16Converter_Ok() [Fact] public void UInt32Converter_Ok() { - var converter = new SocketDataUInt32LittleEndianConverter(); + var converter = new DataUInt32LittleEndianConverter(); var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00 }); Assert.Equal((uint)0xFF, actual); } @@ -33,7 +33,7 @@ public void UInt32Converter_Ok() [Fact] public void Int32Converter_Ok() { - var converter = new SocketDataInt32LittleEndianConverter(); + var converter = new DataInt32LittleEndianConverter(); var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00 }); Assert.Equal(0xFF, actual); } @@ -41,7 +41,7 @@ public void Int32Converter_Ok() [Fact] public void UInt64Converter_Ok() { - var converter = new SocketDataUInt64LittleEndianConverter(); + var converter = new DataUInt64LittleEndianConverter(); var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); Assert.Equal((ulong)0xFF, actual); } @@ -49,7 +49,7 @@ public void UInt64Converter_Ok() [Fact] public void Int64Converter_Ok() { - var converter = new SocketDataInt64LittleEndianConverter(); + var converter = new DataInt64LittleEndianConverter(); var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); Assert.Equal((long)0xFF, actual); } @@ -57,7 +57,7 @@ public void Int64Converter_Ok() [Fact] public void SingleConverter_Ok() { - var converter = new SocketDataSingleLittleEndianConverter(); + var converter = new DataSingleLittleEndianConverter(); var actual = converter.Convert(new byte[] { 0xC3, 0xF5, 0x48, 0x40 }); Assert.Equal((float)3.14, actual); } @@ -65,7 +65,7 @@ public void SingleConverter_Ok() [Fact] public void DoubleConverter_Ok() { - var converter = new SocketDataDoubleLittleEndianConverter(); + var converter = new DataDoubleLittleEndianConverter(); var actual = converter.Convert(new byte[] { 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }); Assert.Equal((double)3.14, actual); } From 4d3bdc34f676ee5a1ab100b6867c1f88904fc92a Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 23 Jul 2025 17:37:13 +0800 Subject: [PATCH 4/5] =?UTF-8?q?chore:=20=E5=A2=9E=E5=8A=A0=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E7=A9=BA=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTestTcpSocket/UnitTestTcpSocket.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/UnitTestTcpSocket/UnitTestTcpSocket.csproj b/test/UnitTestTcpSocket/UnitTestTcpSocket.csproj index be223f3d..3a0e5dc4 100644 --- a/test/UnitTestTcpSocket/UnitTestTcpSocket.csproj +++ b/test/UnitTestTcpSocket/UnitTestTcpSocket.csproj @@ -19,6 +19,11 @@ + + + + + From 2e0912b8fa95d8a264063fe7332cd4fe240e550f Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 23 Jul 2025 18:06:52 +0800 Subject: [PATCH 5/5] =?UTF-8?q?chore:=20=E6=9B=B4=E6=94=B9=20Utility=20?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BootstrapBlazor.Socket.csproj | 2 +- .../BootstrapBlazor.TcpSocket.csproj | 2 +- .../Extensions/ITcpSocketClientExtensions.cs | 2 +- .../Extensions/TcpSocketExtensions.cs | 2 +- .../Extensions/{Utility.cs => TcpSocketUtility.cs} | 4 ++-- .../DefaultSocketClientProviderTest.cs | 2 +- .../SocketDataConverterCollectionsTest.cs | 2 +- test/UnitTestTcpSocket/TcpSocketFactoryTest.cs | 10 +++++----- .../{UtiityTest.cs => TcpSocketUtiityTest.cs} | 10 +++++----- 9 files changed, 18 insertions(+), 18 deletions(-) rename src/extensions/BootstrapBlazor.TcpSocket/Extensions/{Utility.cs => TcpSocketUtility.cs} (98%) rename test/UnitTestTcpSocket/{UtiityTest.cs => TcpSocketUtiityTest.cs} (62%) diff --git a/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj b/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj index 940fb5fe..284ada07 100644 --- a/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj +++ b/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj @@ -1,7 +1,7 @@  - 9.0.0 + 9.0.0-beta01 diff --git a/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj b/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj index 33f5d97a..00d1631a 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj +++ b/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj @@ -1,7 +1,7 @@  - 9.0.0 + 9.0.0-beta01 diff --git a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs index 735524e4..f02ecb34 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs @@ -48,7 +48,7 @@ public static ValueTask SendAsync(this ITcpSocketClient client, string con /// is successfully established; otherwise, . public static ValueTask ConnectAsync(this ITcpSocketClient client, string ipString, int port, CancellationToken token = default) { - var endPoint = Utility.ConvertToIpEndPoint(ipString, port); + var endPoint = TcpSocketUtility.ConvertToIpEndPoint(ipString, port); return client.ConnectAsync(endPoint, token); } diff --git a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs index 0400373f..3371ccb4 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs @@ -36,7 +36,7 @@ public static IServiceCollection AddBootstrapBlazorTcpSocketFactory(this IServic /// /// /// - public static IServiceCollection ConfigureSocketDataConverters(this IServiceCollection services, Action configureOptions) + public static IServiceCollection ConfigureDataConverters(this IServiceCollection services, Action configureOptions) { services.Configure(configureOptions); return services; diff --git a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/Utility.cs b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketUtility.cs similarity index 98% rename from src/extensions/BootstrapBlazor.TcpSocket/Extensions/Utility.cs rename to src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketUtility.cs index 748b2ae5..1118828a 100644 --- a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/Utility.cs +++ b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketUtility.cs @@ -9,9 +9,9 @@ namespace BootstrapBlazor.TcpSocket; /// -/// Utility 帮助类 +/// SocketUtility 帮助类 /// -public static class Utility +public static class TcpSocketUtility { /// /// Converts a string representation of an IP address or hostname into an object. diff --git a/test/UnitTestTcpSocket/DefaultSocketClientProviderTest.cs b/test/UnitTestTcpSocket/DefaultSocketClientProviderTest.cs index 859dfd64..61658a22 100644 --- a/test/UnitTestTcpSocket/DefaultSocketClientProviderTest.cs +++ b/test/UnitTestTcpSocket/DefaultSocketClientProviderTest.cs @@ -39,7 +39,7 @@ public async Task ReceiveAsync_Ok() var factory = provider.GetRequiredService(); var client = factory.GetOrCreate("provider", op => { - op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0); + op.LocalEndPoint = TcpSocketUtility.ConvertToIpEndPoint("localhost", 0); op.IsAutoReceive = false; op.EnableLog = false; }); diff --git a/test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs b/test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs index a892c587..0be74daa 100644 --- a/test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs +++ b/test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs @@ -14,7 +14,7 @@ public class SocketDataConverterCollectionsTest public void TryGetConverter_Ok() { var sc = new ServiceCollection(); - sc.ConfigureSocketDataConverters(options => + sc.ConfigureDataConverters(options => { options.AddTypeConverter(); options.AddPropertyConverter(entity => entity.Header, new DataPropertyConverterAttribute() diff --git a/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs b/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs index 6249c8ae..76673d60 100644 --- a/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs +++ b/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs @@ -25,17 +25,17 @@ public async Task GetOrCreate_Ok() sc.AddBootstrapBlazorTcpSocketFactory(); var provider = sc.BuildServiceProvider(); var factory = provider.GetRequiredService(); - var client1 = factory.GetOrCreate("demo", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0)); + var client1 = factory.GetOrCreate("demo", op => op.LocalEndPoint = TcpSocketUtility.ConvertToIpEndPoint("localhost", 0)); await client1.CloseAsync(); - var client2 = factory.GetOrCreate("demo", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0)); + var client2 = factory.GetOrCreate("demo", op => op.LocalEndPoint = TcpSocketUtility.ConvertToIpEndPoint("localhost", 0)); Assert.Equal(client1, client2); var ip = Dns.GetHostAddresses(Dns.GetHostName(), AddressFamily.InterNetwork).FirstOrDefault() ?? IPAddress.Loopback; - var client3 = factory.GetOrCreate("demo1", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint(ip.ToString(), 0)); + var client3 = factory.GetOrCreate("demo1", op => op.LocalEndPoint = TcpSocketUtility.ConvertToIpEndPoint(ip.ToString(), 0)); // 测试不合格 IP 地址 - var client4 = factory.GetOrCreate("demo2", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint("256.0.0.1", 0)); + var client4 = factory.GetOrCreate("demo2", op => op.LocalEndPoint = TcpSocketUtility.ConvertToIpEndPoint("256.0.0.1", 0)); var client5 = factory.Remove("demo2"); Assert.Equal(client4, client5); @@ -959,7 +959,7 @@ private static ITcpSocketClient CreateClient(Action? builder var factory = provider.GetRequiredService(); var client = factory.GetOrCreate("test", op => { - op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0); + op.LocalEndPoint = TcpSocketUtility.ConvertToIpEndPoint("localhost", 0); op.EnableLog = true; optionConfigure?.Invoke(op); }); diff --git a/test/UnitTestTcpSocket/UtiityTest.cs b/test/UnitTestTcpSocket/TcpSocketUtiityTest.cs similarity index 62% rename from test/UnitTestTcpSocket/UtiityTest.cs rename to test/UnitTestTcpSocket/TcpSocketUtiityTest.cs index 15ba0e0f..1f259696 100644 --- a/test/UnitTestTcpSocket/UtiityTest.cs +++ b/test/UnitTestTcpSocket/TcpSocketUtiityTest.cs @@ -6,27 +6,27 @@ namespace UnitTestTcpSocket; -public class UtiityTest +public class TcpSocketUtiityTest { [Fact] public void ConvertToIPAddress_Ok() { - var ex = Assert.Throws(() => Utility.ConvertToIPAddress("")); + var ex = Assert.Throws(() => TcpSocketUtility.ConvertToIPAddress("")); Assert.NotNull(ex); - var address = Utility.ConvertToIPAddress("any"); + var address = TcpSocketUtility.ConvertToIPAddress("any"); Assert.Equal(IPAddress.Any, address); } [Fact] public void ConvertToIpEndPoint_Ok() { - var ex = Assert.Throws(() => Utility.ConvertToIpEndPoint("localhost", 88990)); + var ex = Assert.Throws(() => TcpSocketUtility.ConvertToIpEndPoint("localhost", 88990)); Assert.NotNull(ex); ex = null; - ex = Assert.Throws(() => Utility.ConvertToIpEndPoint("localhost", -1000)); + ex = Assert.Throws(() => TcpSocketUtility.ConvertToIpEndPoint("localhost", -1000)); Assert.NotNull(ex); } }