Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
});
}
}
Expand All @@ -109,7 +109,7 @@ private async Task UpdateReceiveLog(ReadOnlyMemory<byte> 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
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@page "/socket/receive"
@inject IStringLocalizer<Receives> Localizer
@page "/socket/auto-receive"
@inject IStringLocalizer<AutoReceives> Localizer

<h3>@Localizer["ReceivesTitle"]</h3>
<h4>@Localizer["ReceivesDescription"]</h4>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace BootstrapBlazor.Server.Components.Samples.Sockets;
/// <summary>
/// 接收电文示例
/// </summary>
public partial class Receives : IDisposable
public partial class AutoReceives : IDisposable
{
[Inject, NotNull]
private ITcpSocketFactory? TcpSocketFactory { get; set; }
Expand All @@ -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);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@page "/socket/manual-receive"
@inject IStringLocalizer<ManualReceives> Localizer

<h3>@Localizer["ReceivesTitle"]</h3>
<h4>@Localizer["ReceivesDescription"]</h4>

<Notice></Notice>

<DemoBlock Title="@Localizer["NormalTitle"]"
Introduction="@Localizer["NormalIntro"]"
Name="Normal" ShowCode="false">
<p>本例中连接一个模拟时间同步服务,采用一发一收的方式进行通讯,连接后发送查询电文,接收到服务器端响应时间戳电文数据</p>
<ul class="ul-demo">
<li>点击 <b>连接</b> 按钮后通过 <code>ITcpSocketFactory</code> 服务实例创建的 <code>ITcpSocketClient</code> 对象连接到网站模拟 <code>TcpServer</code></li>
<li>点击 <b>断开</b> 按钮调用 <code>CloseAsync</code> 方法断开 Socket 连接</li>
<li>点击 <b>发送</b> 按钮调用 <code>SendAsync</code> 方法发送请求数据</li>
</ul>
<p>使用 <code>ReceiveAsync</code> 方法主动接收数据</p>
<div class="row form-inline g-3">
<div class="col-12 col-sm-6">
<Button Text="连接" Icon="fa-solid fa-play"
OnClick="OnConnectAsync" IsDisabled="@_client.IsConnected"></Button>
<Button Text="断开" Icon="fa-solid fa-stop" class="ms-2"
OnClick="OnCloseAsync" IsDisabled="@(!_client.IsConnected)"></Button>
<Button Text="发送" Icon="fa-solid fa-paper-plane" class="ms-2" IsAsync="true"
OnClick="OnSendAsync" IsDisabled="@(!_client.IsConnected)"></Button>
</div>
<div class="col-12">
<Console Items="@_items" Height="496" HeaderText="接收数据(间隔 10 秒)"
ShowAutoScroll="true" OnClear="@OnClear"></Console>
</div>
</div>
</DemoBlock>

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

using System.Net;
using System.Text;

namespace BootstrapBlazor.Server.Components.Samples.Sockets;

/// <summary>
/// 接收电文示例
/// </summary>
public partial class ManualReceives
{
[Inject, NotNull]
private ITcpSocketFactory? TcpSocketFactory { get; set; }

private ITcpSocketClient _client = null!;

private List<ConsoleMessageItem> _items = [];

private readonly IPEndPoint _serverEndPoint = new(IPAddress.Loopback, 8810);

/// <summary>
/// <inheritdoc/>
/// </summary>
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
});
}
}
}
}
14 changes: 10 additions & 4 deletions src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public static List<MenuItem> GenerateMenus(this IStringLocalizer<NavMenu> 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);

Expand Down Expand Up @@ -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"
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ void Invoke(BootstrapBlazorOptions option)
services.AddHostedService<ClearTempFilesService>();
services.AddHostedService<MockOnlineContributor>();
services.AddHostedService<MockReceiveSocketServerService>();
services.AddHostedService<MockSendReceiveSocketServerService>();
services.AddHostedService<MockCustomProtocolSocketServerService>();

// 增加通用服务
Expand Down
14 changes: 11 additions & 3 deletions src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -7080,9 +7082,15 @@
"OfficeViewerNormalIntro": "Set the document URL for preview by configuring the <code>Url</code> 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 <code>ReceiveAsync</code> 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 <code>ReceivedCallBack</code> callback method"
},
Expand Down
16 changes: 12 additions & 4 deletions src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -4824,7 +4824,9 @@
"TcpSocketFactory": "套接字服务 ITcpSocketFactory",
"OfficeViewer": "Office 文档预览组件",
"SocketComponents": "Socket 服务",
"SocketReceive": "接收数据"
"SocketAutoReceive": "自动接收数据",
"SocketManualReceive": "手动接收数据",
"DataPackageAdapter": "数据处理器"
},
"BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": {
"TablesHeaderTitle": "表头分组功能",
Expand Down Expand Up @@ -7080,9 +7082,15 @@
"OfficeViewerNormalIntro": "通过设置 <code>Url</code> 值设置预览文档地址",
"OfficeViewerToastSuccessfulContent": "Office 文档加载成功"
},
"BootstrapBlazor.Server.Components.Samples.Sockets.Receives": {
"ReceivesTitle": "Socket 接收示例",
"ReceivesDescription": "通过 Socket 接收数据并且显示",
"BootstrapBlazor.Server.Components.Samples.Sockets.ManualReceives": {
"ReceivesTitle": "手动接收示例",
"ReceivesDescription": "通过调用 ReceiveAsync 接收数据并且显示",
"NormalTitle": "基本用法",
"NormalIntro": "连接后通过 <code>ReceiveAsync</code> 回调方法接收服务端发送来的数据,需要自行处理粘包分包的数据问题"
},
"BootstrapBlazor.Server.Components.Samples.Sockets.AutoReceives": {
"ReceivesTitle": "自动接收示例",
"ReceivesDescription": "通过 ReceiveCallback 接收数据并且显示",
"NormalTitle": "基本用法",
"NormalIntro": "连接后通过 <code>ReceivedCallBack</code> 回调方法自动接收服务端发送来的时间戳数据"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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([email protected]) Website: https://www.blazor.zone

using System.Net;
using System.Net.Sockets;

namespace Longbow.Tasks.Services;

/// <summary>
/// 模拟 Socket 服务端服务类
/// </summary>
class MockSendReceiveSocketServerService(ILogger<MockReceiveSocketServerService> logger) : BackgroundService
{
/// <summary>
/// 运行任务
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
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;
}
}
}
}
3 changes: 2 additions & 1 deletion src/BootstrapBlazor.Server/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
23 changes: 16 additions & 7 deletions src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,14 @@ public async ValueTask<bool> 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)
{
Expand Down Expand Up @@ -241,9 +245,14 @@ private async ValueTask<int> 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)
{
Expand Down
Loading
Loading