From e2607a5eb1c7a89eecfaba7f5ff6d817ead937a5 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 11 Jul 2025 13:24:32 +0800 Subject: [PATCH 01/14] =?UTF-8?q?doc:=20=E5=A2=9E=E5=8A=A0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=A4=84=E7=90=86=E5=99=A8=E6=96=87=E6=A1=A3=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/Sockets/Adapters.razor | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor b/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor index 8bf08597bda..e1402fcef03 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor @@ -39,7 +39,11 @@
  • 响应头为 4 字节定长,响应体为 8 个字节定长
  • 响应体为字符串类型数据
  • -

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

    +

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

    +
    private readonly DataPackageAdapter _dataAdapter = new()
     {
         // 数据适配器内部使用固定长度数据处理器
    
    From 98aaa5d140fb7fde8fe0605bb611044df8e9b4b0 Mon Sep 17 00:00:00 2001
    From: Argo Zhang 
    Date: Fri, 11 Jul 2025 14:34:53 +0800
    Subject: [PATCH 02/14] =?UTF-8?q?doc:=20=E5=A2=9E=E5=8A=A0=E8=87=AA?=
     =?UTF-8?q?=E5=8A=A8=E9=87=8D=E8=BF=9E=E7=A4=BA=E4=BE=8B?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    ---
     .../Samples/Sockets/AutoReconnects.razor      | 47 +++++++++
     .../Samples/Sockets/AutoReconnects.razor.cs   | 96 +++++++++++++++++++
     .../Samples/Sockets/AutoReconnects.razor.css  |  0
     .../Extensions/ServiceCollectionExtensions.cs |  1 +
     .../Services/MockDisconnectService.cs         | 64 +++++++++++++
     5 files changed, 208 insertions(+)
     create mode 100644 src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor
     create mode 100644 src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor.cs
     create mode 100644 src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor.css
     create mode 100644 src/BootstrapBlazor.Server/Services/MockDisconnectService.cs
    
    diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor
    new file mode 100644
    index 00000000000..806d04ef3f6
    --- /dev/null
    +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor
    @@ -0,0 +1,47 @@
    +@page "/socket/auto-connect"
    +@inject IStringLocalizer Localizer
    +
    +
    +    
    +
    +
    +

    @Localizer["AutoReconnectsTitle"]

    +

    @Localizer["AutoReconnectsDescription"]

    + + + + +

    本例中模拟自动重连的业务场景,在实际应用中我们可能建立的链路可能由于种种原因断开,所以就有自动重连的业务需求

    +

    例如:我们与一个远端节点建立连接后,不停地接收远端发送过来的数据,如果断开连接后需要自动重连后继续接收数据

    +

    通过 SocketClientOptions 配置类来开启本功能

    +
    var client = factory.GetOrCreate("demo-reconnect", op =>
    +{
    +    op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0);
    +    options.IsAutoReconnect = true;
    +    options.ReconnectInterval = 5000;
    +});
    +

    参数说明:

    +
      +
    • IsAutoReconnect 是否开启自动重连功能
    • +
    • ReconnectInterval 自动重连等待间隔 默认 5000 毫秒
    • +
    + +
    +
    + + +
    +
    + +
    +
    +
    diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor.cs new file mode 100644 index 00000000000..a3e0f5822cd --- /dev/null +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor.cs @@ -0,0 +1,96 @@ +// 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 System.Net; + +namespace BootstrapBlazor.Server.Components.Samples.Sockets; + +public partial class AutoReconnects : IDisposable +{ + [Inject, NotNull] + private ITcpSocketFactory? TcpSocketFactory { get; set; } + + private ITcpSocketClient _client = null!; + + private List _items = []; + + private readonly IPEndPoint _serverEndPoint = new(IPAddress.Loopback, 8901); + + /// + /// + /// + protected override void OnInitialized() + { + base.OnInitialized(); + + // 从服务中获取 Socket 实例 + _client = TcpSocketFactory.GetOrCreate("demo-auto-connect", options => + { + options.LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0); + options.IsAutoReconnect = true; + options.ReconnectInterval = 5000; + }); + _client.ReceivedCallBack += OnReceivedAsync; + } + + private async Task OnConnectAsync() + { + if (_client is { IsConnected: false }) + { + await _client.ConnectAsync(_serverEndPoint, CancellationToken.None); + } + } + + private async Task OnCloseAsync() + { + if (_client is { IsConnected: true }) + { + await _client.CloseAsync(); + } + } + + private Task OnClear() + { + _items = []; + return Task.CompletedTask; + } + + private async ValueTask OnReceivedAsync(ReadOnlyMemory data) + { + // 将数据显示为十六进制字符串 + var payload = System.Text.Encoding.UTF8.GetString(data.Span); + _items.Add(data.IsEmpty + ? new ConsoleMessageItem { Message = $"当前连接已关闭,5s 后自动重连", Color = Color.Danger } + : new ConsoleMessageItem { Message = $"接收到来自站点的数据为 {payload}" }); + + // 保持队列中最大数量为 50 + while (_items.Count > 50) + { + _items.RemoveAt(0); + } + + await InvokeAsync(StateHasChanged); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + if (_client is { IsConnected: true }) + { + _client.ReceivedCallBack -= OnReceivedAsync; + } + } + } + + /// + /// + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } +} diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor.css b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor.css new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/BootstrapBlazor.Server/Extensions/ServiceCollectionExtensions.cs b/src/BootstrapBlazor.Server/Extensions/ServiceCollectionExtensions.cs index 5f4aba8bc95..bb1daacc3a7 100644 --- a/src/BootstrapBlazor.Server/Extensions/ServiceCollectionExtensions.cs +++ b/src/BootstrapBlazor.Server/Extensions/ServiceCollectionExtensions.cs @@ -48,6 +48,7 @@ void Invoke(BootstrapBlazorOptions option) services.AddHostedService(); services.AddHostedService(); services.AddHostedService(); + services.AddHostedService(); // 增加通用服务 services.AddBootstrapBlazorServices(); diff --git a/src/BootstrapBlazor.Server/Services/MockDisconnectService.cs b/src/BootstrapBlazor.Server/Services/MockDisconnectService.cs new file mode 100644 index 00000000000..ae26367c8db --- /dev/null +++ b/src/BootstrapBlazor.Server/Services/MockDisconnectService.cs @@ -0,0 +1,64 @@ +// 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 System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Longbow.Tasks.Services; + +/// +/// 模拟 Socket 自动断开服务端服务类 +/// +internal class MockDisconnectServerService(ILogger logger) : BackgroundService +{ + /// + /// 运行任务 + /// + /// + /// + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var server = new TcpListener(IPAddress.Loopback, 8901); + server.Start(); + while (stoppingToken is { IsCancellationRequested: false }) + { + try + { + var client = await server.AcceptTcpClientAsync(stoppingToken); + _ = Task.Run(() => OnDataHandlerAsync(client, stoppingToken), stoppingToken); + } + catch { } + } + } + + private async Task OnDataHandlerAsync(TcpClient client, CancellationToken stoppingToken) + { + // 方法目的: + // 收到消息后发送自定义通讯协议的响应数据 + // 响应头 + 响应体 + await using var stream = client.GetStream(); + while (stoppingToken is { IsCancellationRequested: false }) + { + try + { + // 发送数据 + await stream.WriteAsync(Encoding.UTF8.GetBytes(DateTime.Now.ToString("yyyyMMddHHmmss")), stoppingToken); + await Task.Delay(2000, stoppingToken); + + // 主动关闭连接 + client.Close(); + } + catch (OperationCanceledException) { break; } + catch (IOException) { break; } + catch (SocketException) { break; } + catch (Exception ex) + { + logger.LogError(ex, "MockDisconnectServerService encountered an error while sending data."); + break; + } + } + } +} From 5f4c53bb41602968ae5fc64879f922e5aa9bc688 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 11 Jul 2025 14:35:08 +0800 Subject: [PATCH 03/14] =?UTF-8?q?doc:=20=E5=A2=9E=E5=8A=A0=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E9=87=8D=E8=BF=9E=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/MenusLocalizerExtensions.cs | 6 ++++++ src/BootstrapBlazor.Server/docs.json | 5 +---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs index f145b2ccf70..905afb6cf31 100644 --- a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs +++ b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs @@ -224,6 +224,12 @@ void AddSocket(DemoMenuItem item) IsNew = true, Text = Localizer["DataPackageAdapter"], Url = "socket/adapter" + }, + new() + { + IsNew = true, + Text = Localizer["AutoConnect"], + Url = "socket/auto-connect" } }; AddBadge(item, count: 1); diff --git a/src/BootstrapBlazor.Server/docs.json b/src/BootstrapBlazor.Server/docs.json index 662d1eaed1c..71a4b1105d6 100644 --- a/src/BootstrapBlazor.Server/docs.json +++ b/src/BootstrapBlazor.Server/docs.json @@ -242,10 +242,7 @@ "meet": "Meets", "vditor": "Vditors", "socket-factory": "SocketFactories", - "office-viewer": "OfficeViewers", - "socket/manual-receive": "Sockets\\ManualReceives", - "socket/auto-receive": "Sockets\\AutoReceives", - "socket/adapter": "Sockets\\Adapters" + "office-viewer": "OfficeViewers" }, "video": { "table": "BV1ap4y1x7Qn?p=1", From 2359a1548e98adb2bb6df85a6f38b18183f6a650 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 11 Jul 2025 15:30:30 +0800 Subject: [PATCH 04/14] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20Connect=20?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E5=9B=9E=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TcpSocket/DefaultTcpSocketClient.cs | 18 ++++++++++++++++++ .../Services/TcpSocket/ITcpSocketClient.cs | 10 ++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs b/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs index 5d552b471a7..394f1fd44fc 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs @@ -49,6 +49,16 @@ class DefaultTcpSocketClient(SocketClientOptions options) : ITcpSocketClient /// 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; @@ -94,7 +104,15 @@ public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken try { + if (OnConnecting != null) + { + await OnConnecting(); + } ret = await ConnectCoreAsync(SocketClientProvider, endPoint, connectionToken); + if (OnConnected != null) + { + await OnConnected(); + } } catch (OperationCanceledException ex) { diff --git a/src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs b/src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs index c764661c93b..ae965fb1a63 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs @@ -38,6 +38,16 @@ public interface ITcpSocketClient : IAsyncDisposable /// 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. /// From 991f678e650edabe07023e65071f88afc205e0bd Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 11 Jul 2025 15:31:08 +0800 Subject: [PATCH 05/14] =?UTF-8?q?feat:=20=E6=8B=86=E5=88=86=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E9=87=8D=E8=BF=9E=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TcpSocket/DefaultTcpSocketClient.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs b/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs index 394f1fd44fc..d32785e2728 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs @@ -134,23 +134,26 @@ public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken // 释放信号量 _semaphoreSlim.Release(); - if (!ret && reconnect) + if (reconnect) { - Reconnect(); - } + _autoConnectTokenSource = new(); + if (!ret) + { + Reconnect(); + } + } return ret; } private void Reconnect() { - if (options.IsAutoReconnect && _remoteEndPoint != null) + if (_autoConnectTokenSource != null && options.IsAutoReconnect && _remoteEndPoint != null) { Task.Run(async () => { try { - _autoConnectTokenSource ??= new(); await Task.Delay(options.ReconnectInterval, _autoConnectTokenSource.Token).ConfigureAwait(false); await ConnectAsync(_remoteEndPoint, _autoConnectTokenSource.Token).ConfigureAwait(false); } @@ -162,7 +165,7 @@ private void Reconnect() private async ValueTask ConnectCoreAsync(ISocketClientProvider provider, IPEndPoint endPoint, CancellationToken token) { // 释放资源 - await CloseAsync(); + await CloseCoreAsync(); // 创建新的 TcpClient 实例 provider.LocalEndPoint = Options.LocalEndPoint; @@ -378,6 +381,19 @@ private void Log(LogLevel logLevel, Exception? ex, string? message) /// /// public async ValueTask CloseAsync() + { + // 取消重连任务 + if (_autoConnectTokenSource != null) + { + _autoConnectTokenSource.Cancel(); + _autoConnectTokenSource.Dispose(); + _autoConnectTokenSource = null; + } + + await CloseCoreAsync(); + } + + private async ValueTask CloseCoreAsync() { // 取消接收数据的任务 if (_receiveCancellationTokenSource != null) @@ -386,6 +402,7 @@ public async ValueTask CloseAsync() _receiveCancellationTokenSource.Dispose(); _receiveCancellationTokenSource = null; } + if (SocketClientProvider != null) { await SocketClientProvider.CloseAsync(); @@ -404,14 +421,6 @@ private async ValueTask DisposeAsync(bool disposing) { if (disposing) { - // 取消重连任务 - if (_autoConnectTokenSource != null) - { - _autoConnectTokenSource.Cancel(); - _autoConnectTokenSource.Dispose(); - _autoConnectTokenSource = null; - } - await CloseAsync(); } } From 4f92bd14f1bfc5817a7371cd13b0e4158a3b6f94 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 11 Jul 2025 15:32:10 +0800 Subject: [PATCH 06/14] =?UTF-8?q?feat:=20=E8=BF=9C=E7=AB=AF=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E5=90=8E=E9=94=80=E6=AF=81=20TcpClient=20=E5=AE=9E?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/TcpSocket/DefaultSocketClientProvider.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/BootstrapBlazor/Services/TcpSocket/DefaultSocketClientProvider.cs b/src/BootstrapBlazor/Services/TcpSocket/DefaultSocketClientProvider.cs index 74b36c32e22..b07cca71607 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DefaultSocketClientProvider.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DefaultSocketClientProvider.cs @@ -69,6 +69,11 @@ public async ValueTask ReceiveAsync(Memory buffer, CancellationToken { var stream = _client.GetStream(); len = await stream.ReadAsync(buffer, token).ConfigureAwait(false); + + if (len == 0) + { + _client.Close(); + } } return len; } From d962fd02c4e3bbd62f4ab1179a842c069d98a82b Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 11 Jul 2025 15:32:24 +0800 Subject: [PATCH 07/14] =?UTF-8?q?doc:=20=E5=A2=9E=E5=8A=A0=E9=87=8D?= =?UTF-8?q?=E8=BF=9E=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/Sockets/AutoReconnects.razor | 8 -------- .../Samples/Sockets/AutoReconnects.razor.cs | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor index 806d04ef3f6..a99916a6780 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor @@ -1,14 +1,6 @@ @page "/socket/auto-connect" @inject IStringLocalizer Localizer - - - -

    @Localizer["AutoReconnectsTitle"]

    @Localizer["AutoReconnectsDescription"]

    diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor.cs index a3e0f5822cd..4525134c0fc 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor.cs @@ -7,6 +7,9 @@ namespace BootstrapBlazor.Server.Components.Samples.Sockets; +/// +/// 自动重连示例组件 +/// public partial class AutoReconnects : IDisposable { [Inject, NotNull] @@ -33,6 +36,16 @@ protected override void OnInitialized() options.ReconnectInterval = 5000; }); _client.ReceivedCallBack += OnReceivedAsync; + _client.OnConnecting = async () => + { + _items.Add(new ConsoleMessageItem { Message = $"{DateTime.Now} 正在连接到 {_serverEndPoint},请稍候..." }); + await InvokeAsync(StateHasChanged); + }; + _client.OnConnected = async () => + { + _items.Add(new ConsoleMessageItem { Message = $"{DateTime.Now} 已连接到 {_serverEndPoint},等待接收数据", Color = Color.Success }); + await InvokeAsync(StateHasChanged); + }; } private async Task OnConnectAsync() @@ -62,8 +75,8 @@ private async ValueTask OnReceivedAsync(ReadOnlyMemory data) // 将数据显示为十六进制字符串 var payload = System.Text.Encoding.UTF8.GetString(data.Span); _items.Add(data.IsEmpty - ? new ConsoleMessageItem { Message = $"当前连接已关闭,5s 后自动重连", Color = Color.Danger } - : new ConsoleMessageItem { Message = $"接收到来自站点的数据为 {payload}" }); + ? new ConsoleMessageItem { Message = $"{DateTime.Now} 当前连接已关闭,5s 后自动重连", Color = Color.Danger } + : new ConsoleMessageItem { Message = $"{DateTime.Now} 接收到来自站点的数据为 {payload}" }); // 保持队列中最大数量为 50 while (_items.Count > 50) From b66127998d2ed5e5a96a9a3865faa73a451a0b1e Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 11 Jul 2025 15:40:11 +0800 Subject: [PATCH 08/14] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=20OnConnect=20?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTest/Services/TcpSocketFactoryTest.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/UnitTest/Services/TcpSocketFactoryTest.cs b/test/UnitTest/Services/TcpSocketFactoryTest.cs index b18f2f74494..9a07d464d3d 100644 --- a/test/UnitTest/Services/TcpSocketFactoryTest.cs +++ b/test/UnitTest/Services/TcpSocketFactoryTest.cs @@ -274,13 +274,27 @@ public async Task ReceiveAsync_InvalidOperationException() [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); From e3b18d2b28ca0c0089813da349a133eb9b1c32f2 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 11 Jul 2025 16:04:43 +0800 Subject: [PATCH 09/14] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=BC=80=E5=90=AF=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TcpSocket/DefaultTcpSocketClient.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs b/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs index d32785e2728..dbbb863d05d 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs @@ -242,10 +242,7 @@ public async ValueTask SendAsync(ReadOnlyMemory data, CancellationTo Log(LogLevel.Error, ex, $"TCP Socket send failed from {_localEndPoint} to {_remoteEndPoint}"); } - if (options.EnableLog) - { - Log(LogLevel.Information, null, $"Sending data from {_localEndPoint} to {_remoteEndPoint}, Data Length: {data.Length} Data Content: {BitConverter.ToString(data.ToArray())} Result: {ret}"); - } + 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) { @@ -355,10 +352,7 @@ private async ValueTask ReceiveCoreAsync(ISocketClientProvider client, Memo Log(LogLevel.Error, ex, $"TCP Socket receive failed from {_localEndPoint} to {_remoteEndPoint}"); } - if (options.EnableLog) - { - Log(LogLevel.Information, null, $"Receiving data from {_localEndPoint} to {_remoteEndPoint}, Data Length: {len} Data Content: {BitConverter.ToString(buffer.ToArray())}"); - } + Log(LogLevel.Information, null, $"Receiving data from {_localEndPoint} to {_remoteEndPoint}, Data Length: {len} Data Content: {BitConverter.ToString(buffer.ToArray())}"); if (len == 0 && reconnect) { @@ -373,8 +367,11 @@ private async ValueTask ReceiveCoreAsync(ISocketClientProvider client, Memo /// private void Log(LogLevel logLevel, Exception? ex, string? message) { - Logger ??= ServiceProvider?.GetRequiredService>(); - Logger?.Log(logLevel, ex, "{Message}", message); + if (options.EnableLog) + { + Logger ??= ServiceProvider?.GetRequiredService>(); + Logger?.Log(logLevel, ex, "{Message}", message); + } } /// From e79264ff8d2ab45aee48c0ff9607d2dc922936ae Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 11 Jul 2025 16:04:59 +0800 Subject: [PATCH 10/14] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8=E6=96=AD=E5=BC=80=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefaultSocketClientProviderTest.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/test/UnitTest/Services/DefaultSocketClientProviderTest.cs b/test/UnitTest/Services/DefaultSocketClientProviderTest.cs index 8e77c51a936..51918355063 100644 --- a/test/UnitTest/Services/DefaultSocketClientProviderTest.cs +++ b/test/UnitTest/Services/DefaultSocketClientProviderTest.cs @@ -3,7 +3,9 @@ // 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 System.Net; +using System.Net.Sockets; namespace UnitTest.Services; @@ -26,6 +28,35 @@ public async Task DefaultSocketClient_Ok() 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() { @@ -45,4 +76,35 @@ public void SocketClientOptions_Ok() 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(); + } + }); + } + } } From 5999383507f6ae0bd74828517b651a68ec8617c3 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 11 Jul 2025 16:07:28 +0800 Subject: [PATCH 11/14] =?UTF-8?q?doc:=20=E5=A2=9E=E5=8A=A0=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E9=87=8D=E8=BF=9E=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/MenusLocalizerExtensions.cs | 2 +- src/BootstrapBlazor.Server/Locales/en-US.json | 3 ++- src/BootstrapBlazor.Server/Locales/zh-CN.json | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs index 905afb6cf31..719494a3aa6 100644 --- a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs +++ b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs @@ -228,7 +228,7 @@ void AddSocket(DemoMenuItem item) new() { IsNew = true, - Text = Localizer["AutoConnect"], + Text = Localizer["SocketAutoConnect"], Url = "socket/auto-connect" } }; diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index d23e649bcd0..2a0c180a06d 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -4834,7 +4834,8 @@ "SocketComponents": "ITcpSocketFactory", "SocketAutoReceive": "Auto Receive", "SocketManualReceive": "Manual Receive", - "DataPackageAdapter": "DataPackageAdapter" + "DataPackageAdapter": "DataPackageAdapter", + "SocketAutoConnect": "Reconnect" }, "BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": { "TablesHeaderTitle": "Header grouping function", diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index 8dae547ab56..d71254995d7 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -4834,7 +4834,8 @@ "SocketComponents": "Socket 服务", "SocketAutoReceive": "自动接收数据", "SocketManualReceive": "手动接收数据", - "DataPackageAdapter": "数据处理器" + "DataPackageAdapter": "数据处理器", + "SocketAutoConnect": "自动重连" }, "BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": { "TablesHeaderTitle": "表头分组功能", From c7ba068414a38ee3c73773722881890ee7f0fc76 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 11 Jul 2025 16:08:48 +0800 Subject: [PATCH 12/14] =?UTF-8?q?doc:=20=E5=A2=9E=E5=8A=A0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor.Server/docs.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/docs.json b/src/BootstrapBlazor.Server/docs.json index 71a4b1105d6..f0824c531be 100644 --- a/src/BootstrapBlazor.Server/docs.json +++ b/src/BootstrapBlazor.Server/docs.json @@ -242,7 +242,11 @@ "meet": "Meets", "vditor": "Vditors", "socket-factory": "SocketFactories", - "office-viewer": "OfficeViewers" + "office-viewer": "OfficeViewers", + "socket/manual-receive": "Sockets\\ManualReceives", + "socket/auto-receive": "Sockets\\AutoReceives", + "socket/adapter": "Sockets\\Adapters", + "socket/auto-connect": "Sockets\\AutoReconnects" }, "video": { "table": "BV1ap4y1x7Qn?p=1", From 774b46f6ecee0928f8ea851f1ba5dc1d001e07cc Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 11 Jul 2025 16:14:07 +0800 Subject: [PATCH 13/14] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E9=87=8D=E8=BF=9E=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/Sockets/AutoReconnects.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/AutoReconnects.razor b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor index a99916a6780..f9e78a9d3b1 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReconnects.razor @@ -23,7 +23,7 @@
  • IsAutoReconnect 是否开启自动重连功能
  • ReconnectInterval 自动重连等待间隔 默认 5000 毫秒
  • - +

    本例中点击 连接 按钮后程序连接到一个发送数据后自动关闭的模拟服务端,通过输出日志查看运行情况,点击 断开 按钮后程序停止自动重连