From b4872f7afe306cfce007cf33ef9ec105a634df45 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 2 Jul 2025 18:35:14 +0800 Subject: [PATCH 1/8] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/TcpSocket/ITcpSocketClient.cs | 6 ----- .../Services/TcpSocket/TcpSocketClientBase.cs | 26 +------------------ 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs b/src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs index 1d38e26dff3..c764661c93b 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs @@ -38,12 +38,6 @@ public interface ITcpSocketClient : IAsyncDisposable /// impact performance. Func, ValueTask>? ReceivedCallBack { get; set; } - /// - /// Configures the data handler to process incoming data packages. - /// - /// The handler responsible for processing data packages. Cannot be null. - void SetDataHandler(IDataPackageHandler handler); - /// /// Establishes an asynchronous connection to the specified endpoint. /// diff --git a/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs b/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs index 5c6c0960246..7dacfc7e435 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs @@ -60,23 +60,10 @@ public abstract class TcpSocketClientBase(SocketClientOptions options) : ITcpSoc /// public Func, ValueTask>? ReceivedCallBack { get; set; } - /// - /// Gets or sets the handler responsible for processing data packages. - /// - public IDataPackageHandler? DataPackageHandler { get; protected set; } - private IPEndPoint? _remoteEndPoint; private IPEndPoint? _localEndPoint; private CancellationTokenSource? _receiveCancellationTokenSource; - /// - /// - /// - public virtual void SetDataHandler(IDataPackageHandler handler) - { - DataPackageHandler = handler; - } - /// /// /// @@ -157,12 +144,6 @@ public virtual async ValueTask SendAsync(ReadOnlyMemory data, Cancel var sendTokenSource = new CancellationTokenSource(options.SendTimeout); sendToken = CancellationTokenSource.CreateLinkedTokenSource(token, sendTokenSource.Token).Token; } - - if (DataPackageHandler != null) - { - data = await DataPackageHandler.SendAsync(data, sendToken); - } - ret = await SocketClientProvider.SendAsync(data, sendToken); } catch (OperationCanceledException ex) @@ -246,14 +227,9 @@ private async ValueTask ReceiveCoreAsync(ISocketClientProvider client, Memo if (ReceivedCallBack != null) { + // 如果订阅回调则触发回调 await ReceivedCallBack(buffer); } - - if (DataPackageHandler != null) - { - await DataPackageHandler.ReceiveAsync(buffer, receiveToken); - } - len = buffer.Length; } } catch (OperationCanceledException ex) From 179bca2903b37c9d5cec7062355b002dd50226ec Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 2 Jul 2025 18:35:48 +0800 Subject: [PATCH 2/8] =?UTF-8?q?refactor:=20=E5=A2=9E=E5=8A=A0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E9=80=82=E9=85=8D=E5=99=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DataPackage/DataPackageAdapterBase.cs | 62 +++++++++++++++++++ .../DataPackage/DataPackageHandlerBase.cs | 4 +- .../FixLengthDataPackageHandler.cs | 3 +- .../DataPackage/IDataPackageAdapter.cs | 49 +++++++++++++++ .../DataPackage/IDataPackageHandler.cs | 18 +++--- 5 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapterBase.cs create mode 100644 src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageAdapter.cs diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapterBase.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapterBase.cs new file mode 100644 index 00000000000..6da4e22cfb8 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapterBase.cs @@ -0,0 +1,62 @@ +// 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 + +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 abstract class DataPackageAdapterBase : IDataPackageAdapter +{ + private TaskCompletionSource _taskCompletionSource = new(); + private Memory _buffer = Memory.Empty; + + /// + /// + /// + public IDataPackageHandler? DataPackageHandler { get; set; } + + /// + /// + /// + public ITcpSocketClient? TcpSocketClient { get; set; } + + /// + /// + /// + /// + /// + public virtual async ValueTask> ReceiveAsync(CancellationToken token = default) + { + var ret = Memory.Empty; + var result = await _taskCompletionSource.Task; + if (result) + { + // 如果成功返回缓冲区数据 + ret = _buffer; + } + return ret; + } + + /// + /// + /// + /// + /// + /// + public virtual ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default) + { + if (DataPackageHandler != null) + { + // 如果存在数据处理器则调用其处理方法 + DataPackageHandler.ReceiveAsync(data, token); + } + return ValueTask.FromResult(true); + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageHandlerBase.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageHandlerBase.cs index f4d41cecacc..34b74653652 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageHandlerBase.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageHandlerBase.cs @@ -37,9 +37,9 @@ public virtual ValueTask> SendAsync(ReadOnlyMemory da /// /// /// - public virtual ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default) + public virtual ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default) { - return ValueTask.CompletedTask; + return ValueTask.FromResult(true); } /// diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs index c669f845f11..41c7908a078 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs @@ -24,7 +24,7 @@ public class FixLengthDataPackageHandler(int length) : DataPackageHandlerBase /// /// /// - public override async ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default) + public override async ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default) { while (data.Length > 0) { @@ -51,5 +51,6 @@ public override async ValueTask ReceiveAsync(ReadOnlyMemory data, Cancella continue; } } + return false; } } diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageAdapter.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageAdapter.cs new file mode 100644 index 00000000000..e50a0ddd673 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageAdapter.cs @@ -0,0 +1,49 @@ +// 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 + +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 the handler responsible for processing data packages. + /// + IDataPackageHandler? DataPackageHandler { get; } + + /// + /// Gets or sets the TCP socket client used for network communication. + /// + ITcpSocketClient? TcpSocketClient { get; set; } + + /// + /// Asynchronously receives data from the underlying source. + /// + /// This method is non-blocking and returns immediately with a task representing the asynchronous + /// operation. The caller can await the task to retrieve the received data. If the operation is canceled, the task + /// will complete with a . + /// A that can be used to cancel the operation. + /// A containing a of bytes representing the + /// received data. If no data is available, the returned memory may be empty. + ValueTask> ReceiveAsync(CancellationToken token = default); + + /// + /// Processes the provided data asynchronously and returns a value indicating whether the operation was successful. + /// + /// The method performs an asynchronous operation and supports cancellation via the parameter. Ensure that the parameter contains valid input for the + /// operation to succeed. + /// The data to be processed, represented as a read-only block of memory. + /// A cancellation token that can be used to cancel the operation. Defaults to if not + /// provided. + /// A containing if the operation was successful; otherwise, + /// . + ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default); +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs index ea8da06cd76..9c9a2643ec4 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs @@ -31,13 +31,15 @@ public interface IDataPackageHandler ValueTask> SendAsync(ReadOnlyMemory data, CancellationToken token = default); /// - /// Asynchronously receives data from a source and writes it into the provided memory buffer. + /// Asynchronously receives data and processes it. /// - /// This method does not guarantee that the entire buffer will be filled. The number of bytes - /// written depends on the availability of data. - /// The memory buffer to store the received data. The buffer must be writable and have sufficient capacity. - /// A cancellation token that can be used to cancel the operation. The default value is . - /// A task that represents the asynchronous operation. The task result contains the number of bytes written to the - /// buffer. Returns 0 if the end of the data stream is reached. - ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default); + /// 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 ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default); } From 03ed5a9598345fb6aec3edfb17d909b03052e71b Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 2 Jul 2025 19:15:49 +0800 Subject: [PATCH 3/8] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=20ReceiveAsy?= =?UTF-8?q?nc=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DataPackage/DataPackageHandlerBase.cs | 4 +- .../FixLengthDataPackageHandler.cs | 4 +- .../DataPackage/IDataPackageAdapter.cs | 43 ++++++++----------- .../DataPackage/IDataPackageHandler.cs | 2 +- 4 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageHandlerBase.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageHandlerBase.cs index 34b74653652..f4d41cecacc 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageHandlerBase.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageHandlerBase.cs @@ -37,9 +37,9 @@ public virtual ValueTask> SendAsync(ReadOnlyMemory da /// /// /// - public virtual ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default) + public virtual ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default) { - return ValueTask.FromResult(true); + return ValueTask.CompletedTask; } /// diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs index 41c7908a078..0661a8295a8 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs @@ -24,7 +24,7 @@ public class FixLengthDataPackageHandler(int length) : DataPackageHandlerBase /// /// /// - public override async ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default) + public override async ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default) { while (data.Length > 0) { @@ -48,9 +48,7 @@ public override async ValueTask ReceiveAsync(ReadOnlyMemory data, Ca { await ReceivedCallBack(_data); } - continue; } } - return false; } } diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageAdapter.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageAdapter.cs index e50a0ddd673..f91aa15ecca 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageAdapter.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageAdapter.cs @@ -14,36 +14,29 @@ namespace BootstrapBlazor.Components; public interface IDataPackageAdapter { /// - /// Gets the handler responsible for processing data packages. + /// Gets or sets the callback function to be invoked when data is received. /// - IDataPackageHandler? DataPackageHandler { get; } + /// 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 or sets the TCP socket client used for network communication. - /// - ITcpSocketClient? TcpSocketClient { get; set; } - - /// - /// Asynchronously receives data from the underlying source. + /// Gets the handler responsible for processing data packages. /// - /// This method is non-blocking and returns immediately with a task representing the asynchronous - /// operation. The caller can await the task to retrieve the received data. If the operation is canceled, the task - /// will complete with a . - /// A that can be used to cancel the operation. - /// A containing a of bytes representing the - /// received data. If no data is available, the returned memory may be empty. - ValueTask> ReceiveAsync(CancellationToken token = default); + IDataPackageHandler? DataPackageHandler { get; } /// - /// Processes the provided data asynchronously and returns a value indicating whether the operation was successful. + /// Asynchronously receives data from a source and processes it. /// - /// The method performs an asynchronous operation and supports cancellation via the parameter. Ensure that the parameter contains valid input for the - /// operation to succeed. - /// The data to be processed, represented as a read-only block of memory. - /// A cancellation token that can be used to cancel the operation. Defaults to if not - /// provided. - /// A containing if the operation was successful; otherwise, - /// . - ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default); + /// 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 ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default); } diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs index 9c9a2643ec4..705bb70f45e 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs @@ -41,5 +41,5 @@ public interface IDataPackageHandler /// provided. /// A containing if the data was successfully received and /// processed; otherwise, . - ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default); + ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default); } From 6ecfb4d8291a3bcae2f86c2d80f1e607bbe453b0 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 2 Jul 2025 19:16:06 +0800 Subject: [PATCH 4/8] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=AE=9E=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...geAdapterBase.cs => DataPackageAdapter.cs} | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) rename src/BootstrapBlazor/Services/TcpSocket/DataPackage/{DataPackageAdapterBase.cs => DataPackageAdapter.cs} (57%) diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapterBase.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapter.cs similarity index 57% rename from src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapterBase.cs rename to src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapter.cs index 6da4e22cfb8..d19f2736871 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapterBase.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapter.cs @@ -12,51 +12,52 @@ namespace BootstrapBlazor.Components; /// 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 abstract class DataPackageAdapterBase : IDataPackageAdapter +public class DataPackageAdapter : IDataPackageAdapter { - private TaskCompletionSource _taskCompletionSource = new(); - private Memory _buffer = Memory.Empty; - /// /// /// - public IDataPackageHandler? DataPackageHandler { get; set; } + public Func, ValueTask>? ReceivedCallBack { get; set; } /// /// /// - public ITcpSocketClient? TcpSocketClient { get; set; } + public IDataPackageHandler? DataPackageHandler { get; set; } /// /// /// + /// /// /// - public virtual async ValueTask> ReceiveAsync(CancellationToken token = default) + public virtual async ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default) { - var ret = Memory.Empty; - var result = await _taskCompletionSource.Task; - if (result) + if (DataPackageHandler != null) { - // 如果成功返回缓冲区数据 - ret = _buffer; + if (DataPackageHandler.ReceivedCallBack == null) + { + DataPackageHandler.ReceivedCallBack = OnHandlerReceivedCallBack; + } + + // 如果存在数据处理器则调用其处理方法 + await DataPackageHandler.ReceiveAsync(data, token); } - 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. /// - public virtual ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default) + protected virtual async ValueTask OnHandlerReceivedCallBack(ReadOnlyMemory data) { - if (DataPackageHandler != null) + if (ReceivedCallBack != null) { - // 如果存在数据处理器则调用其处理方法 - DataPackageHandler.ReceiveAsync(data, token); + // 调用接收回调方法处理数据 + await ReceivedCallBack(data); } - return ValueTask.FromResult(true); } } From 127b44764bd3261022dea1a4e1a91171691e4d85 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 2 Jul 2025 19:16:22 +0800 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=A8=A1?= =?UTF-8?q?=E6=8B=9F=E5=88=86=E5=8C=85=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MockCustomProtocalSocketServerService.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/BootstrapBlazor.Server/Services/MockCustomProtocalSocketServerService.cs b/src/BootstrapBlazor.Server/Services/MockCustomProtocalSocketServerService.cs index d9ed2acabd9..bdea30c1753 100644 --- a/src/BootstrapBlazor.Server/Services/MockCustomProtocalSocketServerService.cs +++ b/src/BootstrapBlazor.Server/Services/MockCustomProtocalSocketServerService.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Sockets; +using System.Text; namespace Longbow.Tasks.Services; @@ -55,11 +56,12 @@ private async Task OnDataHandlerAsync(TcpClient client, CancellationToken stoppi // 发送响应数据 // 响应头: 4 字节表示响应体长度 [0x32, 0x30, 0x32, 0x35] - // 响应体: 8 字节当前时间戳字符串 - var data = new byte[12]; - "2025"u8.ToArray().CopyTo(data, 0); - System.Text.Encoding.UTF8.GetBytes(DateTime.Now.ToString("ddHHmmss")).CopyTo(data, 4); - await stream.WriteAsync(data, stoppingToken); + // 响应体: 8 字节当前时间戳字符串 + // 此处模拟分包操作故意分 2 次写入数据,导致客户端接收 2 次才能得到完整数据 + await stream.WriteAsync("2025"u8.ToArray(), stoppingToken); + // 模拟延时 + await Task.Delay(40, stoppingToken); + await stream.WriteAsync(Encoding.UTF8.GetBytes(DateTime.Now.ToString("ddHHmmss")), stoppingToken); } catch (OperationCanceledException) { break; } catch (IOException) { break; } From 7c038c285e74df2ee5588a835d310a37764beb60 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 2 Jul 2025 19:16:34 +0800 Subject: [PATCH 6/8] =?UTF-8?q?doc:=20=E5=AE=9E=E7=8E=B0=E6=8E=A5=E6=94=B6?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/Sockets/Adapters.razor.cs | 72 ++++++++++++++----- 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor.cs index 488174594d2..0532684ddcd 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone -using BootstrapBlazor.Server.Components.Components; using System.Net; using System.Text; @@ -26,6 +25,11 @@ public partial class Adapters : IDisposable private readonly CancellationTokenSource _connectTokenSource = new(); private readonly CancellationTokenSource _sendTokenSource = new(); private readonly CancellationTokenSource _receiveTokenSource = new(); + private readonly DataPackageAdapter _dataAdapter = new() + { + DataPackageHandler = new FixLengthDataPackageHandler(12) + }; + private bool _useDataAdapter = true; /// /// @@ -38,10 +42,17 @@ protected override void OnInitialized() _client = TcpSocketFactory.GetOrCreate("demo-adapter", options => { // 关闭自动接收功能 - options.IsAutoReceive = false; + options.IsAutoReceive = true; // 设置本地使用的 IP地址与端口 options.LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0); }); + _client.ReceivedCallBack += OnReceivedAsync; + + _dataAdapter.ReceivedCallBack += async Data => + { + // 直接处理接收的数据 + await UpdateReceiveLog(Data); + }; } private async Task OnConnectAsync() @@ -67,26 +78,49 @@ private async Task OnSendAsync() "2025"u8.CopyTo(data); Encoding.UTF8.GetBytes(DateTime.Now.ToString("ddHHmmss")).CopyTo(data, 4); var result = await _client.SendAsync(data, _sendTokenSource.Token); - if (result) + var state = result ? "成功" : "失败"; + + // 记录日志 + _items.Add(new ConsoleMessageItem() { - // 发送成功 - var payload = await _client.ReceiveAsync(_receiveTokenSource.Token); - if (!payload.IsEmpty) - { - // 解析接收到的数据 - // 响应头: 4 字节表示响应体长度 [0x32, 0x30, 0x32, 0x35] - // 响应体: 8 字节当前时间戳字符串 - data = payload.ToArray(); - var body = BitConverter.ToString(data); - _items.Add(new ConsoleMessageItem() - { - Message = $"{DateTime.Now}: 接收到来自 {_serverEndPoint} 数据: {Encoding.UTF8.GetString(data)} HEX: {body}" - }); - } - } + Message = $"{DateTime.Now}: 发送数据 {_client.LocalEndPoint} - {_serverEndPoint} Data {BitConverter.ToString(data)} {state}" + }); } } + private async ValueTask OnReceivedAsync(ReadOnlyMemory data) + { + if (_useDataAdapter) + { + // 使用数据适配器处理接收的数据 + await _dataAdapter.ReceiveAsync(data, _receiveTokenSource.Token); + } + else + { + // 直接处理接收的数据 + await UpdateReceiveLog(data); + } + } + + private async Task UpdateReceiveLog(ReadOnlyMemory data) + { + var payload = System.Text.Encoding.UTF8.GetString(data.Span); + var body = BitConverter.ToString(data.ToArray()); + + _items.Add(new ConsoleMessageItem + { + Message = $"{DateTime.Now}: 接收数据 {_client.LocalEndPoint} - {_serverEndPoint} Data {payload} HEX: {body}", + Color = Color.Success + }); + + // 保持队列中最大数量为 50 + if (_items.Count > 50) + { + _items.RemoveAt(0); + } + await InvokeAsync(StateHasChanged); + } + private async Task OnCloseAsync() { if (_client is { IsConnected: true }) @@ -111,6 +145,8 @@ private void Dispose(bool disposing) { if (disposing) { + _client.ReceivedCallBack -= OnReceivedAsync; + // 释放连接令牌资源 _connectTokenSource.Cancel(); _connectTokenSource.Dispose(); From b777a276ab6f7cbaf5258fc08d4f91c6d0fffa4f Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 2 Jul 2025 19:29:26 +0800 Subject: [PATCH 7/8] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/Sockets/Adapters.razor | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor b/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor index b3d4ea5509e..b8622fb2199 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor @@ -1,6 +1,14 @@ @page "/socket/adapter" @inject IStringLocalizer Localizer + + + +

