Skip to content
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c7f2dff
feat: 增加扩展方法判断当前环境是否为 IsWasm
ArgoZhang Jun 15, 2025
c777b6f
feat: 增加 ITcpSocketFactory 服务
ArgoZhang Jun 16, 2025
130949e
refactor: 更新 ConnectAsync 接口
ArgoZhang Jun 16, 2025
c23703d
test: 更新单元测试
ArgoZhang Jun 16, 2025
c0d1ac5
refactor: 增加 ITcpSocketClient 服务
ArgoZhang Jun 16, 2025
2ce01c1
test: 增加单元测试
ArgoZhang Jun 16, 2025
f5f0ba1
refactor: 重构日志实例逻辑
ArgoZhang Jun 18, 2025
95ccd15
refactor: 精简代码
ArgoZhang Jun 18, 2025
8898e7a
refactor: 增加取消记录日志逻辑
ArgoZhang Jun 18, 2025
455b445
refactor: 增加 Close 方法
ArgoZhang Jun 18, 2025
283caaa
test: 增加实例单元测试
ArgoZhang Jun 18, 2025
033b0c2
feat: 增加 IDataPackageAdapter 接口
ArgoZhang Jun 18, 2025
60c122e
refactor: 增加设置本地节点逻辑
ArgoZhang Jun 18, 2025
86217e1
refactor: 增加数据处理器功能
ArgoZhang Jun 18, 2025
415c588
refactor: 增加 virtual 关键字
ArgoZhang Jun 18, 2025
2ef702d
test: 增加单元测试
ArgoZhang Jun 18, 2025
3162a01
test: 更新单元测试
ArgoZhang Jun 18, 2025
fda1c32
feat: 增加数据处理类
ArgoZhang Jun 18, 2025
2d087f6
refactor: 增加连接后自动接收逻辑
ArgoZhang Jun 18, 2025
0474f66
test: 增加单元测试
ArgoZhang Jun 18, 2025
8802cdf
refactor: 增加接收任务取消逻辑
ArgoZhang Jun 19, 2025
cf5fa85
refactor: 精简代码逻辑
ArgoZhang Jun 19, 2025
f20cf3e
test: 更新单元测试
ArgoZhang Jun 19, 2025
db7d408
refactor: 实现拆包粘包处理逻辑
ArgoZhang Jun 19, 2025
c498fea
refactor: 优化代码 Logger 不为空
ArgoZhang Jun 19, 2025
056147d
test: 更新单元测试
ArgoZhang Jun 19, 2025
9567cf9
test: 增加 SendAsync 单元测试
ArgoZhang Jun 19, 2025
a772e36
test: 增加 Factory 单元测试
ArgoZhang Jun 19, 2025
2b23292
test: 精简单元测试
ArgoZhang Jun 19, 2025
8b588e8
Merge branch 'main' into feat-socket
ArgoZhang Jun 19, 2025
a9eb67f
test: 增加 IsWasm 单元测试
ArgoZhang Jun 19, 2025
44e4bd3
refactor: 接收方法内异常改为日志
ArgoZhang Jun 19, 2025
efdd447
refactor: 防止缓存区被释放
ArgoZhang Jun 19, 2025
e4d04f7
refactor: 精简代码提高可读性
ArgoZhang Jun 19, 2025
b6e0e6f
Revert "refactor: 接收方法内异常改为日志"
ArgoZhang Jun 19, 2025
fc13b5f
refactor: 更正方法名称为 HandleStickyPackage
ArgoZhang Jun 19, 2025
6ea1b25
refactor: 更改申请缓存区代码
ArgoZhang Jun 19, 2025
ef65e62
refactor: 重构拆包方法名称
ArgoZhang Jun 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/BootstrapBlazor/Extensions/HostEnvironmentExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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([email protected]) Website: https://www.blazor.zone

using Microsoft.Extensions.Hosting;

namespace BootstrapBlazor.Components;

