Skip to content

Commit 4fdc480

Browse files
authored
feat(ITcpSocketClient): add ConnectAsync extension method (#6273)
* doc: 更新数据处理器相关文档 * refactor: 增加 ConnectAsync 扩展方法 * feat: 增加 ConvertToIPAddress 扩展方法 * feat: 更改 DefaultTcpSocketClient 构造函数 * test: 纠正冗余代码 * test: 更新单元测试
1 parent ea1c142 commit 4fdc480

File tree

8 files changed

+104
-36
lines changed

8 files changed

+104
-36
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ private async Task CreateClient()
6666
}
6767
</Pre>
6868

69-
<p>内置数据库处理器</p>
69+
<p>内置数据处理器</p>
7070

7171
<ul class="ul-demo">
7272
<li><code>FixLengthDataPackageHandler</code> <b>固定长度数据处理器</b> 即每个通讯包都是固定长度</li>

src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
// See the LICENSE file in the project root for more information.
44
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
55

6+
using System.Runtime.Versioning;
67
using System.Text;
78

89
namespace BootstrapBlazor.Components;
910

1011
/// <summary>
1112
/// <see cref="ITcpSocketClient"/> 扩展方法类
1213
/// </summary>
14+
[UnsupportedOSPlatform("browser")]
1315
public static class ITcpSocketClientExtensions
1416
{
1517
/// <summary>
@@ -30,4 +32,20 @@ public static ValueTask<bool> SendAsync(this ITcpSocketClient client, string con
3032
var buffer = encoding?.GetBytes(content) ?? Encoding.UTF8.GetBytes(content);
3133
return client.SendAsync(buffer, token);
3234
}
35+
36+
/// <summary>
37+
/// Establishes an asynchronous connection to the specified host and port.
38+
/// </summary>
39+
/// <param name="client">The TCP socket client to which the content will be sent. Cannot be <see langword="null"/>.</param>
40+
/// <param name="ipString">The hostname or IP address of the server to connect to. Cannot be null or empty.</param>
41+
/// <param name="port">The port number on the server to connect to. Must be a valid port number between 0 and 65535.</param>
42+
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the connection attempt. Defaults to <see
43+
/// langword="default"/> if not provided.</param>
44+
/// <returns>A task that represents the asynchronous operation. The task result is <see langword="true"/> if the connection
45+
/// is successfully established; otherwise, <see langword="false"/>.</returns>
46+
public static ValueTask<bool> ConnectAsync(this ITcpSocketClient client, string ipString, int port, CancellationToken token = default)
47+
{
48+
var endPoint = Utility.ConvertToIpEndPoint(ipString, port);
49+
return client.ConnectAsync(endPoint, token);
50+
}
3351
}

src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
namespace BootstrapBlazor.Components;
1313

