Skip to content
This repository was archived by the owner on Nov 13, 2025. It is now read-only.
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 @@ -62,7 +62,10 @@ public static async ValueTask<ReadOnlyMemory<byte>> ReceiveAsync(this ITcpSocket
{
var len = await client.ReceiveAsync(buffer, token);
var payload = new byte[len];
Buffer.BlockCopy(buffer, 0, payload, 0, len);
if (len > 0)
{
Buffer.BlockCopy(buffer, 0, payload, 0, len);
}
return payload;
}
finally
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ static class TcpSocketClientOptionsExtensions
{
ReceiveBufferSize = source.ReceiveBufferSize,
IsAutoReceive = source.IsAutoReceive,
ConnectTimeout = source.ConnectTimeout,
SendTimeout = source.SendTimeout,
ReceiveTimeout = source.ReceiveTimeout,
LocalEndPoint = source.LocalEndPoint,
IsAutoReconnect = source.IsAutoReconnect,
ReconnectInterval = source.ReconnectInterval,
Expand Down
43 changes: 24 additions & 19 deletions src/Longbow.TcpSocket/Interface/DefaultTcpSocketClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,9 @@ public async ValueTask<bool> ConnectAsync(IPEndPoint endPoint, CancellationToken
var ret = false;
try
{
await CloseAsync();
await _semaphoreSlimForConnect.WaitAsync(token).ConfigureAwait(false);

// 并发控制
var connectToken = token;
if (Options.ConnectTimeout > 0)
{
// 设置连接超时时间
using var connectTokenSource = new CancellationTokenSource(Options.ConnectTimeout);
using var link = CancellationTokenSource.CreateLinkedTokenSource(connectToken, connectTokenSource.Token);
connectToken = link.Token;
}
await _semaphoreSlimForConnect.WaitAsync(connectToken).ConfigureAwait(false);
await CloseAsync();

if (OnConnecting != null)
{
Expand All @@ -99,7 +90,7 @@ public async ValueTask<bool> ConnectAsync(IPEndPoint endPoint, CancellationToken

if (Options.IsAutoReceive)
{
_ = Task.Run(AutoReceiveAsync, CancellationToken.None).ConfigureAwait(false);
_ = Task.Run(AutoReceiveAsync, CancellationToken.None);
}
}
finally
Expand All @@ -114,8 +105,9 @@ public async ValueTask<bool> ConnectAsync(IPEndPoint endPoint, CancellationToken

private async ValueTask AutoReceiveAsync()
{
// 自动接收方法
_autoReceiveTokenSource ??= new();
ResetAutoReceiveTokenSource();

Comment thread
ArgoZhang marked this conversation as resolved.
_autoReceiveTokenSource = new();

while (_autoReceiveTokenSource is { IsCancellationRequested: false })
{
Expand Down Expand Up @@ -176,19 +168,32 @@ public async ValueTask<bool> SendAsync(ReadOnlyMemory<byte> data, CancellationTo
/// <summary>
/// <inheritdoc/>
/// </summary>
public ValueTask<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default) => ReceiveCoreAsync(buffer);
public ValueTask<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default)
{
if (Options.IsAutoReceive)
{
throw new InvalidOperationException("Cannot call ReceiveAsync when IsAutoReceive is enabled. Please set IsAutoReceive to false");
}

/// <summary>
/// <inheritdoc/>
/// </summary>
public ValueTask CloseAsync()
return ReceiveCoreAsync(buffer);
}

private void ResetAutoReceiveTokenSource()
{
if (_autoReceiveTokenSource != null)
{
_autoReceiveTokenSource.Cancel();
_autoReceiveTokenSource.Dispose();
_autoReceiveTokenSource = null;
}
}

/// <summary>
/// <inheritdoc/>
/// </summary>
public ValueTask CloseAsync()
{
ResetAutoReceiveTokenSource();

_sender?.Dispose();
_sender = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,6 @@ public class TcpSocketClientOptions
/// </summary>
public bool IsAutoReceive { get; set; } = true;

/// <summary>
/// Gets or sets the timeout duration, in milliseconds, for establishing a connection.
/// </summary>
public int ConnectTimeout { get; set; }

/// <summary>
/// Gets or sets the duration, in milliseconds, to wait for a send operation to complete before timing out.
/// </summary>
/// <remarks>If the send operation does not complete within the specified timeout period, an exception may
/// be thrown.</remarks>
public int SendTimeout { get; set; }

/// <summary>
/// Gets or sets the amount of time, in milliseconds, that the receiver will wait for a response before timing out.
/// </summary>
/// <remarks>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.</remarks>
public int ReceiveTimeout { get; set; }

/// <summary>
/// Gets or sets the local endpoint for the socket client. Default value is <see cref="IPAddress.Any"/>
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Longbow.TcpSocket/Longbow.TcpSocket.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Version>9.0.12</Version>
<Version>9.0.13</Version>
</PropertyGroup>

<PropertyGroup>
Expand Down
84 changes: 31 additions & 53 deletions test/UnitTestTcpSocket/TcpSocketFactoryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public async Task GetOrCreate_Ok()
var sc = new ServiceCollection();
sc.AddTcpSocketFactory(op =>
{
op.ConnectTimeout = 1000;
op.LocalEndPoint = TcpSocketUtility.ConvertToIpEndPoint("localhost", 0);
});
var provider = sc.BuildServiceProvider();
var factory = provider.GetRequiredService<ITcpSocketFactory>();
Expand Down Expand Up @@ -93,7 +93,6 @@ public async Task SendAsync_Ok()
Assert.False(client.IsConnected);

// 连接 TCP Server
client.Options.ConnectTimeout = 1000;
await client.ConnectAsync("localhost", port);
Assert.True(client.IsConnected);
Assert.NotEqual(client.Options.LocalEndPoint, client.LocalEndPoint);
Expand All @@ -111,63 +110,13 @@ public async Task SendAsync_Ok()
// 测试正常电文
cst.Dispose();
cst = new();
client.Options.SendTimeout = 1000;
var result = await client.SendAsync("test", Encoding.UTF8, cst.Token);
Assert.True(result);

// 关闭连接
StopTcpServer(server);
}

[Fact]
public async Task ReceiveAsync_Timeout()
{
var port = 8888;
var server = StartTcpServer(port, MockSplitPackageAsync);

await using var client = CreateClient(configureOptions: op => op.ReceiveTimeout = 100);

await client.ConnectAsync("localhost", port);

var data = new ReadOnlyMemory<byte>([1, 2, 3, 4, 5]);
await client.SendAsync(data);

// 等待接收超时
await Task.Delay(120);
await client.CloseAsync();
}

[Fact]
public async Task ReceiveAsync_Zero()
{
var client = CreateClient(configureOptions: op => op.IsAutoReceive = false);

// 已连接但是启用了自动接收功能时调用 ReceiveAsync 方法会抛出 InvalidOperationException 异常
var port = 8894;
var server = StartTcpServer(port, MockZeroPackageAsync);
await client.ConnectAsync("localhost", port);

await Assert.ThrowsAnyAsync<Exception>(async () => await client.ReceiveAsync());
server.Stop();
}

[Fact]
public async Task ReceiveAsync_NotConnected()
{
// 未连接时调用 ReceiveAsync 方法会抛出 InvalidOperationException 异常
var client = CreateClient(configureOptions: op => op.IsAutoReceive = true);
// 内部未连接 返回 0 字节
var len = await client.ReceiveAsync(new byte[1024]);
Assert.Equal(0, len);

// 已连接但是启用了自动接收功能时调用 ReceiveAsync 方法会抛出 InvalidOperationException 异常
var port = 8893;
var server = StartTcpServer(port, MockSplitPackageAsync);

var connected = await client.ConnectAsync("localhost", port);
Assert.True(connected);
}

[Fact]
public async Task ReceiveAsync_Ok()
{
Expand All @@ -177,6 +126,11 @@ public async Task ReceiveAsync_Ok()
var server = StartTcpServer(port, MockSplitPackageAsync);

var client = CreateClient(configureOptions: op => op.IsAutoReceive = false);

// 未连接时调用 ReceiveAsync 方法会返回 0 字节
var payload = await client.ReceiveAsync();
Assert.Equal(0, payload.Length);

client.OnConnecting = () =>
{
onConnecting = true;
Expand All @@ -187,11 +141,14 @@ public async Task ReceiveAsync_Ok()
onConnected = true;
return Task.CompletedTask;
};

// 连接 TCP Server
var connected = await client.ConnectAsync("localhost", port);
Assert.True(connected);
Assert.True(onConnecting);
Assert.True(onConnected);

// 发送数据
var data = new ReadOnlyMemory<byte>([1, 2, 3, 4, 5]);
var send = await client.SendAsync(data);
Assert.True(send);
Expand All @@ -204,8 +161,29 @@ public async Task ReceiveAsync_Ok()

// 由于服务器端模拟了拆包发送第二段数据,所以这里可以再次调用 ReceiveAsync 方法获取第二段数据
// 调用扩展方法直接获得到接收数据
var payload = await client.ReceiveAsync();
payload = await client.ReceiveAsync();
Assert.Equal([3, 4], payload.ToArray());

// 设置 IsAutoReceive = true 后,调用 ReceiveAsync 方法会抛出 InvalidOperationException 异常
client.Options.IsAutoReceive = true;
await Assert.ThrowsAnyAsync<InvalidOperationException>(async () => await client.ReceiveAsync(buffer));

// 关闭后停止自动接收
await client.CloseAsync();
}

[Fact]
public async Task ReceiveAsync_Zero()
{
var client = CreateClient(configureOptions: op => op.IsAutoReceive = false);

// 已连接但是启用了自动接收功能时调用 ReceiveAsync 方法会抛出 InvalidOperationException 异常
var port = 8894;
var server = StartTcpServer(port, MockZeroPackageAsync);
await client.ConnectAsync("localhost", port);

await Assert.ThrowsAnyAsync<Exception>(async () => await client.ReceiveAsync());
server.Stop();
}

[Fact]
Expand Down