/// <summary>
/// <see cref="IHostEnvironment"/> 扩展方法"
/// </summary>
public static class HostEnvironmentExtensions
{
/// <summary>
/// 当前程序是否为 WebAssembly 环境
/// </summary>
/// <param name="hostEnvironment"></param>
/// <returns></returns>
public static bool IsWasm(this IHostEnvironment hostEnvironment) => hostEnvironment is MockWasmHostEnvironment;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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([email protected]) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

/// <summary>
/// Provides a base implementation for handling data packages in a communication system.
/// </summary>
/// <remarks>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.</remarks>
public abstract class DataPackageHandlerBase : IDataPackageHandler
{
private Memory<byte> _lastReceiveBuffer = Memory<byte>.Empty;

/// <summary>
/// 当接收数据处理完成后,回调该函数执行接收
/// </summary>
public Func<Memory<byte>, Task>? ReceivedCallBack { get; set; }

/// <summary>
/// Sends the specified data asynchronously to the target destination.
/// </summary>
/// <remarks>The method performs an asynchronous operation to send the provided data. The caller must
/// ensure that the data is valid and non-empty. The returned memory block may contain a response or acknowledgment
/// depending on the implementation of the target destination.</remarks>
/// <param name="data">The data to be sent, represented as a block of memory.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a <see cref="Memory{T}"/> of <see
/// cref="byte"/> representing the response or acknowledgment received from the target destination.</returns>
public virtual Task<Memory<byte>> SendAsync(Memory<byte> data)
{
return Task.FromResult(data);
}

/// <summary>
/// Processes the received data asynchronously.
/// </summary>
/// <param name="data">A memory buffer containing the data to be processed. The buffer must not be empty.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public virtual Task ReceiveAsync(Memory<byte> data)
{
return Task.CompletedTask;
}

/// <summary>
/// Handles the processing of a sticky package by adjusting the provided buffer and length.
/// </summary>
/// <remarks>This method processes the portion of the buffer beyond the specified length and updates the
/// internal state accordingly. The caller must ensure that the <paramref name="buffer"/> contains sufficient data
/// for the specified <paramref name="length"/>.</remarks>
/// <param name="buffer">The memory buffer containing the data to process.</param>
/// <param name="length">The length of the valid data within the buffer.</param>
protected void HandlerStickyPackage(Memory<byte> buffer, int length)
{
if (buffer.Length > length)
{
_lastReceiveBuffer = buffer[length..].ToArray().AsMemory();
}
}

/// <summary>
/// Concatenates the provided buffer with any previously stored data and returns the combined result.
/// </summary>
/// <remarks>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.</remarks>
/// <param name="buffer">The buffer to concatenate with the previously stored data. Must not be empty.</param>
/// <returns>A <see cref="Memory{T}"/> instance containing the concatenated data. If no previously stored data exists, the
/// method returns the input <paramref name="buffer"/>.</returns>
protected Memory<byte> ConcatBuffer(Memory<byte> buffer)
{
if (_lastReceiveBuffer.IsEmpty)
{
return buffer;
}

// 计算缓存区长度
var total = _lastReceiveBuffer.Length + buffer.Length;
var merged = new byte[total];
_lastReceiveBuffer.CopyTo(merged);
buffer.CopyTo(merged.AsMemory(_lastReceiveBuffer.Length));

// Clear the sticky buffer
_lastReceiveBuffer = Memory<byte>.Empty;
return merged;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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([email protected]) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

/// <summary>
/// Handles fixed-length data packages by processing incoming data of a specified length.
/// </summary>
/// <remarks>This class is designed to handle data packages with a fixed length, as specified during
/// initialization. It extends <see cref="DataPackageHandlerBase"/> and overrides its behavior to process fixed-length
/// data.</remarks>
/// <param name="length">The data package total data length.</param>
public class FixLengthDataPackageHandler(int length) : DataPackageHandlerBase
{
private readonly Memory<byte> _data = new byte[length];

private int _receivedLength;

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public override async Task ReceiveAsync(Memory<byte> data)
{
// 处理上次粘包数据
data = ConcatBuffer(data);

// 拷贝数据
var len = length - _receivedLength;
var segment = data.Length > len ? data[..len] : data;
segment.CopyTo(_data[_receivedLength..]);

if(data.Length > len)
{
HandlerStickyPackage(data, data.Length - len);
}

// 更新已接收长度
_receivedLength += segment.Length;

// 如果已接收长度等于总长度则触发回调
if (_receivedLength == length)
{
// 重置已接收长度
_receivedLength = 0;
if (ReceivedCallBack != null)
{
await ReceivedCallBack(_data);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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([email protected]) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

/// <summary>
/// Defines an interface for adapting data packages to and from a TCP socket connection.
/// </summary>
/// <remarks>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.</remarks>
public interface IDataPackageHandler
{
/// <summary>
/// Gets or sets the callback function to be invoked when data is received asynchronously.
/// </summary>
Func<Memory<byte>, Task>? ReceivedCallBack { get; set; }

/// <summary>
/// Sends the specified data asynchronously to the target destination.
/// </summary>
/// <remarks>The method performs an asynchronous operation to send the provided data. The caller must
/// ensure that the data is valid and non-empty. The returned memory block may contain a response or acknowledgment
/// depending on the implementation of the target destination.</remarks>
/// <param name="data">The data to be sent, represented as a block of memory.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a <see cref="Memory{T}"/> of <see
/// cref="byte"/> representing the response or acknowledgment received from the target destination.</returns>
Task<Memory<byte>> SendAsync(Memory<byte> data);

/// <summary>
/// Asynchronously receives data from a source and writes it into the provided memory buffer.
/// </summary>
/// <remarks>This method does not guarantee that the entire buffer will be filled. The number of bytes
/// written depends on the availability of data.</remarks>
/// <param name="data">The memory buffer to store the received data. The buffer must be writable and have sufficient capacity.</param>
/// <returns>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.</returns>
Task ReceiveAsync(Memory<byte> data);
}
Loading
Loading