Skip to content

Commit 5d1457b

Browse files
authored
doc(ITcpSocketClient): add Receive documentation (#6322)
* refactor: 精简代码提高可读性 * refactor: 更新连接状态返回值 * feat: 更新读取数据长度 * feat: 更新远端节点数据 * refactor: 更新自动接收逻辑增加已连接逻辑判断 * chore: bump version 9.8.0-beta07 * doc: 更新文档 * feat: 增加模拟 Socket 服务端服务 * doc: 更新文档 * doc: 增加 Socket 实战 * refactor: 代码格式化 * refactor: 更改 Func 类型支持异步 * refactor: 更改为私有属性 * doc: 更新示例 * test: 更新单元测试 * refactor: 重构 Socket Server 模拟器 * test: 更新单元测试 * doc: 完善示例 * refactor: 微调日志逻辑 * refactor: 增加异常捕获 * doc: 更新示例 * doc: 增加接收数据示例文档
1 parent 2abfb68 commit 5d1457b

File tree

15 files changed

+267
-46
lines changed

15 files changed

+267
-46
lines changed

src/BootstrapBlazor.Server/Components/Samples/SocketFactories.razor

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66

77
<p class="code-label">1. 服务注入</p>
88

9-
<Pre>[Inject]
10-
[NotNull]
11-
private ITcpSocketFactory? TcpSocketFactory { get; set; }</Pre>
9+
<Pre>services.AddBootstrapBlazorTcpSocketFactory();</Pre>
1210

1311
<p class="code-label">2. 使用服务</p>
1412
<p>调用 <code>TcpSocketFactory</code> 实例方法 <code>GetOrCreate</code> 即可得到一个 <code>ITcpSocketClient</code> 实例。内部提供复用机制,调用两次得到的 <code>ITcpSocketClient</code> 为同一对象</p>
1513

14+
<Pre>[Inject]
15+
[NotNull]
16+
private ITcpSocketFactory? TcpSocketFactory { get; set; }</Pre>
17+
1618
<Pre>var client = factory.GetOrCreate("192.168.1.100", 0);</Pre>
1719

1820
<p class="code-label">3. 使用方法</p>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<Tips>
2+
<p><code>ITcpSocketFactory</code> 服务仅在 <code>Server</code> 模式下可用</p>
3+
</Tips>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
@page "/socket/receive"
2+
@inject IStringLocalizer<Receives> Localizer
3+
4+
<h3>@Localizer["ReceivesTitle"]</h3>
5+
<h4>@Localizer["ReceivesDescription"]</h4>
6+
7+
<Notice></Notice>
8+
9+
<DemoBlock Title="@Localizer["NormalTitle"]"
10+
Introduction="@Localizer["NormalIntro"]"
11+
Name="Normal" ShowCode="false">
12+
<p>本例中连接一个模拟时间同步服务,每间隔 10s 自动发送服务器时间戳,连接后无需发送任何数据即可持续收到时间戳数据</p>
13+
<ul class="ul-demo">
14+
<li>点击 <b>连接</b> 按钮后通过 <code>ITcpSocketFactory</code> 服务实例创建的 <code>ITcpSocketClient</code> 对象连接到网站模拟 <code>TcpServer</code></li>
15+
<li>点击 <b>断开</b> 按钮调用 <code>CloseAsync</code> 方法断开 Socket 连接</li>
16+
</ul>
17+
<p>使用 <code>ReceivedCallBack</code> 委托获得接收到的数据,可通过 <code>+=</code> 方法支持多个客户端接收数据</p>
18+
<Pre>_client.ReceivedCallBack += OnReceivedAsync;</Pre>
19+
<p>特别注意页面需要继承 <code>IDisposable</code> 或者 <code>IAsyncDisposable</code> 接口,在 <code>Dispose</code> 或者 <code>DisposeAsync</code> 中移除委托</p>
20+
<Pre>private void Dispose(bool disposing)
21+
{
22+
if (disposing)
23+
{
24+
if (_client is { IsConnected: true })
25+
{
26+
_client.ReceivedCallBack -= OnReceivedAsync;
27+
}
28+
}
29+
}</Pre>
30+
31+
<div class="row form-inline g-3">
32+
<div class="col-12 col-sm-6">
33+
<Button Text="连接" Icon="fa-solid fa-play"
34+
OnClick="OnConnectAsync" IsDisabled="@_client.IsConnected"></Button>
35+
<Button Text="断开" Icon="fa-solid fa-stop" class="ms-2"
36+
OnClick="OnCloseAsync" IsDisabled="@(!_client.IsConnected)"></Button>
37+
</div>
38+
<div class="col-12">
39+
<Console Items="@_items" Height="496" HeaderText="接收数据(间隔 10 秒)"
40+
ShowAutoScroll="true" OnClear="@OnClear"></Console>
41+
</div>
42+
</div>
43+
</DemoBlock>
44+
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
using System.Net;
7+
8+
namespace BootstrapBlazor.Server.Components.Samples.Sockets;
9+
10+
/// <summary>
11+
///
12+
/// </summary>
13+
public partial class Receives : ComponentBase, IDisposable
14+
{
15+
[Inject, NotNull]
16+
private ITcpSocketFactory? TcpSocketFactory { get; set; }
17+
18+
private ITcpSocketClient _client = null!;
19+
20+
private List<ConsoleMessageItem> _items = [];
21+
22+
/// <summary>
23+
/// <inheritdoc/>
24+
/// </summary>
25+
protected override void OnInitialized()
26+
{
27+
base.OnInitialized();
28+
29+
// 从服务中获取 Socket 实例
30+
_client = TcpSocketFactory.GetOrCreate("bb", key => new IPEndPoint(IPAddress.Loopback, 0));
31+
_client.ReceivedCallBack += OnReceivedAsync;
32+
}
33+
34+
private async Task OnConnectAsync()
35+
{
36+
if (_client is { IsConnected: false })
37+
{
38+
await _client.ConnectAsync("127.0.0.1", 8800, CancellationToken.None);
39+
}
40+
}
41+
42+
private async Task OnCloseAsync()
43+
{
44+
if (_client is { IsConnected: true })
45+
{
46+
await _client.CloseAsync();
47+
}
48+
}
49+
50+
private Task OnClear()
51+
{
52+
_items = [];
53+
return Task.CompletedTask;
54+
}
55+
56+
private async ValueTask OnReceivedAsync(ReadOnlyMemory<byte> data)
57+
{
58+
// 将数据显示为十六进制字符串
59+
var payload = System.Text.Encoding.UTF8.GetString(data.Span);
60+
_items.Add(new ConsoleMessageItem
61+
{
62+
Message = $"接收到来自站点的数据为 {payload}"
63+
});
64+
65+
// 保持队列中最大数量为 50
66+
if (_items.Count > 50)
67+
{
68+
_items.RemoveAt(0);
69+
}
70+
await InvokeAsync(StateHasChanged);
71+
}
72+
73+
private void Dispose(bool disposing)
74+
{
75+
if (disposing)
76+
{
77+
if (_client is { IsConnected: true })
78+
{
79+
_client.ReceivedCallBack -= OnReceivedAsync;
80+
}
81+
}
82+
}
83+
84+
/// <summary>
85+
/// <inheritdoc/>
86+
/// </summary>
87+
public void Dispose()
88+
{
89+
Dispose(true);
90+
GC.SuppressFinalize(this);
91+
}
92+
}

src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ public static List<MenuItem> GenerateMenus(this IStringLocalizer<NavMenu> Locali
9191
};
9292
AddSpeech(item);
9393

94+
item = new DemoMenuItem()
95+
{
96+
Text = Localizer["SocketComponents"],
97+
Icon = "fa-solid fa-square-binary text-danger"
98+
};
99+
AddSocket(item);
100+
94101
item = new DemoMenuItem()
95102
{
96103
Text = Localizer["Services"],
@@ -205,6 +212,19 @@ void AddSpeech(DemoMenuItem item)
205212
AddBadge(item, count: 5);
206213
}
207214

215+
void AddSocket(DemoMenuItem item)
216+
{
217+
item.Items = new List<DemoMenuItem>
218+
{
219+
new()
220+
{
221+
Text = Localizer["SocketReceive"],
222+
Url = "socket/receive"
223+
}
224+
};
225+
AddBadge(item, count: 1);
226+
}
227+
208228
void AddQuickStar(DemoMenuItem item)
209229
{
210230
item.Items = new List<DemoMenuItem>

src/BootstrapBlazor.Server/Extensions/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ void Invoke(BootstrapBlazorOptions option)
4545
services.AddTaskServices();
4646
services.AddHostedService<ClearTempFilesService>();
4747
services.AddHostedService<MockOnlineContributor>();
48+
services.AddHostedService<MockSocketServerService>();
4849

4950
// 增加通用服务
5051
services.AddBootstrapBlazorServices();

src/BootstrapBlazor.Server/Extensions/ServiceCollectionSharedExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ public static IServiceCollection AddBootstrapBlazorServices(this IServiceCollect
9191
// 增加 JuHe 定位服务
9292
services.AddBootstrapBlazorJuHeIpLocatorService();
9393

94+
// 增加 ITcpSocketFactory 服务
95+
services.AddBootstrapBlazorTcpSocketFactory();
96+
9497
// 增加 PetaPoco ORM 数据服务操作类
9598
// 需要时打开下面代码
9699
//services.AddPetaPoco(option =>

src/BootstrapBlazor.Server/Locales/en-US.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4953,7 +4953,9 @@
49534953
"DropUpload": "DropUpload",
49544954
"Vditor": "Vditor Markdown",
49554955
"TcpSocketFactory": "ITcpSocketFactory",
4956-
"OfficeViewer": "Office Viewer"
4956+
"OfficeViewer": "Office Viewer",
4957+
"SocketComponents": "ITcpSocketFactory",
4958+
"SocketReceive": "Receive"
49574959
},
49584960
"BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": {
49594961
"TablesHeaderTitle": "Header grouping function",

src/BootstrapBlazor.Server/Locales/zh-CN.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4953,7 +4953,9 @@
49534953
"DropUpload": "拖动上传组件 DropUpload",
49544954
"Vditor": "富文本框 Vditor Markdown",
49554955
"TcpSocketFactory": "套接字服务 ITcpSocketFactory",
4956-
"OfficeViewer": "Office 文档预览组件"
4956+
"OfficeViewer": "Office 文档预览组件",
4957+
"SocketComponents": "Socket 服务",
4958+
"SocketReceive": "接收数据"
49574959
},
49584960
"BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": {
49594961
"TablesHeaderTitle": "表头分组功能",
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
using System.Net;
7+
using System.Net.Sockets;
8+
9+
namespace Longbow.Tasks.Services;
10+
11+
/// <summary>
12+
/// 模拟 Socket 服务端服务类
13+
/// </summary>
14+
internal class MockSocketServerService(ILogger<MockSocketServerService> logger) : BackgroundService
15+
{
16+
/// <summary>
17+
/// 运行任务
18+
/// </summary>
19+
/// <param name="stoppingToken"></param>
20+
/// <returns></returns>
21+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
22+
{
23+
var server = new TcpListener(IPAddress.Loopback, 8800);
24+
server.Start();
25+
while (stoppingToken is { IsCancellationRequested: false })
26+
{
27+
try
28+
{
29+
var client = await server.AcceptTcpClientAsync(stoppingToken);
30+
_ = Task.Run(() => MockSendAsync(client, stoppingToken), stoppingToken);
31+
}
32+
catch { }
33+
}
34+
}
35+
36+
private async Task MockSendAsync(TcpClient client, CancellationToken stoppingToken)
37+
{
38+
// 方法目的:
39+
// 1. 模拟服务器间隔 10秒 发送当前时间戳数据包到客户端
40+
await using var stream = client.GetStream();
41+
while (stoppingToken is { IsCancellationRequested: false })
42+
{
43+
try
44+
{
45+
var data = System.Text.Encoding.UTF8.GetBytes(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
46+
await stream.WriteAsync(data, stoppingToken);
47+
48+
await Task.Delay(10 * 1000, stoppingToken);
49+
}
50+
catch (OperationCanceledException) { break; }
51+
catch (IOException) { break; }
52+
catch (SocketException) { break; }
53+
catch (Exception ex)
54+
{
55+
logger.LogError(ex, "MockSocketServerService encountered an error while sending data.");
56+
break;
57+
}
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)