Skip to content

Commit 4bc092d

Browse files
authored
feat(SocketDataConveter): remove abstract keyword (#6447)
* refactor: 更改 SocketDataConverter 虚类 * doc: 更新文档 * doc: 更新相关文档说明 * test: 提高代码覆盖率
1 parent f1ad761 commit 4bc092d

File tree

8 files changed

+81
-43
lines changed

8 files changed

+81
-43
lines changed

src/BootstrapBlazor.Server/Components/Samples/SocketFactories.razor

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,15 @@ private async Task CreateClient()
5454
// 创建 ITcpSocketClient 实例
5555
var client = TcpSocketFactory.GetOrCreate("localhost", 0);
5656

57-
// 设置 FixLengthDataPackageHandler 数据处理器处理数据定长 4 的数据
58-
client.SetDataHandler(new FixLengthDataPackageHandler(4)
57+
// 设置数据适配器 使用 FixLengthDataPackageHandler 数据处理器处理数据定长 4 的数据
58+
var adapter = new DataPackageAdapter
5959
{
60-
// 设置接收数据回调方法
61-
ReceivedCallBack = buffer =>
62-
{
63-
// 内部自动处理粘包分包问题,这里参数 buffer 一定是定长为 4 的业务数据
64-
receivedBuffer = buffer;
65-
return Task.CompletedTask;
66-
}
60+
DataPackageHandler = new FixLengthDataPackageHandler(4)
61+
};
62+
client.SetDataPackageAdapter(adapter, buffer =>
63+
{
64+
// buffer 即是接收到的数据
65+
return ValueTask.CompletedTask;
6766
});
6867

6968
// 连接远端节点 连接成功后自动开始接收数据
@@ -78,8 +77,36 @@ private async Task CreateClient()
7877
<li><code>DelimiterDataPackageHandler</code> <b>分隔符数据处理器</b> 即通讯包以特定一个或一组字节分割</li>
7978
</ul>
8079

81-
<p class="code-label">5. 数据适配器(设计中)</p>
80+
<p class="code-label">5. 数据适配器</p>
8281

8382
<p>在我们实际应用中,接收到数据包后(已经过数据处理器)大多情况下是需要将电文转化为应用中的具体数据类型 <code>Class</code> 或 <code>Struct</code>。将原始数据包转化为类或者结构体的过程由我们的数据适配器来实现</p>
8483

8584
<p>数据适配器设计思路如下</p>
85+
86+
<ol class="ul-demo">
87+
<li>使用 <code>SocketDataConverterAttribute</code> 标签约定通讯数据使用那个转换类型进行转换 指定类型需继承 <code>ISocketDataConverter</code>
88+
接口
89+
</li>
90+
<li>使用 <code>SocketDataPropertyAttribute</code> 标签约定如何转换数据类型 (Property) 属性值</li>
91+
</ol>
92+
93+
<Pre>[SocketDataConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
94+
class MockEntity
95+
{
96+
[SocketDataProperty(Type = typeof(byte[]), Offset = 0, Length = 5)]
97+
public byte[]? Header { get; set; }
98+
99+
[SocketDataProperty(Type = typeof(byte[]), Offset = 5, Length = 2)]
100+
public byte[]? Body { get; set; }
101+
102+
[SocketDataProperty(Type = typeof(Foo), Offset = 7, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])]
103+
public string? Value1 { get; set; }
104+
}</Pre>
105+
106+
<Pre>class FooConverter(string name) : ISocketDataPropertyConverter
107+
{
108+
public object? Convert(ReadOnlyMemory&lt;byte&gt; data)
109+
{
110+
return new Foo() { Id = data.Span[0], Name = name };
111+
}
112+
}</Pre>

src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141
</ul>
4242
<p>本示例服务器端模拟了数据分包即响应数据实际是两次写入所以实际接收端是要通过两次接收才能得到一个完整的响应数据包,可通过 <b>数据适配器</b> 来简化接收逻辑。通过切换下方 <b>是否使用数据适配器</b> 控制开关进行测试查看实际数据接收情况。</p>
4343
<ul class="ul-demo">
44-
<li>不使用 <b>数据处理器</b> 要分两次接收才能接收完整</li>
45-
<li>使用 <b>数据处理器</b> 一次即可接收完整数据包</li>
44+
<li>不使用 <b>数据适配器</b> 要分两次接收才能接收完整</li>
45+
<li>使用 <b>数据适配器</b> 一次即可接收完整数据包</li>
4646
</ul>
4747
<Pre>private readonly DataPackageAdapter _dataAdapter = new()
4848
{

src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ protected override void OnInitialized()
4848
});
4949
_client.ReceivedCallBack += OnReceivedAsync;
5050

51-
_dataAdapter.ReceivedCallBack += async Data =>
51+
_dataAdapter.ReceivedCallBack += async data =>
5252
{
5353
// 直接处理接收的数据
54-
await UpdateReceiveLog(Data);
54+
await UpdateReceiveLog(data);
5555
};
5656
}
5757

