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 @@ -66,7 +66,7 @@ private async Task CreateClient()
}
</Pre>

<p>内置数据库处理器</p>
<p>内置数据处理器</p>

<ul class="ul-demo">
<li><code>FixLengthDataPackageHandler</code> <b>固定长度数据处理器</b> 即每个通讯包都是固定长度</li>
Expand Down
18 changes: 18 additions & 0 deletions src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

using System.Runtime.Versioning;
using System.Text;

namespace BootstrapBlazor.Components;

/// <summary>
/// <see cref="ITcpSocketClient"/> 扩展方法类
/// </summary>
[UnsupportedOSPlatform("browser")]
public static class ITcpSocketClientExtensions
{
/// <summary>
Expand All @@ -30,4 +32,20 @@ public static ValueTask<bool> SendAsync(this ITcpSocketClient client, string con
var buffer = encoding?.GetBytes(content) ?? Encoding.UTF8.GetBytes(content);
return client.SendAsync(buffer, token);
}

/// <summary>
/// Establishes an asynchronous connection to the specified host and port.
/// </summary>
/// <param name="client">The TCP socket client to which the content will be sent. Cannot be <see langword="null"/>.</param>
/// <param name="ipString">The hostname or IP address of the server to connect to. Cannot be null or empty.</param>
/// <param name="port">The port number on the server to connect to. Must be a valid port number between 0 and 65535.</param>
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the connection attempt. Defaults to <see
/// langword="default"/> if not provided.</param>
/// <returns>A task that represents the asynchronous operation. The task result is <see langword="true"/> if the connection
/// is successfully established; otherwise, <see langword="false"/>.</returns>
public static ValueTask<bool> ConnectAsync(this ITcpSocketClient client, string ipString, int port, CancellationToken token = default)
{
var endPoint = Utility.ConvertToIpEndPoint(ipString, port);
return client.ConnectAsync(endPoint, token);
}
}
22 changes: 2 additions & 20 deletions src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
namespace BootstrapBlazor.Components;