1414
[UnsupportedOSPlatform("browser")]
15-
class DefaultTcpSocketClient : ITcpSocketClient
15+
class DefaultTcpSocketClient(IPEndPoint endPoint) : ITcpSocketClient
1616
{
1717
private TcpClient? _client;
1818
private IDataPackageHandler? _dataPackageHandler;
@@ -21,7 +21,7 @@ class DefaultTcpSocketClient : ITcpSocketClient
2121

2222
public bool IsConnected => _client?.Connected ?? false;
2323

24-
public IPEndPoint LocalEndPoint { get; set; }
24+
public IPEndPoint LocalEndPoint { get; set; } = endPoint;
2525

2626
[NotNull]
2727
public ILogger<DefaultTcpSocketClient>? Logger { get; set; }
@@ -30,29 +30,11 @@ class DefaultTcpSocketClient : ITcpSocketClient
3030

3131
public Func<ReadOnlyMemory<byte>, ValueTask>? ReceivedCallBack { get; set; }
3232

33-
public DefaultTcpSocketClient(string host, int port = 0)
34-
{
35-
LocalEndPoint = new IPEndPoint(GetIPAddress(host), port);
36-
}
37-
38-
private static IPAddress GetIPAddress(string host) => host.Equals("localhost", StringComparison.OrdinalIgnoreCase)
39-
? IPAddress.Loopback
40-
: IPAddress.TryParse(host, out var ip) ? ip : IPAddressByHostName;
41-
42-
[ExcludeFromCodeCoverage]
43-
private static IPAddress IPAddressByHostName => Dns.GetHostAddresses(Dns.GetHostName(), AddressFamily.InterNetwork).FirstOrDefault() ?? IPAddress.Loopback;
44-
4533
public void SetDataHandler(IDataPackageHandler handler)
4634
{
4735
_dataPackageHandler = handler;
4836
}
4937

50-
public ValueTask<bool> ConnectAsync(string host, int port, CancellationToken token = default)
51-
{
52-
var endPoint = new IPEndPoint(GetIPAddress(host), port);
53-
return ConnectAsync(endPoint, token);
54-
}
55-
5638
public async ValueTask<bool> ConnectAsync(IPEndPoint endPoint, CancellationToken token = default)
5739
{
5840
var ret = false;

src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketFactory.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.Extensions.DependencyInjection;
77
using Microsoft.Extensions.Logging;
88
using System.Collections.Concurrent;
9+
using System.Net;
910
using System.Runtime.Versioning;
1011

1112
namespace BootstrapBlazor.Components;
@@ -19,7 +20,8 @@ public ITcpSocketClient GetOrCreate(string host, int port = 0)
1920
{
2021
return _pool.GetOrAdd($"{host}:{port}", key =>
2122
{
22-
var client = new DefaultTcpSocketClient(host, port)
23+
var endPoint = Utility.ConvertToIpEndPoint(host, port);
24+
var client = new DefaultTcpSocketClient(endPoint)
2325
{
2426
Logger = provider.GetService<ILogger<DefaultTcpSocketClient>>()
2527
};

src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,6 @@ public interface ITcpSocketClient : IDisposable
4444
/// <param name="handler">The handler responsible for processing data packages. Cannot be null.</param>
4545
void SetDataHandler(IDataPackageHandler handler);
4646

47-
/// <summary>
48-
/// Establishes an asynchronous connection to the specified host and port.
49-
/// </summary>
50-
/// <param name="host">The hostname or IP address of the server to connect to. Cannot be null or empty.</param>
51-
/// <param name="port">The port number on the server to connect to. Must be a valid port number between 0 and 65535.</param>
52-
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the connection attempt. Defaults to <see
53-
/// langword="default"/> if not provided.</param>
54-
/// <returns>A task that represents the asynchronous operation. The task result is <see langword="true"/> if the connection
55-
/// is successfully established; otherwise, <see langword="false"/>.</returns>
56-
ValueTask<bool> ConnectAsync(string host, int port, CancellationToken token = default);
57-
5847
/// <summary>
5948
/// Establishes an asynchronous connection to the specified endpoint.
6049
/// </summary>

src/BootstrapBlazor/Utils/Utility.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
using System.ComponentModel;
99
using System.Data;
1010
using System.Linq.Expressions;
11+
using System.Net;
12+
using System.Net.Sockets;
1113
using System.Reflection;
14+
using System.Runtime.Versioning;
1215

1316
namespace BootstrapBlazor.Components;
1417

@@ -898,4 +901,63 @@ static Expression<Func<ComponentBase, object, string, object>> CreateLambda(Type
898901
/// <param name="type"></param>
899902
/// <returns></returns>
900903
public static IStringLocalizer? CreateLocalizer(Type type) => CacheManager.CreateLocalizerByType(type);
904+
905+
/// <summary>
906+
/// Converts a string representation of an IP address or hostname into an <see cref="IPAddress"/> object.
907+
/// </summary>
908+
/// <remarks>This method handles common special cases for IP address strings, such as "localhost" and
909+
/// "any". For other inputs, it attempts to parse the string as an IP address using <see
910+
/// cref="IPAddress.TryParse(string, out IPAddress)"/>. If parsing fails, the method resolves the input as a
911+
/// hostname.</remarks>
912+
/// <param name="ipString">A string containing the IP address or hostname to convert. Special values include: <list type="bullet">
913+
/// <item><description><c>"localhost"</c> returns the loopback address (<see
914+
/// cref="IPAddress.Loopback"/>).</description></item> <item><description><c>"any"</c> returns the wildcard address
915+
/// (<see cref="IPAddress.Any"/>).</description></item> </list> For other values, the method attempts to parse the
916+
/// string as an IP address or resolve it as a hostname.</param>
917+
/// <returns>An <see cref="IPAddress"/> object representing the parsed or resolved IP address. If the input cannot be parsed
918+
/// or resolved, the method returns a default IP address.</returns>
919+
[UnsupportedOSPlatform("browser")]
920+
public static IPAddress ConvertToIPAddress(string ipString)
921+
{
922+
if (string.IsNullOrEmpty(ipString))
923+
{
924+
throw new ArgumentNullException(nameof(ipString), "IP address cannot be null or empty.");
925+
}
926+
927+
if (ipString.Equals("localhost", StringComparison.OrdinalIgnoreCase))
928+
{
929+
return IPAddress.Loopback;
930+
}
931+
if (ipString.Equals("any", StringComparison.OrdinalIgnoreCase))
932+
{
933+
return IPAddress.Any;
934+
}
935+
936+
return IPAddress.TryParse(ipString, out var ip) ? ip : IPAddressByHostName;
937+
}
938+
939+
[ExcludeFromCodeCoverage]
940+
941+
[UnsupportedOSPlatform("browser")]
942+
private static IPAddress IPAddressByHostName => Dns.GetHostAddresses(Dns.GetHostName(), AddressFamily.InterNetwork).FirstOrDefault() ?? IPAddress.Loopback;
943+
944+
/// <summary>
945+
/// Converts a string representation of an IP address and a port number into an <see cref="IPEndPoint"/> instance.
946+
/// </summary>
947+
/// <remarks>This method is not supported on browser platforms.</remarks>
948+
/// <param name="ipString">The string representation of the IP address. Must be a valid IPv4 or IPv6 address.</param>
949+
/// <param name="port">The port number associated with the endpoint. Must be between 0 and 65535.</param>
950+
/// <returns>An <see cref="IPEndPoint"/> representing the specified IP address and port.</returns>
951+
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="port"/> is less than 0 or greater than 65535.</exception>
952+
[UnsupportedOSPlatform("browser")]
953+
public static IPEndPoint ConvertToIpEndPoint(string ipString, int port)
954+
{
955+
if (port < 0 || port > 65535)
956+
{
957+
throw new ArgumentOutOfRangeException(nameof(port), "Port must be between 0 and 65535.");
958+
}
959+
960+
var address = ConvertToIPAddress(ipString);
961+
return new IPEndPoint(address, port);
962+
}
901963
}

test/UnitTest/Services/TcpSocketFactoryTest.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,6 @@ public async Task FixLengthDataPackageHandler_Sticky()
274274
tcs = new TaskCompletionSource();
275275
await tcs.Task;
276276

277-
// 等待第二次数据
278-
await tcs.Task;
279-
280277
// 验证第三次收到的数据
281278
Assert.Equal(receivedBuffer.ToArray(), [3, 2, 3, 4, 5, 6, 7]);
282279

test/UnitTest/Utils/UtilityTest.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.ComponentModel.DataAnnotations;
1111
using System.Data;
1212
using System.Globalization;
13+
using System.Net;
1314
using System.Reflection;
1415
using UnitTest.Components;
1516

@@ -742,6 +743,23 @@ public void FormatIp_Test()
742743
Assert.Equal("192.168.1.###", result);
743744
}
744745

746+
[Fact]
747+
public void ConvertToIPAddress_Ok()
748+
{
749+
var ex = Assert.Throws<ArgumentNullException>(() => Utility.ConvertToIPAddress(""));
750+
Assert.NotNull(ex);
751+
752+
var address = Utility.ConvertToIPAddress("any");
753+
Assert.Equal(IPAddress.Any, address);
754+
}
755+
756+
[Fact]
757+
public void ConvertToIpEndPoint_Ok()
758+
{
759+
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => Utility.ConvertToIpEndPoint("localhost", 88990));
760+
Assert.NotNull(ex);
761+
}
762+
745763
[AutoGenerateClass(Align = Alignment.Center)]
746764
private class Dog
747765
{

0 commit comments

Comments
 (0)