Skip to content

Commit 1dd1e19

Browse files
authored
doc(IDataPackageAdapter): add DataPackageAdapter sample code (#6345)
* refactor: 移除数据处理 * refactor: 增加数据适配器接口 * refactor: 重构 ReceiveAsync 方法 * refactor: 更改为实类 * feat: 增加模拟分包逻辑 * doc: 实现接收逻辑 * doc: 更新示例 * doc: 更新示例代码
1 parent 37db88a commit 1dd1e19

File tree

11 files changed

+210
-62
lines changed

11 files changed

+210
-62
lines changed

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
@page "/socket/adapter"
22
@inject IStringLocalizer<Adapters> Localizer
33

4+
<HeadContent>
5+
<style>
6+
:root {
7+
--bb-row-label-width: 180px;
8+
}
9+
</style>
10+
</HeadContent>
11+
412
<h3>@Localizer["AdaptersTitle"]</h3>
513
<h4>@Localizer["AdaptersDescription"]</h4>
614

@@ -31,8 +39,22 @@
3139
<li>响应头为 4 字节定长,响应体为 8 个字节定长</li>
3240
<li>响应体为字符串类型数据</li>
3341
</ul>
42+
<p>本示例服务器端模拟了数据分包即响应数据实际是两次写入所以实际接收端是要通过两次接收才能得到一个完整的响应数据包,可通过 <b>数据适配器</b> 来简化接收逻辑。通过切换下方 <b>是否使用数据适配器</b> 控制开关进行测试查看实际数据接收情况</p>
43+
<Pre>private readonly DataPackageAdapter _dataAdapter = new()
44+
{
45+
// 数据适配器内部使用固定长度数据处理器
46+
DataPackageHandler = new FixLengthDataPackageHandler(12)
47+
};
48+
49+
_dataAdapter.ReceivedCallBack += async Data =>
50+
{
51+
// 此处接收到的数据 Data 为完整响应数据
52+
};</Pre>
3453

3554
<div class="row form-inline g-3">
55+
<div class="col-12">
56+
<Switch ShowLabel="true" DisplayText="是否使用数据适配器" @bind-Value="_useDataAdapter"></Switch>
57+
</div>
3658
<div class="col-12 col-sm-6">
3759
<Button Text="连接" Icon="fa-solid fa-play"
3860
OnClick="OnConnectAsync" IsDisabled="@_client.IsConnected"></Button>

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

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// See the LICENSE file in the project root for more information.
44
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
55

6-
using BootstrapBlazor.Server.Components.Components;
76
using System.Net;
87
using System.Text;
98

@@ -26,6 +25,11 @@ public partial class Adapters : IDisposable
2625
private readonly CancellationTokenSource _connectTokenSource = new();
2726
private readonly CancellationTokenSource _sendTokenSource = new();
2827
private readonly CancellationTokenSource _receiveTokenSource = new();
28+
private readonly DataPackageAdapter _dataAdapter = new()
29+
{
30+
DataPackageHandler = new FixLengthDataPackageHandler(12)
31+
};
32+
private bool _useDataAdapter = true;
2933

3034
/// <summary>
3135
/// <inheritdoc/>
@@ -38,10 +42,17 @@ protected override void OnInitialized()
3842
_client = TcpSocketFactory.GetOrCreate("demo-adapter", options =>
3943
{
4044
// 关闭自动接收功能
41-
options.IsAutoReceive = false;
45+
options.IsAutoReceive = true;
4246
// 设置本地使用的 IP地址与端口
4347
options.LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
4448
});
49+
_client.ReceivedCallBack += OnReceivedAsync;
50+
51+
_dataAdapter.ReceivedCallBack += async Data =>
52+
{
53+
// 直接处理接收的数据
54+
await UpdateReceiveLog(Data);
55+
};
4556
}
4657

