diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor.cs index 4baa2bf2d1e..9125c8f7978 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor.cs @@ -83,7 +83,7 @@ private async Task OnSendAsync() // 记录日志 _items.Add(new ConsoleMessageItem() { - Message = $"{DateTime.Now}: 发送数据 {_client.LocalEndPoint} - {_serverEndPoint} Data {BitConverter.ToString(data)} {state}" + Message = $"{DateTime.Now}: 发送数据 {_client.LocalEndPoint} - {_serverEndPoint} Data: {BitConverter.ToString(data)} {state}" }); } } @@ -109,7 +109,7 @@ private async Task UpdateReceiveLog(ReadOnlyMemory data) _items.Add(new ConsoleMessageItem { - Message = $"{DateTime.Now}: 接收数据 {_client.LocalEndPoint} - {_serverEndPoint} Data {payload} HEX: {body}", + Message = $"{DateTime.Now}: 接收数据 {_client.LocalEndPoint} - {_serverEndPoint} Data: {payload} HEX: {body}", Color = Color.Success }); diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Receives.razor b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReceives.razor similarity index 96% rename from src/BootstrapBlazor.Server/Components/Samples/Sockets/Receives.razor rename to src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReceives.razor index f62065ad5cd..1a1231fe020 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Receives.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReceives.razor @@ -1,5 +1,5 @@ -@page "/socket/receive" -@inject IStringLocalizer Localizer +@page "/socket/auto-receive" +@inject IStringLocalizer Localizer

@Localizer["ReceivesTitle"]

@Localizer["ReceivesDescription"]

diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Receives.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReceives.razor.cs similarity index 94% rename from src/BootstrapBlazor.Server/Components/Samples/Sockets/Receives.razor.cs rename to src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReceives.razor.cs index c8977842423..1ef1b705d9d 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Receives.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/AutoReceives.razor.cs @@ -10,7 +10,7 @@ namespace BootstrapBlazor.Server.Components.Samples.Sockets; /// /// 接收电文示例 /// -public partial class Receives : IDisposable +public partial class AutoReceives : IDisposable { [Inject, NotNull] private ITcpSocketFactory? TcpSocketFactory { get; set; } @@ -29,7 +29,7 @@ protected override void OnInitialized() base.OnInitialized(); // 从服务中获取 Socket 实例 - _client = TcpSocketFactory.GetOrCreate("demo-receive", options => + _client = TcpSocketFactory.GetOrCreate("demo-auto-receive", options => { options.LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0); }); diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/ManualReceives.razor b/src/BootstrapBlazor.Server/Components/Samples/Sockets/ManualReceives.razor new file mode 100644 index 00000000000..dcd782a500c --- /dev/null +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/ManualReceives.razor @@ -0,0 +1,34 @@ +@page "/socket/manual-receive" +@inject IStringLocalizer Localizer + +

@Localizer["ReceivesTitle"]

+

@Localizer["ReceivesDescription"]

+ + + + +

本例中连接一个模拟时间同步服务,采用一发一收的方式进行通讯,连接后发送查询电文,接收到服务器端响应时间戳电文数据

+
    +
  • 点击 连接 按钮后通过 ITcpSocketFactory 服务实例创建的 ITcpSocketClient 对象连接到网站模拟 TcpServer
  • +
  • 点击 断开 按钮调用 CloseAsync 方法断开 Socket 连接
  • +
  • 点击 发送 按钮调用 SendAsync 方法发送请求数据
  • +
+

使用 ReceiveAsync 方法主动接收数据