@@ -104,7 +104,7 @@ private async ValueTask OnReceivedAsync(ReadOnlyMemory<byte> data)
104104

105105
private async Task UpdateReceiveLog(ReadOnlyMemory<byte> data)
106106
{
107-
var payload = System.Text.Encoding.UTF8.GetString(data.Span);
107+
var payload = Encoding.UTF8.GetString(data.Span);
108108
var body = BitConverter.ToString(data.ToArray());
109109

110110
_items.Add(new ConsoleMessageItem

src/BootstrapBlazor.Server/Components/Samples/Sockets/DataEntities.razor

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
Name="Normal" ShowCode="false">
1010
<p>通过 <code>SocketDataConverterAttribute</code> 类标签与 <code>SocketDataPropertyAttribute</code>
1111
属性标签可以将通讯数据自动转化为我们系统中需要的业务实体类,示例如下:</p>
12-
<Pre>[SocketDataConverter(Type = typeof(MockEntitySocketDataConverter))]
12+
<Pre>[SocketDataConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
1313
class MockEntity
1414
{
1515
[SocketDataProperty(Type = typeof(byte[]), Offset = 0, Length = 5)]
@@ -26,8 +26,7 @@ class MockEntity
2626
}</Pre>
2727
<p class="code-label">1. <code>SocketDataConverter</code> 参数说明:</p>
2828
<ul class="ul-demo">
29-
<li><b>Type</b>: 自定义转换器类型,组件库内置了 <code>SocketDataConverterBase</code> 泛型基类,继承后非常方便扩展出自己的转换器,重载
30-
<code>TryConvertTo</code> 方法调用基类的 <code>Parse</code> 基本就完成了 99% 的工作量,也可以完全自己解析通讯电文到实体类的各个属性上
29+
<li><b>Type</b>: 自定义转换器类型,组件库内置了 <code>SocketDataConverter</code> 泛型类,建议看一下源码非常方便扩展出自己的转换器
3130
</li>
3231
</ul>
3332
<Pre>class MockEntitySocketDataConverter : SocketDataConverterBase&lt;MockEntity&gt;
@@ -96,7 +95,7 @@ class MockEntity
9695
<li><code>SocketDataUInt64LittleEndianConverter</code> 转成 ulong 无符号整形小端读取</li>
9796
</ul>
9897
<p>自定义数据类型转化器示例</p>
99-
<Pre>[SocketDataConverter(Type = typeof(MockEntitySocketDataConverter))]
98+
<Pre>[SocketDataConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
10099
class MockEntity
101100
{
102101
[SocketDataProperty(Type = typeof(byte[]), Offset = 0, Length = 5)]
@@ -107,9 +106,8 @@ class MockEntity
107106

108107
[SocketDataProperty(Type = typeof(Foo), Offset = 7, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])]
109108
public string? Value1 { get; set; }
110-
}
111-
112-
class FooConverter(string name) : ISocketDataPropertyConverter
109+
}</Pre>
110+
<Pre>class FooConverter(string name) : ISocketDataPropertyConverter
113111
{
114112
public object? Convert(ReadOnlyMemory&lt;byte&gt; data)
115113
{

src/BootstrapBlazor.Server/Locales/zh-CN.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4839,7 +4839,7 @@
48394839
"SocketComponents": "Socket 服务",
48404840
"SocketAutoReceive": "自动接收数据",
48414841
"SocketManualReceive": "手动接收数据",
4842-
"DataPackageAdapter": "数据处理器",
4842+
"DataPackageAdapter": "数据适配器",
48434843
"SocketAutoConnect": "自动重连",
48444844
"SocketDataEntity": "通讯数据转实体类",
48454845
"NetworkMonitor": "网络状态 NetworkMonitor"

src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverterBase.cs renamed to src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverter.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,27 @@ namespace BootstrapBlazor.Components;
1111
/// Provides a base class for converting socket data into a specified entity type.
1212
/// </summary>
1313
/// <typeparam name="TEntity">The type of entity to convert the socket data into.</typeparam>
14-
public abstract class SocketDataConverterBase<TEntity> : ISocketDataConverter<TEntity>
14+
public class SocketDataConverter<TEntity> : ISocketDataConverter<TEntity>
1515
{
1616
/// <summary>
1717
/// <inheritdoc/>
1818
/// </summary>
1919
/// <param name="data"></param>
2020
/// <param name="entity"></param>
2121
/// <returns></returns>
22-
public abstract bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out TEntity? entity);
22+
public virtual bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out TEntity? entity)
23+
{
24+
var v = CreateEntity();
25+
var ret = Parse(data, v);
26+
entity = ret ? v : default;
27+
return ret;
28+
}
29+
30+
/// <summary>
31+
/// 创建实体实例方法
32+
/// </summary>
33+
/// <returns></returns>
34+
protected virtual TEntity CreateEntity() => Activator.CreateInstance<TEntity>();
2335

2436
/// <summary>
2537
/// 将字节数据转换为指定实体类型的实例。

src/BootstrapBlazor/Services/TcpSocket/SocketClientOptions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace BootstrapBlazor.Components;
1010
/// <summary>
1111
/// Represents configuration options for a socket client, including buffer sizes, timeouts, and endpoints.
1212
/// </summary>
13-
/// <remarks>Use this class to configure various settings for a socket client, such as connection timeouts,
13+
/// <remarks>Use this class to configure various settings for a socket client, such as connection timeouts,
1414
/// buffer sizes, and local or remote endpoints. These options allow fine-tuning of socket behavior to suit specific
1515
/// networking scenarios.</remarks>
1616
public class SocketClientOptions
@@ -49,14 +49,14 @@ public class SocketClientOptions
4949
/// Gets or sets the local endpoint for the socket client. Default value is <see cref="IPAddress.Any"/>
5050
/// </summary>
5151
/// <remarks>This property specifies the local network endpoint that the socket client will bind to when establishing a connection.</remarks>
52-
public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
52+
public IPEndPoint LocalEndPoint { get; set; } = new(IPAddress.Any, 0);
5353

5454
/// <summary>
5555
/// Gets or sets a value indicating whether logging is enabled. Default value is false.
5656
/// </summary>
5757
public bool EnableLog { get; set; }
58-
59-
/// <summary>
58+
59+
/// <summary>
6060
/// Gets or sets a value indicating whether the system should automatically attempt to reconnect after a connection is lost. Default value is false.
6161
/// </summary>
6262
public bool IsAutoReconnect { get; set; }

test/UnitTest/Services/TcpSocketFactoryTest.cs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,7 @@ public async Task TryConvertTo_Ok()
655655
{
656656
DataPackageHandler = new FixLengthDataPackageHandler(29),
657657
};
658-
client.SetDataPackageAdapter(adapter, new MockEntitySocketDataConverter(), t =>
658+
client.SetDataPackageAdapter(adapter, new SocketDataConverter<MockEntity>(), t =>
659659
{
660660
entity = t;
661661
tcs.SetResult();
@@ -734,7 +734,7 @@ public async Task TryConvertTo_Ok()
734734

735735
// 测试数据适配器直接调用 TryConvertTo 方法转换数据
736736
var adapter2 = new DataPackageAdapter();
737-
var result = adapter2.TryConvertTo(data, new MockEntitySocketDataConverter(), out var t);
737+
var result = adapter2.TryConvertTo(data, new SocketDataConverter<MockEntity>(), out var t);
738738
Assert.True(result);
739739
Assert.NotNull(t);
740740
Assert.Equal([1, 2, 3, 4, 5], entity.Header);
@@ -751,6 +751,10 @@ public async Task TryConvertTo_Ok()
751751
await client.SendAsync(data);
752752
await tcs.Task;
753753
Assert.Null(noConvertEntity);
754+
755+
var converter = new MockSocketDataConverter();
756+
result = converter.TryConvertTo(new byte[] { 0x1, 0x2 }, out t);
757+
Assert.False(result);
754758
}
755759

756760
private static TcpListener StartTcpServer(int port, Func<TcpClient, Task> handler)
@@ -1137,18 +1141,7 @@ public void SetReceive(bool state)
11371141
}
11381142
}
11391143

1140-
class MockEntitySocketDataConverter : SocketDataConverterBase<MockEntity>
1141-
{
1142-
public override bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out MockEntity? entity)
1143-
{
1144-
var v = new MockEntity();
1145-
var ret = Parse(data, v);
1146-
entity = ret ? v : null;
1147-
return ret;
1148-
}
1149-
}
1150-
1151-
[SocketDataConverter(Type = typeof(MockEntitySocketDataConverter))]
1144+
[SocketDataConverter(Type = typeof(SocketDataConverter<MockEntity>))]
11521145
class MockEntity
11531146
{
11541147
[SocketDataProperty(Type = typeof(byte[]), Offset = 0, Length = 5)]
@@ -1199,6 +1192,14 @@ class MockEntity
11991192
public string? Value13 { get; set; }
12001193
}
12011194

1195+
class MockSocketDataConverter: SocketDataConverter<MockEntity>
1196+
{
1197+
protected override bool Parse(ReadOnlyMemory<byte> data, MockEntity entity)
1198+
{
1199+
return false;
1200+
}
1201+
}
1202+
12021203
class FooConverter(string name) : ISocketDataPropertyConverter
12031204
{
12041205
public object? Convert(ReadOnlyMemory<byte> data)

0 commit comments

Comments
 (0)