[UnsupportedOSPlatform("browser")]
class DefaultTcpSocketClient : ITcpSocketClient
class DefaultTcpSocketClient(IPEndPoint endPoint) : ITcpSocketClient
{
private TcpClient? _client;
private IDataPackageHandler? _dataPackageHandler;
Expand All @@ -21,7 +21,7 @@ class DefaultTcpSocketClient : ITcpSocketClient

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

public IPEndPoint LocalEndPoint { get; set; }
public IPEndPoint LocalEndPoint { get; set; } = endPoint;

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

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

public DefaultTcpSocketClient(string host, int port = 0)
{
LocalEndPoint = new IPEndPoint(GetIPAddress(host), port);
}

private static IPAddress GetIPAddress(string host) => host.Equals("localhost", StringComparison.OrdinalIgnoreCase)
? IPAddress.Loopback
: IPAddress.TryParse(host, out var ip) ? ip : IPAddressByHostName;

[ExcludeFromCodeCoverage]
private static IPAddress IPAddressByHostName => Dns.GetHostAddresses(Dns.GetHostName(), AddressFamily.InterNetwork).FirstOrDefault() ?? IPAddress.Loopback;

public void SetDataHandler(IDataPackageHandler handler)
{
_dataPackageHandler = handler;
}

public ValueTask<bool> ConnectAsync(string host, int port, CancellationToken token = default)
{
var endPoint = new IPEndPoint(GetIPAddress(host), port);
return ConnectAsync(endPoint, token);
}

public async ValueTask<bool> ConnectAsync(IPEndPoint endPoint, CancellationToken token = default)
{
var ret = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Net;
using System.Runtime.Versioning;

namespace BootstrapBlazor.Components;
Expand All @@ -19,7 +20,8 @@ public ITcpSocketClient GetOrCreate(string host, int port = 0)
{
return _pool.GetOrAdd($"{host}:{port}", key =>
{
var client = new DefaultTcpSocketClient(host, port)
var endPoint = Utility.ConvertToIpEndPoint(host, port);
var client = new DefaultTcpSocketClient(endPoint)
{
Logger = provider.GetService<ILogger<DefaultTcpSocketClient>>()
};
Expand Down
11 changes: 0 additions & 11 deletions src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,6 @@ public interface ITcpSocketClient : IDisposable
/// <param name="handler">The handler responsible for processing data packages. Cannot be null.</param>
void SetDataHandler(IDataPackageHandler handler);

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

/// <summary>
/// Establishes an asynchronous connection to the specified endpoint.
/// </summary>
Expand Down
62 changes: 62 additions & 0 deletions src/BootstrapBlazor/Utils/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
using System.ComponentModel;
using System.Data;
using System.Linq.Expressions;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.Versioning;

namespace BootstrapBlazor.Components;

Expand Down Expand Up @@ -898,4 +901,63 @@ static Expression<Func<ComponentBase, object, string, object>> CreateLambda(Type
/// <param name="type"></param>
/// <returns></returns>
public static IStringLocalizer? CreateLocalizer(Type type) => CacheManager.CreateLocalizerByType(type);

/// <summary>
/// Converts a string representation of an IP address or hostname into an <see cref="IPAddress"/> object.
/// </summary>
/// <remarks>This method handles common special cases for IP address strings, such as "localhost" and
/// "any". For other inputs, it attempts to parse the string as an IP address using <see
/// cref="IPAddress.TryParse(string, out IPAddress)"/>. If parsing fails, the method resolves the input as a
/// hostname.</remarks>
/// <param name="ipString">A string containing the IP address or hostname to convert. Special values include: <list type="bullet">
/// <item><description><c>"localhost"</c> returns the loopback address (<see
/// cref="IPAddress.Loopback"/>).</description></item> <item><description><c>"any"</c> returns the wildcard address
/// (<see cref="IPAddress.Any"/>).</description></item> </list> For other values, the method attempts to parse the
/// string as an IP address or resolve it as a hostname.</param>
/// <returns>An <see cref="IPAddress"/> object representing the parsed or resolved IP address. If the input cannot be parsed
/// or resolved, the method returns a default IP address.</returns>
[UnsupportedOSPlatform("browser")]
public static IPAddress ConvertToIPAddress(string ipString)
{
if (string.IsNullOrEmpty(ipString))
{
throw new ArgumentNullException(nameof(ipString), "IP address cannot be null or empty.");
}

if (ipString.Equals("localhost", StringComparison.OrdinalIgnoreCase))
{
return IPAddress.Loopback;
}
if (ipString.Equals("any", StringComparison.OrdinalIgnoreCase))
{
return IPAddress.Any;
}

return IPAddress.TryParse(ipString, out var ip) ? ip : IPAddressByHostName;
}

[ExcludeFromCodeCoverage]

[UnsupportedOSPlatform("browser")]
private static IPAddress IPAddressByHostName => Dns.GetHostAddresses(Dns.GetHostName(), AddressFamily.InterNetwork).FirstOrDefault() ?? IPAddress.Loopback;

/// <summary>
/// Converts a string representation of an IP address and a port number into an <see cref="IPEndPoint"/> instance.
/// </summary>
/// <remarks>This method is not supported on browser platforms.</remarks>
/// <param name="ipString">The string representation of the IP address. Must be a valid IPv4 or IPv6 address.</param>
/// <param name="port">The port number associated with the endpoint. Must be between 0 and 65535.</param>
/// <returns>An <see cref="IPEndPoint"/> representing the specified IP address and port.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="port"/> is less than 0 or greater than 65535.</exception>
[UnsupportedOSPlatform("browser")]
public static IPEndPoint ConvertToIpEndPoint(string ipString, int port)
{
if (port < 0 || port > 65535)
{
throw new ArgumentOutOfRangeException(nameof(port), "Port must be between 0 and 65535.");
}

var address = ConvertToIPAddress(ipString);
return new IPEndPoint(address, port);
}
}
3 changes: 0 additions & 3 deletions test/UnitTest/Services/TcpSocketFactoryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,6 @@ public async Task FixLengthDataPackageHandler_Sticky()
tcs = new TaskCompletionSource();
await tcs.Task;

// 等待第二次数据
await tcs.Task;

// 验证第三次收到的数据
Assert.Equal(receivedBuffer.ToArray(), [3, 2, 3, 4, 5, 6, 7]);

Expand Down
18 changes: 18 additions & 0 deletions test/UnitTest/Utils/UtilityTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Globalization;
using System.Net;
using System.Reflection;
using UnitTest.Components;

Expand Down Expand Up @@ -742,6 +743,23 @@ public void FormatIp_Test()
Assert.Equal("192.168.1.###", result);
}

[Fact]
public void ConvertToIPAddress_Ok()
{
var ex = Assert.Throws<ArgumentNullException>(() => Utility.ConvertToIPAddress(""));
Assert.NotNull(ex);

var address = Utility.ConvertToIPAddress("any");
Assert.Equal(IPAddress.Any, address);
}

[Fact]
public void ConvertToIpEndPoint_Ok()
{
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => Utility.ConvertToIpEndPoint("localhost", 88990));
Assert.NotNull(ex);
}

[AutoGenerateClass(Align = Alignment.Center)]
private class Dog
{
Expand Down