+
+
+ + + +
+
+ +
+
+
+ diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/ManualReceives.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Sockets/ManualReceives.razor.cs new file mode 100644 index 00000000000..892f21be9b8 --- /dev/null +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/ManualReceives.razor.cs @@ -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(argo@live.ca) Website: https://www.blazor.zone + +using System.Net; +using System.Text; + +namespace BootstrapBlazor.Server.Components.Samples.Sockets; + +/// +/// 接收电文示例 +/// +public partial class ManualReceives +{ + [Inject, NotNull] + private ITcpSocketFactory? TcpSocketFactory { get; set; } + + private ITcpSocketClient _client = null!; + + private List _items = []; + + private readonly IPEndPoint _serverEndPoint = new(IPAddress.Loopback, 8810); + + /// + /// + /// + protected override void OnInitialized() + { + base.OnInitialized(); + + // 从服务中获取 Socket 实例 + _client = TcpSocketFactory.GetOrCreate("demo-manual-receive", options => + { + options.LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0); + options.IsAutoReceive = false; + }); + } + + 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 Task OnSendAsync() + { + if (_client is { IsConnected: true }) + { + // 准备通讯数据 + var data = new byte[2] { 0x01, 0x02 }; + var result = await _client.SendAsync(data, CancellationToken.None); + var state = result ? "成功" : "失败"; + + // 记录日志 + _items.Add(new ConsoleMessageItem() + { + Message = $"{DateTime.Now}: 发送数据 {_client.LocalEndPoint} - {_serverEndPoint} Data: {BitConverter.ToString(data)} {state}" + }); + + if (result) + { + var buffer = await _client.ReceiveAsync(CancellationToken.None); + var payload = buffer.ToArray(); + _items.Add(new ConsoleMessageItem() + { + Message = $"{DateTime.Now}: 接收数据 {_client.LocalEndPoint} - {_serverEndPoint} Data {Encoding.UTF8.GetString(payload)} HEX: {BitConverter.ToString(payload)} 成功", + Color = Color.Success + }); + } + } + } +} diff --git a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs index cf95b837594..f145b2ccf70 100644 --- a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs +++ b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs @@ -86,7 +86,7 @@ public static List GenerateMenus(this IStringLocalizer Locali item = new DemoMenuItem() { Text = Localizer["SocketComponents"], - Icon = "fa-fw fa-solid fa-square-binary text-danger" + Icon = "fa-fw fa-solid fa-satellite-dish text-danger" }; AddSocket(item); @@ -210,13 +210,19 @@ void AddSocket(DemoMenuItem item) new() { IsNew = true, - Text = Localizer["SocketReceive"], - Url = "socket/receive" + Text = Localizer["SocketManualReceive"], + Url = "socket/manual-receive" }, new() { IsNew = true, - Text = Localizer["SocketDataAdapter"], + Text = Localizer["SocketAutoReceive"], + Url = "socket/auto-receive" + }, + new() + { + IsNew = true, + Text = Localizer["DataPackageAdapter"], Url = "socket/adapter" } }; diff --git a/src/BootstrapBlazor.Server/Extensions/ServiceCollectionExtensions.cs b/src/BootstrapBlazor.Server/Extensions/ServiceCollectionExtensions.cs index 2687f60d537..5f4aba8bc95 100644 --- a/src/BootstrapBlazor.Server/Extensions/ServiceCollectionExtensions.cs +++ b/src/BootstrapBlazor.Server/Extensions/ServiceCollectionExtensions.cs @@ -46,6 +46,7 @@ void Invoke(BootstrapBlazorOptions option) services.AddHostedService(); services.AddHostedService(); services.AddHostedService(); + services.AddHostedService(); services.AddHostedService(); // 增加通用服务 diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index 6ec42de57e6..b166736940e 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -4824,7 +4824,9 @@ "TcpSocketFactory": "ITcpSocketFactory", "OfficeViewer": "Office Viewer", "SocketComponents": "ITcpSocketFactory", - "SocketReceive": "Receive" + "SocketAutoReceive": "Auto Receive", + "SocketManualReceive": "Manual Receive", + "DataPackageAdapter": "DataPackageAdapter" }, "BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": { "TablesHeaderTitle": "Header grouping function", @@ -7080,9 +7082,15 @@ "OfficeViewerNormalIntro": "Set the document URL for preview by configuring the Url value", "OfficeViewerToastSuccessfulContent": "Office document loaded successfully" }, - "BootstrapBlazor.Server.Components.Samples.Sockets.Receives": { + "BootstrapBlazor.Server.Components.Samples.Sockets.ManualReceives": { + "ReceivesTitle": "Manual Receive", + "ReceivesDescription": "Receive data through call ReceiveAsync and display it", + "NormalTitle": "Basic usage", + "NormalIntro": "After the connection is established, the data sent by the server is received through the ReceiveAsync callback method. The data problems of sticking and splitting packets need to be handled by the client." + }, + "BootstrapBlazor.Server.Components.Samples.Sockets.AutoReceives": { "ReceivesTitle": "Socket Receive", - "ReceivesDescription": "Receive data through Socket and display it", + "ReceivesDescription": "Receive data through ReceivedCallBack and display it", "NormalTitle": "Basic usage", "NormalIntro": "After connecting, the timestamp data sent by the server is automatically received through the ReceivedCallBack callback method" }, diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index fdd38117ebf..4aa07ac4d98 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -4824,7 +4824,9 @@ "TcpSocketFactory": "套接字服务 ITcpSocketFactory", "OfficeViewer": "Office 文档预览组件", "SocketComponents": "Socket 服务", - "SocketReceive": "接收数据" + "SocketAutoReceive": "自动接收数据", + "SocketManualReceive": "手动接收数据", + "DataPackageAdapter": "数据处理器" }, "BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": { "TablesHeaderTitle": "表头分组功能", @@ -7080,9 +7082,15 @@ "OfficeViewerNormalIntro": "通过设置 Url 值设置预览文档地址", "OfficeViewerToastSuccessfulContent": "Office 文档加载成功" }, - "BootstrapBlazor.Server.Components.Samples.Sockets.Receives": { - "ReceivesTitle": "Socket 接收示例", - "ReceivesDescription": "通过 Socket 接收数据并且显示", + "BootstrapBlazor.Server.Components.Samples.Sockets.ManualReceives": { + "ReceivesTitle": "手动接收示例", + "ReceivesDescription": "通过调用 ReceiveAsync 接收数据并且显示", + "NormalTitle": "基本用法", + "NormalIntro": "连接后通过 ReceiveAsync 回调方法接收服务端发送来的数据,需要自行处理粘包分包的数据问题" + }, + "BootstrapBlazor.Server.Components.Samples.Sockets.AutoReceives": { + "ReceivesTitle": "自动接收示例", + "ReceivesDescription": "通过 ReceiveCallback 接收数据并且显示", "NormalTitle": "基本用法", "NormalIntro": "连接后通过 ReceivedCallBack 回调方法自动接收服务端发送来的时间戳数据" }, diff --git a/src/BootstrapBlazor.Server/Services/MockSendReceiveSocketServerService.cs b/src/BootstrapBlazor.Server/Services/MockSendReceiveSocketServerService.cs new file mode 100644 index 00000000000..18a784227b7 --- /dev/null +++ b/src/BootstrapBlazor.Server/Services/MockSendReceiveSocketServerService.cs @@ -0,0 +1,67 @@ +// 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; + +namespace Longbow.Tasks.Services; + +/// +/// 模拟 Socket 服务端服务类 +/// +class MockSendReceiveSocketServerService(ILogger logger) : BackgroundService +{ + /// + /// 运行任务 + /// + /// + /// + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var server = new TcpListener(IPAddress.Loopback, 8810); + server.Start(); + while (stoppingToken is { IsCancellationRequested: false }) + { + try + { + var client = await server.AcceptTcpClientAsync(stoppingToken); + _ = Task.Run(() => MockSendAsync(client, stoppingToken), stoppingToken); + } + catch { } + } + } + + private async Task MockSendAsync(TcpClient client, CancellationToken stoppingToken) + { + // 方法目的: + // 接收到数据后会送当前时间戳数据包到客户端 + await using var stream = client.GetStream(); + while (stoppingToken is { IsCancellationRequested: false }) + { + try + { + // 接收数据 + var len = await stream.ReadAsync(new byte[1024], stoppingToken); + if (len == 0) + { + // 断开连接 + break; + } + // 模拟服务,对接收到的消息未做处理 + // 模拟一发一收的通讯方法 + var data = System.Text.Encoding.UTF8.GetBytes(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + await stream.WriteAsync(data, stoppingToken); + } + catch (OperationCanceledException) { break; } + catch (IOException) { break; } + catch (SocketException) { break; } + catch (Exception ex) + { + logger.LogError(ex, "MockSendReceiveSocketServerService encountered an error while sending data."); + break; + } + } + } +} diff --git a/src/BootstrapBlazor.Server/docs.json b/src/BootstrapBlazor.Server/docs.json index 20fe9370e93..662d1eaed1c 100644 --- a/src/BootstrapBlazor.Server/docs.json +++ b/src/BootstrapBlazor.Server/docs.json @@ -243,7 +243,8 @@ "vditor": "Vditors", "socket-factory": "SocketFactories", "office-viewer": "OfficeViewers", - "socket/receive": "Sockets\\Receives", + "socket/manual-receive": "Sockets\\ManualReceives", + "socket/auto-receive": "Sockets\\AutoReceives", "socket/adapter": "Sockets\\Adapters" }, "video": { diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapter.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackageAdapter/DataPackageAdapter.cs similarity index 100% rename from src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapter.cs rename to src/BootstrapBlazor/Services/TcpSocket/DataPackageAdapter/DataPackageAdapter.cs diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageAdapter.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackageAdapter/IDataPackageAdapter.cs similarity index 100% rename from src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageAdapter.cs rename to src/BootstrapBlazor/Services/TcpSocket/DataPackageAdapter/IDataPackageAdapter.cs diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageHandlerBase.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackageHandler/DataPackageHandlerBase.cs similarity index 100% rename from src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageHandlerBase.cs rename to src/BootstrapBlazor/Services/TcpSocket/DataPackageHandler/DataPackageHandlerBase.cs diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DelimiterDataPackageHandler.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackageHandler/DelimiterDataPackageHandler.cs similarity index 100% rename from src/BootstrapBlazor/Services/TcpSocket/DataPackage/DelimiterDataPackageHandler.cs rename to src/BootstrapBlazor/Services/TcpSocket/DataPackageHandler/DelimiterDataPackageHandler.cs diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackageHandler/FixLengthDataPackageHandler.cs similarity index 100% rename from src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs rename to src/BootstrapBlazor/Services/TcpSocket/DataPackageHandler/FixLengthDataPackageHandler.cs diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackageHandler/IDataPackageHandler.cs similarity index 100% rename from src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs rename to src/BootstrapBlazor/Services/TcpSocket/DataPackageHandler/IDataPackageHandler.cs diff --git a/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs b/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs index 8c5134c8528..24e563ed1a4 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs @@ -109,10 +109,14 @@ public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken } catch (OperationCanceledException ex) { - var message = token.IsCancellationRequested - ? $"TCP Socket connect operation was canceled from {LocalEndPoint} to {endPoint}" - : $"TCP Socket connect operation timed out from {LocalEndPoint} to {endPoint}"; - Log(LogLevel.Warning, ex, message); + if (token.IsCancellationRequested) + { + Log(LogLevel.Warning, ex, $"TCP Socket connect operation was canceled from {LocalEndPoint} to {endPoint}"); + } + else + { + Log(LogLevel.Warning, ex, $"TCP Socket connect operation timed out from {LocalEndPoint} to {endPoint}"); + } } catch (Exception ex) { @@ -241,9 +245,14 @@ private async ValueTask ReceiveCoreAsync(ISocketClientProvider client, Memo } catch (OperationCanceledException ex) { - Log(LogLevel.Warning, ex, token.IsCancellationRequested - ? $"TCP Socket receive operation canceled from {_localEndPoint} to {_remoteEndPoint}" - : $"TCP Socket receive operation timed out from {_localEndPoint} to {_remoteEndPoint}"); + if (token.IsCancellationRequested) + { + Log(LogLevel.Warning, ex, $"TCP Socket receive operation canceled from {_localEndPoint} to {_remoteEndPoint}"); + } + else + { + Log(LogLevel.Warning, ex, $"TCP Socket receive operation timed out from {_localEndPoint} to {_remoteEndPoint}"); + } } catch (Exception ex) { diff --git a/test/UnitTest/Services/TcpSocketFactoryTest.cs b/test/UnitTest/Services/TcpSocketFactoryTest.cs index 1f7ccb317ae..e8f1220f84a 100644 --- a/test/UnitTest/Services/TcpSocketFactoryTest.cs +++ b/test/UnitTest/Services/TcpSocketFactoryTest.cs @@ -48,8 +48,12 @@ public async Task GetOrCreate_Ok() [Fact] public async Task ConnectAsync_Timeout() { - var client = CreateClient(); - client.Options.ConnectTimeout = 1; + var client = CreateClient(builder => + { + // 增加发送报错 MockSocket + builder.AddTransient(); + }); + client.Options.ConnectTimeout = 10; var connect = await client.ConnectAsync("localhost", 9999); Assert.False(connect); @@ -244,8 +248,14 @@ public async Task ReceiveAsync_Ok() var send = await client.SendAsync(data); Assert.True(send); + // 未设置数据处理器未开启自动接收时,调用 ReceiveAsync 方法获取数据 + // 需要自己处理粘包分包和业务问题 var payload = await client.ReceiveAsync(); - Assert.Equal(payload.ToArray(), [1, 2, 3, 4, 5]); + Assert.Equal([1, 2, 3, 4, 5], payload.ToArray()); + + // 由于服务器端模拟了拆包发送第二段数据,所以这里可以再次调用 ReceiveAsync 方法获取第二段数据 + payload = await client.ReceiveAsync(); + Assert.Equal([3, 4], payload.ToArray()); } [Fact] @@ -290,7 +300,7 @@ public async Task ReceiveAsync_Error() await client.SendAsync(data); await tcs.Task; - Assert.Equal(buffer.ToArray(), [1, 2, 3, 4, 5]); + Assert.Equal([1, 2, 3, 4, 5], buffer.ToArray()); // 关闭连接 StopTcpServer(server); @@ -330,7 +340,7 @@ public async Task FixLengthDataPackageHandler_Ok() Assert.True(result); await tcs.Task; - Assert.Equal(receivedBuffer.ToArray(), [1, 2, 3, 4, 5, 3, 4]); + Assert.Equal([1, 2, 3, 4, 5, 3, 4], receivedBuffer.ToArray()); // 关闭连接 await client.CloseAsync(); @@ -372,7 +382,7 @@ public async Task FixLengthDataPackageHandler_Sticky() await tcs.Task; // 验证接收到的数据 - Assert.Equal(receivedBuffer.ToArray(), [1, 2, 3, 4, 5, 3, 4]); + Assert.Equal([1, 2, 3, 4, 5, 3, 4], receivedBuffer.ToArray()); // 重置接收缓冲区 receivedBuffer = new byte[1024]; @@ -382,12 +392,12 @@ public async Task FixLengthDataPackageHandler_Sticky() await tcs.Task; // 验证第二次收到的数据 - Assert.Equal(receivedBuffer.ToArray(), [2, 2, 3, 4, 5, 6, 7]); + Assert.Equal([2, 2, 3, 4, 5, 6, 7], receivedBuffer.ToArray()); tcs = new TaskCompletionSource(); await tcs.Task; // 验证第三次收到的数据 - Assert.Equal(receivedBuffer.ToArray(), [3, 2, 3, 4, 5, 6, 7]); + Assert.Equal([3, 2, 3, 4, 5, 6, 7], receivedBuffer.ToArray()); // 关闭连接 await client.CloseAsync(); @@ -428,7 +438,7 @@ public async Task DelimiterDataPackageHandler_Ok() await tcs.Task; // 验证接收到的数据 - Assert.Equal(receivedBuffer.ToArray(), [1, 2, 3, 4, 5, 13, 10]); + Assert.Equal([1, 2, 3, 4, 5, 13, 10], receivedBuffer.ToArray()); // 等待第二次数据 receivedBuffer = new byte[1024]; @@ -436,7 +446,7 @@ public async Task DelimiterDataPackageHandler_Ok() await tcs.Task; // 验证接收到的数据 - Assert.Equal(receivedBuffer.ToArray(), [5, 6, 13, 10]); + Assert.Equal([5, 6, 13, 10], receivedBuffer.ToArray()); // 关闭连接 await client.CloseAsync(); @@ -480,8 +490,8 @@ public async Task TryConvertTo_Ok() await tcs.Task; Assert.NotNull(entity); - Assert.Equal(entity.Header, [1, 2, 3, 4, 5]); - Assert.Equal(entity.Body, [3, 4]); + Assert.Equal([1, 2, 3, 4, 5], entity.Header); + Assert.Equal([3, 4], entity.Body); // 测试异常流程 var adapter2 = new DataPackageAdapter(); @@ -696,6 +706,35 @@ public ValueTask SendAsync(ReadOnlyMemory data, CancellationToken to } } + class MockConnectTimeoutSocketProvider : ISocketClientProvider + { + public bool IsConnected { get; private set; } + + public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); + + public ValueTask CloseAsync() + { + return ValueTask.CompletedTask; + } + + public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) + { + await Task.Delay(1000, token); + IsConnected = false; + return false; + } + + public ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) + { + return ValueTask.FromResult(0); + } + + public ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) + { + return ValueTask.FromResult(true); + } + } + class MockSendTimeoutSocketProvider : ISocketClientProvider { public bool IsConnected { get; private set; }