4758
private async Task OnConnectAsync()
@@ -67,26 +78,49 @@ private async Task OnSendAsync()
6778
"2025"u8.CopyTo(data);
6879
Encoding.UTF8.GetBytes(DateTime.Now.ToString("ddHHmmss")).CopyTo(data, 4);
6980
var result = await _client.SendAsync(data, _sendTokenSource.Token);
70-
if (result)
81+
var state = result ? "成功" : "失败";
82+
83+
// 记录日志
84+
_items.Add(new ConsoleMessageItem()
7185
{
72-
// 发送成功
73-
var payload = await _client.ReceiveAsync(_receiveTokenSource.Token);
74-
if (!payload.IsEmpty)
75-
{
76-
// 解析接收到的数据
77-
// 响应头: 4 字节表示响应体长度 [0x32, 0x30, 0x32, 0x35]
78-
// 响应体: 8 字节当前时间戳字符串
79-
data = payload.ToArray();
80-
var body = BitConverter.ToString(data);
81-
_items.Add(new ConsoleMessageItem()
82-
{
83-
Message = $"{DateTime.Now}: 接收到来自 {_serverEndPoint} 数据: {Encoding.UTF8.GetString(data)} HEX: {body}"
84-
});
85-
}
86-
}
86+
Message = $"{DateTime.Now}: 发送数据 {_client.LocalEndPoint} - {_serverEndPoint} Data {BitConverter.ToString(data)} {state}"
87+
});
8788
}
8889
}
8990

91+
private async ValueTask OnReceivedAsync(ReadOnlyMemory<byte> data)
92+
{
93+
if (_useDataAdapter)
94+
{
95+
// 使用数据适配器处理接收的数据
96+
await _dataAdapter.ReceiveAsync(data, _receiveTokenSource.Token);
97+
}
98+
else
99+
{
100+
// 直接处理接收的数据
101+
await UpdateReceiveLog(data);
102+
}
103+
}
104+
105+
private async Task UpdateReceiveLog(ReadOnlyMemory<byte> data)
106+
{
107+
var payload = System.Text.Encoding.UTF8.GetString(data.Span);
108+
var body = BitConverter.ToString(data.ToArray());
109+
110+
_items.Add(new ConsoleMessageItem
111+
{
112+
Message = $"{DateTime.Now}: 接收数据 {_client.LocalEndPoint} - {_serverEndPoint} Data {payload} HEX: {body}",
113+
Color = Color.Success
114+
});
115+
116+
// 保持队列中最大数量为 50
117+
if (_items.Count > 50)
118+
{
119+
_items.RemoveAt(0);
120+
}
121+
await InvokeAsync(StateHasChanged);
122+
}
123+
90124
private async Task OnCloseAsync()
91125
{
92126
if (_client is { IsConnected: true })
@@ -111,6 +145,8 @@ private void Dispose(bool disposing)
111145
{
112146
if (disposing)
113147
{
148+
_client.ReceivedCallBack -= OnReceivedAsync;
149+
114150
// 释放连接令牌资源
115151
_connectTokenSource.Cancel();
116152
_connectTokenSource.Dispose();

src/BootstrapBlazor.Server/Locales/en-US.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7216,5 +7216,11 @@
72167216
"ReceivesDescription": "Receive data through Socket and display it",
72177217
"NormalTitle": "Basic usage",
72187218
"NormalIntro": "After connecting, the timestamp data sent by the server is automatically received through the <code>ReceivedCallBack</code> callback method"
7219+
},
7220+
"BootstrapBlazor.Server.Components.Samples.Sockets.Adapters": {
7221+
"AdaptersTitle": "DataPackageAdapter",
7222+
"AdaptersDescription": "Receive data through the data adapter and display",
7223+
"NormalTitle": "Basic usage",
7224+
"NormalIntro": "After the connection is established, the timestamp data sent by the server is received through the <code>ReceivedCallBack</code> callback method of the <code>DataPackageAdapter</code> data adapter."
72197225
}
72207226
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7216,5 +7216,11 @@
72167216
"ReceivesDescription": "通过 Socket 接收数据并且显示",
72177217
"NormalTitle": "基本用法",
72187218
"NormalIntro": "连接后通过 <code>ReceivedCallBack</code> 回调方法自动接收服务端发送来的时间戳数据"
7219+
},
7220+
"BootstrapBlazor.Server.Components.Samples.Sockets.Adapters": {
7221+
"AdaptersTitle": "Socket 数据适配器示例",
7222+
"AdaptersDescription": "通过数据适配器接收数据并且显示",
7223+
"NormalTitle": "基本用法",
7224+
"NormalIntro": "连接后通过 <code>DataPackageAdapter</code> 数据适配器的 <code>ReceivedCallBack</code> 回调方法接收服务端发送来的时间戳数据"
72197225
}
72207226
}

