Skip to content
This repository was archived by the owner on Nov 13, 2025. It is now read-only.

Commit c5079ff

Browse files
authored
feat(BufferList): support BufferList parameter (#38)
* feat: 增加 BufferList 参数支持 * refactor: 增加偏移量设置 * feat: 支持 BufferList 参数 * test: 增加单元测试 * test: 增加单元测试
1 parent 602f3cc commit c5079ff

5 files changed

Lines changed: 111 additions & 53 deletions

File tree

src/Longbow.TcpSocket/Interface/DefaultTcpSocketClient.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,25 @@ public async ValueTask<bool> SendAsync(ReadOnlyMemory<byte> data, CancellationTo
165165
return ret;
166166
}
167167

168+
/// <summary>
169+
/// <inheritdoc/>
170+
/// </summary>
171+
public async ValueTask<bool> SendAsync(IList<ArraySegment<byte>> data, CancellationToken token = default)
172+
{
173+
if (_client != null)
174+
{
175+
_sender ??= new Sender(_client.Client);
176+
}
177+
178+
var ret = false;
179+
if (_sender != null)
180+
{
181+
ret = await _sender.SendAsync(data);
182+
}
183+
184+
return ret;
185+
}
186+
168187
/// <summary>
169188
/// <inheritdoc/>
170189
/// </summary>

src/Longbow.TcpSocket/Interface/ITcpSocketClient.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ public interface ITcpSocketClient : IAsyncDisposable
7171
/// sent successfully; otherwise, <see langword="false"/>.</returns>
7272
ValueTask<bool> SendAsync(ReadOnlyMemory<byte> data, CancellationToken token = default);
7373

74+
/// <summary>
75+
/// 批量发送数据方法
76+
/// </summary>
77+
/// <param name="data"></param>
78+
/// <param name="token"></param>
79+
/// <returns></returns>
80+
ValueTask<bool> SendAsync(IList<ArraySegment<byte>> data, CancellationToken token = default);
81+
7482
/// <summary>
7583
/// 异步接收方法
7684
/// </summary>

src/Longbow.TcpSocket/Internal/Receiver.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@ protected override void OnCompleted(SocketAsyncEventArgs e)
4141
}
4242
}
4343

44+
#region IValueTaskSource Implementation
4445
int IValueTaskSource<int>.GetResult(short token) => _tcs.GetResult(token);
4546

4647
ValueTaskSourceStatus IValueTaskSource<int>.GetStatus(short token) => _tcs.GetStatus(token);
4748

4849
void IValueTaskSource<int>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
4950
=> _tcs.OnCompleted(continuation, state, token, flags);
51+
#endregion
5052
}

src/Longbow.TcpSocket/Internal/Sender.cs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,46 +11,44 @@ namespace Longbow.TcpSocket;
1111
sealed class Sender(Socket socket) : SocketAsyncEventArgs, IValueTaskSource<bool>
1212
{
1313
private ManualResetValueTaskSourceCore<bool> _tcs;
14-
private int _length;
15-
private int _totalSent;
16-
private Memory<byte> _buffer;
1714

1815
public ValueTask<bool> SendAsync(ReadOnlyMemory<byte> data)
1916
{
2017
_tcs.Reset();
2118

22-
_length = data.Length;
23-
_totalSent = 0;
24-
_buffer = MemoryMarshal.AsMemory(data);
25-
SendCoreAsync();
19+
var _buffer = MemoryMarshal.AsMemory(data);
20+
SetBuffer(_buffer);
21+
22+
// 发送数据
23+
if (!socket.SendAsync(this))
24+
{
25+
OnCompleted(this);
26+
}
2627

2728
return new ValueTask<bool>(this, _tcs.Version);
2829
}
2930

30-
private void SendCoreAsync()
31+
public ValueTask<bool> SendAsync(IList<ArraySegment<byte>> data)
3132
{
32-
int bytesToSend = Math.Min(_length - _totalSent, 1460);
33-
SetBuffer(_buffer.Slice(_totalSent, bytesToSend));
33+
_tcs.Reset();
34+
35+
SetBuffer(null, 0, 0);
36+
BufferList = data;
3437

38+
// 发送数据
3539
if (!socket.SendAsync(this))
3640
{
3741
OnCompleted(this);
3842
}
43+
44+
return new ValueTask<bool>(this, _tcs.Version);
3945
}
4046

4147
protected override void OnCompleted(SocketAsyncEventArgs e)
4248
{
4349
if (e.SocketError == SocketError.Success)
4450
{
45-
_totalSent += e.BytesTransferred;
46-
if (_totalSent >= _length)
47-
{
48-
_tcs.SetResult(true);
49-
}
50-
else
51-
{
52-
SendCoreAsync();
53-
}
51+
_tcs.SetResult(true);
5452
}
5553
else
5654
{
@@ -59,11 +57,13 @@ protected override void OnCompleted(SocketAsyncEventArgs e)
5957
}
6058
}
6159

60+
#region IValueTaskSource Implementation
6261
bool IValueTaskSource<bool>.GetResult(short token) => _tcs.GetResult(token);
6362

6463
ValueTaskSourceStatus IValueTaskSource<bool>.GetStatus(short token) => _tcs.GetStatus(token);
6564

6665
[ExcludeFromCodeCoverage]
6766
void IValueTaskSource<bool>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
6867
=> _tcs.OnCompleted(continuation, state, token, flags);
68+
#endregion
6969
}