@Localizer["AdaptersTitle"]

@Localizer["AdaptersDescription"]

@@ -31,8 +39,22 @@
  • 响应头为 4 字节定长,响应体为 8 个字节定长
  • 响应体为字符串类型数据
  • +

    本示例服务器端模拟了数据分包即响应数据实际是两次写入所以实际接收端是要通过两次接收才能得到一个完整的响应数据包,可通过 数据适配器 来简化接收逻辑

    +
    private readonly DataPackageAdapter _dataAdapter = new()
    +{
    +    // 数据适配器内部使用固定长度数据处理器
    +    DataPackageHandler = new FixLengthDataPackageHandler(12)
    +};
    +
    +_dataAdapter.ReceivedCallBack += async Data =>
    +{
    +    // 此处接收到的数据 Data 为完整响应数据
    +};
    +
    + +
    From a8dc6036feed9c9d76febff5056182ad7cbbe9ed Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 2 Jul 2025 19:35:31 +0800 Subject: [PATCH 8/8] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/Sockets/Adapters.razor | 2 +- src/BootstrapBlazor.Server/Locales/en-US.json | 6 ++++++ src/BootstrapBlazor.Server/Locales/zh-CN.json | 6 ++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor b/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor index b8622fb2199..8bf08597bda 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor @@ -39,7 +39,7 @@
  • 响应头为 4 字节定长,响应体为 8 个字节定长
  • 响应体为字符串类型数据
  • -

    本示例服务器端模拟了数据分包即响应数据实际是两次写入所以实际接收端是要通过两次接收才能得到一个完整的响应数据包,可通过 数据适配器 来简化接收逻辑

    +

    本示例服务器端模拟了数据分包即响应数据实际是两次写入所以实际接收端是要通过两次接收才能得到一个完整的响应数据包,可通过 数据适配器 来简化接收逻辑。通过切换下方 是否使用数据适配器 控制开关进行测试查看实际数据接收情况

    private readonly DataPackageAdapter _dataAdapter = new()
     {
         // 数据适配器内部使用固定长度数据处理器
    diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json
    index cbfa88e0e44..af3aed1e7dd 100644
    --- a/src/BootstrapBlazor.Server/Locales/en-US.json
    +++ b/src/BootstrapBlazor.Server/Locales/en-US.json
    @@ -7216,5 +7216,11 @@
         "ReceivesDescription": "Receive data through Socket and display it",
         "NormalTitle": "Basic usage",
         "NormalIntro": "After connecting, the timestamp data sent by the server is automatically received through the ReceivedCallBack callback method"
    +  },
    +  "BootstrapBlazor.Server.Components.Samples.Sockets.Adapters": {
    +    "AdaptersTitle": "DataPackageAdapter",
    +    "AdaptersDescription": "Receive data through the data adapter and display",
    +    "NormalTitle": "Basic usage",
    +    "NormalIntro": "After the connection is established, the timestamp data sent by the server is received through the ReceivedCallBack callback method of the DataPackageAdapter data adapter."
       }
     }
    diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json
    index b012a2ed8c5..bf1d81163c7 100644
    --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json
    +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json
    @@ -7216,5 +7216,11 @@
         "ReceivesDescription": "通过 Socket 接收数据并且显示",
         "NormalTitle": "基本用法",
         "NormalIntro": "连接后通过 ReceivedCallBack 回调方法自动接收服务端发送来的时间戳数据"
    +  },
    +  "BootstrapBlazor.Server.Components.Samples.Sockets.Adapters": {
    +    "AdaptersTitle": "Socket 数据适配器示例",
    +    "AdaptersDescription": "通过数据适配器接收数据并且显示",
    +    "NormalTitle": "基本用法",
    +    "NormalIntro": "连接后通过 DataPackageAdapter 数据适配器的 ReceivedCallBack 回调方法接收服务端发送来的时间戳数据"
       }
     }