src/BootstrapBlazor.Server/Services/MockCustomProtocalSocketServerService.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using System.Net;
77
using System.Net.Sockets;
8+
using System.Text;
89

910
namespace Longbow.Tasks.Services;
1011

@@ -55,11 +56,12 @@ private async Task OnDataHandlerAsync(TcpClient client, CancellationToken stoppi
5556

5657
// 发送响应数据
5758
// 响应头: 4 字节表示响应体长度 [0x32, 0x30, 0x32, 0x35]
58-
// 响应体: 8 字节当前时间戳字符串
59-
var data = new byte[12];
60-
"2025"u8.ToArray().CopyTo(data, 0);
61-
System.Text.Encoding.UTF8.GetBytes(DateTime.Now.ToString("ddHHmmss")).CopyTo(data, 4);
62-
await stream.WriteAsync(data, stoppingToken);
59+
// 响应体: 8 字节当前时间戳字符串
60+
// 此处模拟分包操作故意分 2 次写入数据,导致客户端接收 2 次才能得到完整数据
61+
await stream.WriteAsync("2025"u8.ToArray(), stoppingToken);
62+
// 模拟延时
63+
await Task.Delay(40, stoppingToken);
64+
await stream.WriteAsync(Encoding.UTF8.GetBytes(DateTime.Now.ToString("ddHHmmss")), stoppingToken);
6365
}
6466
catch (OperationCanceledException) { break; }
6567
catch (IOException) { break; }
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
namespace BootstrapBlazor.Components;
7+
8+
/// <summary>
9+
/// Provides a base implementation for adapting data packages between different systems or formats.
10+
/// </summary>
11+
/// <remarks>This abstract class serves as a foundation for implementing custom data package adapters. It defines
12+
/// common methods for sending, receiving, and handling data packages, as well as a property for accessing the
13+
/// associated data package handler. Derived classes should override the virtual methods to provide specific behavior
14+
/// for handling data packages.</remarks>
15+
public class DataPackageAdapter : IDataPackageAdapter
16+
{
17+
/// <summary>
18+
/// <inheritdoc/>
19+
/// </summary>
20+
public Func<ReadOnlyMemory<byte>, ValueTask>? ReceivedCallBack { get; set; }
21+
22+
/// <summary>
23+
/// <inheritdoc/>
24+
/// </summary>
25+
public IDataPackageHandler? DataPackageHandler { get; set; }
26+
27+
/// <summary>
28+
/// <inheritdoc/>
29+
/// </summary>
30+
/// <param name="data"></param>
31+
/// <param name="token"></param>
32+
/// <returns></returns>
33+
public virtual async ValueTask ReceiveAsync(ReadOnlyMemory<byte> data, CancellationToken token = default)
34+
{
35+
if (DataPackageHandler != null)
36+
{
37+
if (DataPackageHandler.ReceivedCallBack == null)
38+
{
39+
DataPackageHandler.ReceivedCallBack = OnHandlerReceivedCallBack;
40+
}
41+
42+
// 如果存在数据处理器则调用其处理方法
43+
await DataPackageHandler.ReceiveAsync(data, token);
44+
}
45+
}
46+
47+
/// <summary>
48+
/// Handles incoming data by invoking a callback method, if one is defined.
49+
/// </summary>
50+
/// <remarks>This method is designed to be overridden in derived classes to provide custom handling of
51+
/// incoming data. If a callback method is assigned, it will be invoked asynchronously with the provided
52+
/// data.</remarks>
53+
/// <param name="data">The incoming data to be processed, represented as a read-only memory block of bytes.</param>
54+
/// <returns></returns>
55+
protected virtual async ValueTask OnHandlerReceivedCallBack(ReadOnlyMemory<byte> data)
56+
{
57+
if (ReceivedCallBack != null)
58+
{
59+
// 调用接收回调方法处理数据
60+
await ReceivedCallBack(data);
61+
}
62+
}
63+
}

