diff --git a/src/BootstrapBlazor/Services/TcpSocket/DefaultSocketClient.cs b/src/BootstrapBlazor/Services/TcpSocket/DefaultSocketClientProvider.cs
similarity index 94%
rename from src/BootstrapBlazor/Services/TcpSocket/DefaultSocketClient.cs
rename to src/BootstrapBlazor/Services/TcpSocket/DefaultSocketClientProvider.cs
index 9b83c9197f0..df9da26960e 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/DefaultSocketClient.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/DefaultSocketClientProvider.cs
@@ -13,7 +13,7 @@ namespace BootstrapBlazor.Components;
/// TcpSocket 客户端默认实现
///
[UnsupportedOSPlatform("browser")]
-class DefaultSocketClient(IPEndPoint localEndPoint) : ISocketClient
+class DefaultSocketClientProvider : ISocketClientProvider
{
private TcpClient? _client;
@@ -25,7 +25,7 @@ class DefaultSocketClient(IPEndPoint localEndPoint) : ISocketClient
///
///
///
- public IPEndPoint LocalEndPoint { get; set; } = localEndPoint;
+ public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
///
///
diff --git a/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs b/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs
index 6ac0cc02d90..f8c0aa1c86e 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs
@@ -3,32 +3,12 @@
// 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.Runtime.Versioning;
namespace BootstrapBlazor.Components;
[UnsupportedOSPlatform("browser")]
-sealed class DefaultTcpSocketClient : TcpSocketClientBase
+sealed class DefaultTcpSocketClient(SocketClientOptions options) : TcpSocketClientBase(options)
{
- public DefaultTcpSocketClient(SocketClientOptions options)
- {
- ReceiveBufferSize = Math.Max(1024, options.ReceiveBufferSize);
- IsAutoReceive = options.IsAutoReceive;
- ConnectTimeout = options.ConnectTimeout;
- SendTimeout = options.SendTimeout;
- ReceiveTimeout = options.ReceiveTimeout;
- LocalEndPoint = options.LocalEndPoint;
- }
- ///
- ///
- ///
- ///
- ///
- ///
- protected override DefaultSocketClient CreateSocketClient(IPEndPoint localEndPoint)
- {
- return new DefaultSocketClient(localEndPoint);
- }
}
diff --git a/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketFactory.cs b/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketFactory.cs
index 49037b345f8..55f8815c58c 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketFactory.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketFactory.cs
@@ -3,8 +3,6 @@
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Runtime.Versioning;
@@ -23,7 +21,7 @@ public ITcpSocketClient GetOrCreate(string name, Action val
valueFactory(options);
var client = new DefaultTcpSocketClient(options)
{
- Logger = provider.GetService>()
+ ServiceProvider = provider,
};
return client;
});
diff --git a/src/BootstrapBlazor/Services/TcpSocket/Extensions/TcpSocketExtensions.cs b/src/BootstrapBlazor/Services/TcpSocket/Extensions/TcpSocketExtensions.cs
index c88529509fa..dde3bc7aa8f 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/Extensions/TcpSocketExtensions.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/Extensions/TcpSocketExtensions.cs
@@ -22,9 +22,11 @@ public static class TcpSocketExtensions
///
public static IServiceCollection AddBootstrapBlazorTcpSocketFactory(this IServiceCollection services)
{
- // 添加 ITcpSocket 实现
- services.TryAddSingleton();
+ // 添加 ITcpSocketFactory 服务
+ services.AddSingleton();
+ // 增加 ISocketClientProvider 服务
+ services.TryAddTransient();
return services;
}
}
diff --git a/src/BootstrapBlazor/Services/TcpSocket/ISocketClient.cs b/src/BootstrapBlazor/Services/TcpSocket/ISocketClientProvider.cs
similarity index 92%
rename from src/BootstrapBlazor/Services/TcpSocket/ISocketClient.cs
rename to src/BootstrapBlazor/Services/TcpSocket/ISocketClientProvider.cs
index 27962b3d6c4..6c3bd9e862b 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/ISocketClient.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/ISocketClientProvider.cs
@@ -15,7 +15,7 @@ namespace BootstrapBlazor.Components;
/// establishing connections, transmitting data, and receiving data asynchronously. Implementations of this interface
/// should ensure proper resource management, including closing connections and releasing resources when no longer
/// needed.
-public interface ISocketClient
+public interface ISocketClientProvider
{
///
/// Gets a value indicating whether the connection is currently active.
@@ -25,10 +25,7 @@ public interface ISocketClient
///
/// Gets the local network endpoint that the socket is bound to.
///
- /// This property provides information about the local endpoint of the socket, which is typically
- /// used to identify the local address and port being used for communication. If the socket is not bound to a
- /// specific local endpoint, this property may return .
- IPEndPoint LocalEndPoint { get; }
+ IPEndPoint LocalEndPoint { get; set; }
///
/// Establishes an asynchronous connection to the specified endpoint.
diff --git a/src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs b/src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs
index e199d1744f0..1d38e26dff3 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/ITcpSocketClient.cs
@@ -12,40 +12,15 @@ namespace BootstrapBlazor.Components;
///
public interface ITcpSocketClient : IAsyncDisposable
{
- ///
- /// Gets or sets the size, in bytes, of the receive buffer used for network operations.
- ///
- int ReceiveBufferSize { get; set; }
-
///
/// Gets a value indicating whether the system is currently connected. Default is false.
///
bool IsConnected { get; }
///
- /// Gets or sets a value indicating whether automatic receiving data is enabled. Default is true.
- ///
- bool IsAutoReceive { get; set; }
-
- ///
- /// Gets or sets the timeout duration, in milliseconds, for establishing a connection.
- ///
- int ConnectTimeout { get; set; }
-
- ///
- /// Gets or sets the duration, in milliseconds, to wait for a send operation to complete before timing out.
- ///
- /// If the send operation does not complete within the specified timeout period, an exception may
- /// be thrown.
- int SendTimeout { get; set; }
-
- ///
- /// Gets or sets the amount of time, in milliseconds, that the receiver will wait for a response before timing out.
+ /// Gets or sets the configuration options for the socket client.
///
- /// Use this property to configure the maximum wait time for receiving a response. Setting an
- /// appropriate timeout can help prevent indefinite blocking in scenarios where responses may be delayed or
- /// unavailable.
- int ReceiveTimeout { get; set; }
+ SocketClientOptions Options { get; }
///
/// Gets the local network endpoint that the socket is bound to.
diff --git a/src/BootstrapBlazor/Services/TcpSocket/SocketClientOptions.cs b/src/BootstrapBlazor/Services/TcpSocket/SocketClientOptions.cs
index 95d7fecf6f6..72904adfd72 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/SocketClientOptions.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/SocketClientOptions.cs
@@ -46,8 +46,8 @@ public class SocketClientOptions
public int ReceiveTimeout { get; set; }
///
- /// Gets or sets the local endpoint for the socket client. Default value is
+ /// Gets or sets the local endpoint for the socket client. Default value is
///
/// This property specifies the local network endpoint that the socket client will bind to when establishing a connection.
- public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Loopback, 0);
+ public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
}
diff --git a/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs b/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs
index 9bd992bfc02..05aff55447b 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Buffers;
using System.Net;
@@ -10,63 +11,55 @@
namespace BootstrapBlazor.Components;
///
-/// Provides a base implementation for a TCP socket client, enabling connection, data transmission, and reception over
-/// TCP.
+/// Provides a base implementation for TCP socket clients, enabling connection management, data transmission, and
+/// reception over TCP sockets. This class is designed to be extended by specific implementations of TCP socket
+/// clients.
///
-/// This abstract class serves as a foundation for implementing TCP socket clients. It provides methods
-/// for connecting to a remote endpoint, sending and receiving data, and managing connection state. Derived classes can
-/// extend or customize the behavior as needed.
-public abstract class TcpSocketClientBase : ITcpSocketClient where TSocketClient : class, ISocketClient
+/// The class offers core functionality for managing TCP socket
+/// connections, including connecting to remote endpoints, sending and receiving data, and handling data packages.
+/// Derived classes can extend or override its behavior to implement specific client logic. Key features include: -
+/// Connection management with support for timeouts and cancellation tokens. - Data transmission and reception with
+/// optional data package handling. - Logging capabilities for tracking events and errors. - Dependency injection
+/// support via . This class is abstract and cannot be instantiated directly. Use a
+/// derived class to implement specific functionality.
+///
+public abstract class TcpSocketClientBase(SocketClientOptions options) : ITcpSocketClient
{
///
- /// Gets or sets the underlying socket client used for network communication.
+ /// Gets or sets the socket client provider used for managing socket connections.
///
- protected TSocketClient? Client { get; set; }
+ protected ISocketClientProvider? SocketClientProvider { get; set; }
- ///
- ///
- ///
- public ILogger? Logger { get; set; }
-
- ///
- ///
- ///
- public bool IsConnected => Client?.IsConnected ?? false;
-
- ///
- ///
- ///
- public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Loopback, 0);
///
- ///
+ /// Gets or sets the logger instance used for logging messages and events.
///
- public int ReceiveBufferSize { get; set; } = 1024 * 64;
+ protected ILogger? Logger { get; set; }
///
- ///
+ /// Gets or sets the service provider used to resolve dependencies.
///
- public bool IsAutoReceive { get; set; } = true;
+ public IServiceProvider? ServiceProvider { get; set; }
///
///
///
- public Func, ValueTask>? ReceivedCallBack { get; set; }
+ public SocketClientOptions Options => options;
///
///
///
- public int ConnectTimeout { get; set; }
+ public bool IsConnected => SocketClientProvider?.IsConnected ?? false;
///
///
///
- public int SendTimeout { get; set; }
+ public IPEndPoint LocalEndPoint => SocketClientProvider?.LocalEndPoint ?? new IPEndPoint(IPAddress.Any, 0);
///
///
///
- public int ReceiveTimeout { get; set; }
+ public Func, ValueTask>? ReceivedCallBack { get; set; }
///
/// Gets or sets the handler responsible for processing data packages.
@@ -77,13 +70,6 @@ public abstract class TcpSocketClientBase : ITcpSocketClient wher
private IPEndPoint? _localEndPoint;
private CancellationTokenSource? _receiveCancellationTokenSource;
- ///
- /// Creates and initializes a new instance of the socket client for the specified endpoint.
- ///
- /// The network endpoint to which the socket client will connect. Cannot be null.
- /// An instance of configured for the specified endpoint.
- protected abstract TSocketClient CreateSocketClient(IPEndPoint localEndPoint);
-
///
///
///
@@ -101,36 +87,39 @@ public virtual void SetDataHandler(IDataPackageHandler handler)
public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default)
{
var ret = false;
+ SocketClientProvider = ServiceProvider?.GetRequiredService()
+ ?? throw new InvalidOperationException("SocketClientProvider is not registered in the service provider.");
+
try
{
// 释放资源
await CloseAsync();
// 创建新的 TcpClient 实例
- Client ??= CreateSocketClient(LocalEndPoint);
- _localEndPoint = LocalEndPoint;
+ SocketClientProvider.LocalEndPoint = Options.LocalEndPoint;
+
+ _localEndPoint = Options.LocalEndPoint;
_remoteEndPoint = null;
var connectionToken = token;
- if (ConnectTimeout > 0)
+ if (Options.ConnectTimeout > 0)
{
// 设置连接超时时间
- var connectTokenSource = new CancellationTokenSource(ConnectTimeout);
+ var connectTokenSource = new CancellationTokenSource(options.ConnectTimeout);
connectionToken = CancellationTokenSource.CreateLinkedTokenSource(token, connectTokenSource.Token).Token;
}
- await Client.ConnectAsync(endPoint, connectionToken);
+ ret = await SocketClientProvider.ConnectAsync(endPoint, connectionToken);
- if (Client.IsConnected)
+ if (ret)
{
- _localEndPoint = Client.LocalEndPoint;
+ _localEndPoint = SocketClientProvider.LocalEndPoint;
_remoteEndPoint = endPoint;
- if (IsAutoReceive)
+ if (options.IsAutoReceive)
{
_ = Task.Run(AutoReceiveAsync, token);
}
}
- ret = Client.IsConnected;
}
catch (OperationCanceledException ex)
{
@@ -154,7 +143,7 @@ public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken
///
public virtual async ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default)
{
- if (Client is not { IsConnected: true })
+ if (SocketClientProvider is not { IsConnected: true })
{
throw new InvalidOperationException($"TCP Socket is not connected {LocalEndPoint}");
}
@@ -163,10 +152,10 @@ public virtual async ValueTask SendAsync(ReadOnlyMemory data, Cancel
try
{
var sendToken = token;
- if (SendTimeout > 0)
+ if (options.SendTimeout > 0)
{
// 设置发送超时时间
- var sendTokenSource = new CancellationTokenSource(SendTimeout);
+ var sendTokenSource = new CancellationTokenSource(options.SendTimeout);
sendToken = CancellationTokenSource.CreateLinkedTokenSource(token, sendTokenSource.Token).Token;
}
@@ -175,7 +164,7 @@ public virtual async ValueTask SendAsync(ReadOnlyMemory data, Cancel
data = await DataPackageHandler.SendAsync(data, sendToken);
}
- ret = await Client.SendAsync(data, sendToken);
+ ret = await SocketClientProvider.SendAsync(data, sendToken);
}
catch (OperationCanceledException ex)
{
@@ -197,19 +186,19 @@ public virtual async ValueTask SendAsync(ReadOnlyMemory data, Cancel
///
public virtual async ValueTask> ReceiveAsync(CancellationToken token = default)
{
- if (Client is not { IsConnected: true })
+ if (SocketClientProvider is not { IsConnected: true })
{
throw new InvalidOperationException($"TCP Socket is not connected {LocalEndPoint}");
}
- if (IsAutoReceive)
+ if (options.IsAutoReceive)
{
throw new InvalidOperationException("Cannot call ReceiveAsync when IsAutoReceive is true. Use the auto-receive mechanism instead.");
}
- using var block = MemoryPool.Shared.Rent(ReceiveBufferSize);
+ using var block = MemoryPool.Shared.Rent(options.ReceiveBufferSize);
var buffer = block.Memory;
- var len = await ReceiveCoreAsync(Client, buffer, token);
+ var len = await ReceiveCoreAsync(SocketClientProvider, buffer, token);
return buffer[..len];
}
@@ -218,14 +207,14 @@ private async ValueTask AutoReceiveAsync()
_receiveCancellationTokenSource ??= new();
while (_receiveCancellationTokenSource is { IsCancellationRequested: false })
{
- if (Client is not { IsConnected: true })
+ if (SocketClientProvider is not { IsConnected: true })
{
throw new InvalidOperationException($"TCP Socket is not connected {LocalEndPoint}");
}
- using var block = MemoryPool.Shared.Rent(ReceiveBufferSize);
+ using var block = MemoryPool.Shared.Rent(options.ReceiveBufferSize);
var buffer = block.Memory;
- var len = await ReceiveCoreAsync(Client, buffer, _receiveCancellationTokenSource.Token);
+ var len = await ReceiveCoreAsync(SocketClientProvider, buffer, _receiveCancellationTokenSource.Token);
if (len == 0)
{
break;
@@ -233,16 +222,16 @@ private async ValueTask AutoReceiveAsync()
}
}
- private async ValueTask ReceiveCoreAsync(ISocketClient client, Memory buffer, CancellationToken token)
+ private async ValueTask ReceiveCoreAsync(ISocketClientProvider client, Memory buffer, CancellationToken token)
{
var len = 0;
try
{
var receiveToken = token;
- if (ReceiveTimeout > 0)
+ if (options.ReceiveTimeout > 0)
{
// 设置接收超时时间
- var receiveTokenSource = new CancellationTokenSource(ReceiveTimeout);
+ var receiveTokenSource = new CancellationTokenSource(options.ReceiveTimeout);
receiveToken = CancellationTokenSource.CreateLinkedTokenSource(receiveToken, receiveTokenSource.Token).Token;
}
@@ -286,6 +275,7 @@ private async ValueTask ReceiveCoreAsync(ISocketClient client, Memory
///
protected void Log(LogLevel logLevel, Exception? ex, string? message)
{
+ Logger ??= ServiceProvider?.GetRequiredService>();
Logger?.Log(logLevel, ex, "{Message}", message);
}
@@ -317,9 +307,9 @@ protected virtual async ValueTask DisposeAsync(bool disposing)
_receiveCancellationTokenSource = null;
}
- if (Client != null)
+ if (SocketClientProvider != null)
{
- await Client.CloseAsync();
+ await SocketClientProvider.CloseAsync();
}
}
}
diff --git a/src/BootstrapBlazor/Utils/Utility.cs b/src/BootstrapBlazor/Utils/Utility.cs
index 2c9ff091d20..c2be34a3c4d 100644
--- a/src/BootstrapBlazor/Utils/Utility.cs
+++ b/src/BootstrapBlazor/Utils/Utility.cs
@@ -939,7 +939,7 @@ public static IPAddress ConvertToIPAddress(string ipString)
[ExcludeFromCodeCoverage]
[UnsupportedOSPlatform("browser")]
- private static IPAddress IPAddressByHostName => Dns.GetHostAddresses(Dns.GetHostName(), AddressFamily.InterNetwork).FirstOrDefault() ?? IPAddress.Loopback;
+ private static IPAddress IPAddressByHostName => Dns.GetHostAddresses(Dns.GetHostName(), AddressFamily.InterNetwork).FirstOrDefault() ?? IPAddress.Any;
///
/// Converts a string representation of an IP address and a port number into an instance.
diff --git a/test/UnitTest/Services/DefaultSocketClientProviderTest.cs b/test/UnitTest/Services/DefaultSocketClientProviderTest.cs
new file mode 100644
index 00000000000..8e77c51a936
--- /dev/null
+++ b/test/UnitTest/Services/DefaultSocketClientProviderTest.cs
@@ -0,0 +1,48 @@
+// 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;
+
+namespace UnitTest.Services;
+
+public class DefaultSocketClientProviderTest
+{
+ [Fact]
+ public async Task DefaultSocketClient_Ok()
+ {
+ var sc = new ServiceCollection();
+ sc.AddBootstrapBlazorTcpSocketFactory();
+ var provider = sc.BuildServiceProvider();
+ var clientProvider = provider.GetRequiredService();
+
+ // 未建立连接时 IsConnected 应为 false
+ Assert.False(clientProvider.IsConnected);
+
+ // 未建立连接直接调用 ReceiveAsync 方法
+ var buffer = new byte[1024];
+ var len = await clientProvider.ReceiveAsync(buffer);
+ Assert.Equal(0, len);
+ }
+
+ [Fact]
+ public void SocketClientOptions_Ok()
+ {
+ var options = new SocketClientOptions
+ {
+ ReceiveBufferSize = 1024 * 64,
+ IsAutoReceive = true,
+ ConnectTimeout = 1000,
+ SendTimeout = 500,
+ ReceiveTimeout = 500,
+ LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0)
+ };
+ Assert.Equal(1024 * 64, options.ReceiveBufferSize);
+ Assert.True(options.IsAutoReceive);
+ Assert.Equal(1000, options.ConnectTimeout);
+ Assert.Equal(500, options.SendTimeout);
+ Assert.Equal(500, options.ReceiveTimeout);
+ Assert.Equal(new IPEndPoint(IPAddress.Loopback, 0), options.LocalEndPoint);
+ }
+}
diff --git a/test/UnitTest/Services/DefaultSocketClientTest.cs b/test/UnitTest/Services/DefaultSocketClientTest.cs
deleted file mode 100644
index 4fcdf0b5c96..00000000000
--- a/test/UnitTest/Services/DefaultSocketClientTest.cs
+++ /dev/null
@@ -1,231 +0,0 @@
-// 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 Microsoft.Extensions.Logging;
-using System.Net;
-using System.Net.Sockets;
-using System.Reflection;
-
-namespace UnitTest.Services;
-
-public class DefaultSocketClientTest
-{
- [Fact]
- public void Logger_Null()
- {
- // 测试 Logger 为 null 的情况
- var client = CreateClient();
- var baseType = client.GetType().BaseType;
- Assert.NotNull(baseType);
-
- // 获取 Logger 字段设置为 null 测试 Log 不会抛出异常
- var propertyInfo = baseType.GetProperty("Logger", BindingFlags.Public | BindingFlags.Instance);
- Assert.NotNull(propertyInfo);
-
- propertyInfo.SetValue(client, null);
-
- var methodInfo = baseType.GetMethod("Log", BindingFlags.NonPublic | BindingFlags.Instance);
- Assert.NotNull(methodInfo);
- methodInfo.Invoke(client, [LogLevel.Information, null!, "Test log message"]);
- }
-
- [Fact]
- public async Task DefaultSocketClient_Ok()
- {
- var port = 8894;
- var server = StartTcpServer(port, MockDelimiterPackageAsync);
- var client = CreateClient();
-
- // 获得 Client 泛型属性
- var baseType = client.GetType().BaseType;
- Assert.NotNull(baseType);
-
- // 建立连接
- var connect = await client.ConnectAsync("localhost", port);
- Assert.True(connect);
-
- var propertyInfo = baseType.GetProperty("Client", BindingFlags.NonPublic | BindingFlags.Instance);
- Assert.NotNull(propertyInfo);
- var instance = propertyInfo.GetValue(client);
- Assert.NotNull(instance);
-
- ISocketClient socketClient = (ISocketClient)instance;
- Assert.NotNull(socketClient);
- Assert.True(socketClient.IsConnected);
-
- await socketClient.CloseAsync();
- Assert.False(socketClient.IsConnected);
-
- var buffer = new byte[10];
- var len = await socketClient.ReceiveAsync(buffer);
- Assert.Equal(0, len);
- }
-
- [Fact]
- public void SocketClientOptions_Ok()
- {
- var options = new SocketClientOptions
- {
- ReceiveBufferSize = 1024 * 64,
- IsAutoReceive = true,
- ConnectTimeout = 1000,
- SendTimeout = 500,
- ReceiveTimeout = 500,
- LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0)
- };
- Assert.Equal(1024 * 64, options.ReceiveBufferSize);
- Assert.True(options.IsAutoReceive);
- Assert.Equal(1000, options.ConnectTimeout);
- Assert.Equal(500, options.SendTimeout);
- Assert.Equal(500, options.ReceiveTimeout);
- Assert.Equal(new IPEndPoint(IPAddress.Loopback, 0), options.LocalEndPoint);
- }
-
- private static TcpListener StartTcpServer(int port, Func handler)
- {
- var server = new TcpListener(IPAddress.Loopback, port);
- server.Start();
- Task.Run(() => AcceptClientsAsync(server, handler));
- return server;
- }
-
- private static async Task AcceptClientsAsync(TcpListener server, Func handler)
- {
- while (true)
- {
- var client = await server.AcceptTcpClientAsync();
- _ = Task.Run(() => handler(client));
- }
- }
-
- private static async Task MockDelimiterPackageAsync(TcpClient client)
- {
- using var stream = client.GetStream();
- while (true)
- {
- var buffer = new byte[10240];
- var len = await stream.ReadAsync(buffer);
- if (len == 0)
- {
- break;
- }
-
- // 回写数据到客户端
- var block = new ReadOnlyMemory(buffer, 0, len);
- await stream.WriteAsync(block, CancellationToken.None);
-
- await Task.Delay(20);
-
- // 模拟拆包发送第二段数据
- await stream.WriteAsync(new byte[] { 0x13, 0x10, 0x5, 0x6, 0x13, 0x10 }, CancellationToken.None);
- }
- }
-
- private static ITcpSocketClient CreateClient()
- {
- var sc = new ServiceCollection();
- sc.AddLogging(builder =>
- {
- builder.AddProvider(new MockLoggerProvider());
- });
- sc.AddBootstrapBlazorTcpSocketFactory();
- var provider = sc.BuildServiceProvider();
- var factory = provider.GetRequiredService();
- var client = factory.GetOrCreate("test", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0));
- return client;
- }
-
- class MockLoggerProvider : ILoggerProvider
- {
- public ILogger CreateLogger(string categoryName)
- {
- return new MockLogger();
- }
-
- public void Dispose()
- {
-
- }
- }
-
- class MockLogger : ILogger
- {
- public IDisposable? BeginScope(TState state) where TState : notnull
- {
- return null;
- }
-
- public bool IsEnabled(LogLevel logLevel)
- {
- return true;
- }
-
- public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
- {
-
- }
- }
-
- class MockSendErrorHandler : DataPackageHandlerBase
- {
- public ITcpSocketClient? Socket { get; set; }
-
- public override ValueTask> SendAsync(ReadOnlyMemory data, CancellationToken token = default)
- {
- throw new Exception("Mock send failed");
- }
- }
-
- class MockSendCancelHandler : DataPackageHandlerBase
- {
- public ITcpSocketClient? Socket { get; set; }
-
- public override async ValueTask> SendAsync(ReadOnlyMemory data, CancellationToken token = default)
- {
- if (Socket != null)
- {
- await Socket.CloseAsync();
- }
- await Task.Delay(10, token);
- return data;
- }
- }
-
- class MockReceiveErrorHandler : DataPackageHandlerBase
- {
- public override ValueTask> SendAsync(ReadOnlyMemory data, CancellationToken token = default)
- {
- return ValueTask.FromResult(data);
- }
-
- public override async ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default)
- {
- await base.ReceiveAsync(data, token);
-
- // 模拟接收数据时报错
- throw new InvalidOperationException("Test Error");
- }
- }
-
- class MockSendTimeoutHandler : DataPackageHandlerBase
- {
- public override async ValueTask> SendAsync(ReadOnlyMemory data, CancellationToken token = default)
- {
- // 模拟发送超时
- await Task.Delay(200, token);
- return data;
- }
- }
-
- class MockReceiveTimeoutHandler : DataPackageHandlerBase
- {
- public override async ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default)
- {
- // 模拟接收超时
- await Task.Delay(200, token);
- await base.ReceiveAsync(data, token);
- }
- }
-}
diff --git a/test/UnitTest/Services/TcpSocketFactoryTest.cs b/test/UnitTest/Services/TcpSocketFactoryTest.cs
index e9d4b1de43a..9fdcffeee53 100644
--- a/test/UnitTest/Services/TcpSocketFactoryTest.cs
+++ b/test/UnitTest/Services/TcpSocketFactoryTest.cs
@@ -49,7 +49,7 @@ public async Task GetOrCreate_Ok()
public async Task ConnectAsync_Timeout()
{
var client = CreateClient();
- client.ConnectTimeout = 100;
+ client.Options.ConnectTimeout = 1;
var connect = await client.ConnectAsync("localhost", 9999);
Assert.False(connect);
@@ -77,6 +77,26 @@ public async Task ConnectAsync_Failed()
Assert.False(connect);
}
+ [Fact]
+ public async Task ConnectAsync_Error()
+ {
+ var client = CreateClient();
+
+ // 反射设置 SocketClientProvider 为空
+ var propertyInfo = client.GetType().GetProperty("ServiceProvider", BindingFlags.Public | BindingFlags.Instance);
+ Assert.NotNull(propertyInfo);
+ propertyInfo.SetValue(client, null);
+
+ // 测试 ConnectAsync 方法连接失败
+ var ex = await Assert.ThrowsAsync(async () => await client.ConnectAsync("localhost", 9999));
+ Assert.NotNull(ex);
+
+ // 反射测试 Log 方法
+ var methodInfo = client.GetType().GetMethod("Log", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(methodInfo);
+ methodInfo.Invoke(client, [LogLevel.Error, null!, "Test error log"]);
+ }
+
[Fact]
public async Task Send_Timeout()
{
@@ -84,7 +104,7 @@ public async Task Send_Timeout()
var server = StartTcpServer(port, MockSplitPackageAsync);
var client = CreateClient();
- client.SendTimeout = 100;
+ client.Options.SendTimeout = 100;
client.SetDataHandler(new MockSendTimeoutHandler());
await client.ConnectAsync("localhost", port);
@@ -166,7 +186,7 @@ public async Task ReceiveAsync_Timeout()
var server = StartTcpServer(port, MockSplitPackageAsync);
var client = CreateClient();
- client.ReceiveTimeout = 100;
+ client.Options.ReceiveTimeout = 100;
client.SetDataHandler(new MockReceiveTimeoutHandler());
await client.ConnectAsync("localhost", port);
@@ -214,7 +234,7 @@ public async Task ReceiveAsync_InvalidOperationException()
var port = 8893;
var server = StartTcpServer(port, MockSplitPackageAsync);
- client.IsAutoReceive = true;
+ client.Options.IsAutoReceive = true;
var connected = await client.ConnectAsync("localhost", port);
Assert.True(connected);
@@ -229,7 +249,7 @@ public async Task ReceiveAsync_Ok()
var server = StartTcpServer(port, MockSplitPackageAsync);
var client = CreateClient();
- client.IsAutoReceive = false;
+ client.Options.IsAutoReceive = false;
var connected = await client.ConnectAsync("localhost", port);
Assert.True(connected);
@@ -260,10 +280,10 @@ public async Task ReceiveAsync_Error()
var port = 8882;
var server = StartTcpServer(port, MockSplitPackageAsync);
- Assert.Equal(1024 * 64, client.ReceiveBufferSize);
+ Assert.Equal(1024 * 64, client.Options.ReceiveBufferSize);
- client.ReceiveBufferSize = 1024 * 20;
- Assert.Equal(1024 * 20, client.ReceiveBufferSize);
+ client.Options.ReceiveBufferSize = 1024 * 20;
+ Assert.Equal(1024 * 20, client.Options.ReceiveBufferSize);
client.SetDataHandler(new MockReceiveErrorHandler());