test/UnitTestTcpSocket/TcpSocketFactoryTest.cs

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -49,40 +49,6 @@ public async Task GetOrCreate_Ok()
4949
await factory.DisposeAsync();
5050
}
5151

52-
[Fact]
53-
public async Task OnCompleted_Ok()
54-
{
55-
var port = 8882;
56-
var server = StartTcpServer(port, MockSplitPackageAsync);
57-
58-
var client = CreateClient();
59-
await client.ConnectAsync("localhost", port);
60-
61-
// 获得 client 内部 Socket 对象
62-
var clientField = client.GetType().GetField("_client", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
63-
Assert.NotNull(clientField);
64-
65-
var tcpClient = clientField.GetValue(client) as TcpClient;
66-
Assert.NotNull(tcpClient);
67-
68-
var senderType = Type.GetType("Longbow.TcpSocket.Sender, Longbow.TcpSocket");
69-
Assert.NotNull(senderType);
70-
71-
var sender = Activator.CreateInstance(senderType, tcpClient.Client);
72-
73-
// 反射获得 OnCompleted 方法
74-
var method = senderType.GetMethod("OnCompleted", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
75-
Assert.NotNull(method);
76-
77-
// 反射获得 SendAsync 方法
78-
var sendAsyncMethod = senderType.GetMethod("SendAsync", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
79-
Assert.NotNull(sendAsyncMethod);
80-
81-
// 调用 SendAsync 方法
82-
ReadOnlyMemory<byte> buffer = new byte[2048];
83-
sendAsyncMethod.Invoke(sender, [buffer]);
84-
}
85-
8652
[Fact]
8753
public async Task SendAsync_Ok()
8854
{
@@ -117,6 +83,53 @@ public async Task SendAsync_Ok()
11783
StopTcpServer(server);
11884
}
11985

86+
[Fact]
87+
public async Task SendAsync_BufferList_Ok()
88+
{
89+
var port = 8883;
90+
var server = StartTcpServer(port, MockSplitPackageAsync);
91+
92+
var client = CreateClient();
93+
94+
// 连接 TCP Server
95+
await client.ConnectAsync("localhost", port);
96+
97+
var buffer = new List<ArraySegment<byte>>
98+
{
99+
new byte[2],
100+
new byte[3]
101+
};
102+
var result = await client.SendAsync(buffer);
103+
Assert.True(result);
104+
105+
// 关闭连接
106+
StopTcpServer(server);
107+
}
108+
109+
[Fact]
110+
public void Sender_Coverage_Ok()
111+
{
112+
// 利用反射测试 Sender OnCompleted 方法覆盖率
113+
var type = Type.GetType("Longbow.TcpSocket.Sender, Longbow.TcpSocket");
114+
Assert.NotNull(type);
115+
116+
var instance = Activator.CreateInstance(type, new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp));
117+
Assert.NotNull(instance);
118+
119+
var method = type.GetMethod("OnCompleted", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
120+
Assert.NotNull(method);
121+
122+
method.Invoke(instance, [new MockSocketAsyncEventArgs()]);
123+
}
124+
125+
class MockSocketAsyncEventArgs : SocketAsyncEventArgs
126+
{
127+
public MockSocketAsyncEventArgs()
128+
{
129+
SocketError = SocketError.SocketError;
130+
}
131+
}
132+
120133
[Fact]
121134
public async Task ReceiveAsync_Ok()
122135
{
@@ -186,6 +199,22 @@ public async Task ReceiveAsync_Zero()
186199
server.Stop();
187200
}
188201

202+
[Fact]
203+
public void Receiver_Coverage_Ok()
204+
{
205+
// 利用反射测试 Receiver OnCompleted 方法覆盖率
206+
var type = Type.GetType("Longbow.TcpSocket.Receiver, Longbow.TcpSocket");
207+
Assert.NotNull(type);
208+
209+
var instance = Activator.CreateInstance(type, new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp));
210+
Assert.NotNull(instance);
211+
212+
var method = type.GetMethod("OnCompleted", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
213+
Assert.NotNull(method);
214+
215+
method.Invoke(instance, [new MockSocketAsyncEventArgs()]);
216+
}
217+
189218
[Fact]
190219
public async Task AddDataPackageAdapter_Ok()
191220
{

0 commit comments

Comments
 (0)