src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ public override async ValueTask ReceiveAsync(ReadOnlyMemory<byte> data, Cancella
4848
{
4949
await ReceivedCallBack(_data);
5050
}
51-
continue;
5251
}
5352
}
5453
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
namespace BootstrapBlazor.Components;
7+
8+
/// <summary>
9+
/// Defines an adapter for handling and transmitting data packages to a target destination.
10+
/// </summary>
11+
/// <remarks>This interface provides methods for sending data asynchronously and configuring a data handler.
12+
/// Implementations of this interface are responsible for managing the interaction between the caller and the underlying
13+
/// data transmission mechanism.</remarks>
14+
public interface IDataPackageAdapter
15+
{
16+
/// <summary>
17+
/// Gets or sets the callback function to be invoked when data is received.
18+
/// </summary>
19+
/// <remarks>The callback function is expected to handle the received data asynchronously. Ensure that the
20+
/// implementation of the callback does not block the calling thread and completes promptly to avoid performance
21+
/// issues.</remarks>
22+
Func<ReadOnlyMemory<byte>, ValueTask>? ReceivedCallBack { get; set; }
23+
24+
/// <summary>
25+
/// Gets the handler responsible for processing data packages.
26+
/// </summary>
27+
IDataPackageHandler? DataPackageHandler { get; }
28+
29+
/// <summary>
30+
/// Asynchronously receives data from a source and processes it.
31+
/// </summary>
32+
/// <remarks>This method does not return any result directly. It is intended for scenarios where data is received
33+
/// and processed asynchronously. Ensure that the <paramref name="data"/> parameter contains valid data before calling
34+
/// this method.</remarks>
35+
/// <param name="data">A read-only memory region containing the data to be received. The caller must ensure the memory is valid and
36+
/// populated.</param>
37+
/// <param name="token">An optional cancellation token that can be used to cancel the operation. Defaults to <see langword="default"/> if
38+
/// not provided.</param>
39+
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation. The task completes when the data has been
40+
/// successfully received and processed.</returns>
41+
ValueTask ReceiveAsync(ReadOnlyMemory<byte> data, CancellationToken token = default);
42+
}

src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ public interface IDataPackageHandler
3131
ValueTask<ReadOnlyMemory<byte>> SendAsync(ReadOnlyMemory<byte> data, CancellationToken token = default);
3232

3333
/// <summary>
34-
/// Asynchronously receives data from a source and writes it into the provided memory buffer.
34+
/// Asynchronously receives data and processes it.
3535
/// </summary>
36-
/// <remarks>This method does not guarantee that the entire buffer will be filled. The number of bytes
37-
/// written depends on the availability of data.</remarks>
38-
/// <param name="data">The memory buffer to store the received data. The buffer must be writable and have sufficient capacity.</param>
39-
/// <param name="token">A cancellation token that can be used to cancel the operation. The default value is <see langword="default"/>.</param>
40-
/// <returns>A task that represents the asynchronous operation. The task result contains the number of bytes written to the
41-
/// buffer. Returns 0 if the end of the data stream is reached.</returns>
36+
/// <remarks>The method is designed for asynchronous operations and may be used in scenarios where
37+
/// efficient handling of data streams is required. Ensure that the <paramref name="data"/> parameter contains valid
38+
/// data for processing, and handle potential cancellation using the <paramref name="token"/>.</remarks>
39+
/// <param name="data">The data to be received, represented as a read-only memory block of bytes.</param>
40+
/// <param name="token">A cancellation token that can be used to cancel the operation. Defaults to <see langword="default"/> if not
41+
/// provided.</param>
42+
/// <returns>A <see cref="ValueTask{TResult}"/> containing <see langword="true"/> if the data was successfully received and
43+
/// processed; otherwise, <see langword="false"/>.</returns>
4244
ValueTask ReceiveAsync(ReadOnlyMemory<byte> data, CancellationToken token = default);
4345
}

src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,6 @@ public interface ITcpSocketClient : IAsyncDisposable
3838
/// impact performance.</remarks>
3939
Func<ReadOnlyMemory<byte>, ValueTask>? ReceivedCallBack { get; set; }
4040

41-
/// <summary>
42-
/// Configures the data handler to process incoming data packages.
43-
/// </summary>
44-
/// <param name="handler">The handler responsible for processing data packages. Cannot be null.</param>
45-
void SetDataHandler(IDataPackageHandler handler);
46-
4741
/// <summary>
4842
/// Establishes an asynchronous connection to the specified endpoint.
4943
/// </summary>

0 commit comments

Comments
 (0)