-
-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathITcpSocketClientExtensions.cs
More file actions
305 lines (279 loc) · 14.5 KB
/
ITcpSocketClientExtensions.cs
File metadata and controls
305 lines (279 loc) · 14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Reflection;
using System.Runtime.Versioning;
using System.Text;
namespace BootstrapBlazor.TcpSocket;
/// <summary>
/// <see cref="ITcpSocketClient"/> 扩展方法类
/// </summary>
[UnsupportedOSPlatform("browser")]
public static class ITcpSocketClientExtensions
{
/// <summary>
/// Sends the specified string content to the connected TCP socket client asynchronously.
/// </summary>
/// <remarks>This method converts the provided string content into a byte array using the specified
/// encoding (or UTF-8 by default) and sends it to the connected TCP socket client. Ensure the client is connected
/// before calling this method.</remarks>
/// <param name="client">The TCP socket client to which the content will be sent. Cannot be <see langword="null"/>.</param>
/// <param name="content">The string content to send. Cannot be <see langword="null"/> or empty.</param>
/// <param name="encoding">The character encoding to use for converting the string content to bytes. If <see langword="null"/>, UTF-8
/// encoding is used by default.</param>
/// <param name="token">A <see cref="CancellationToken"/> to observe while waiting for the operation to complete.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> that represents the asynchronous operation. The result is <see
/// langword="true"/> if the content was sent successfully; otherwise, <see langword="false"/>.</returns>
public static ValueTask<bool> SendAsync(this ITcpSocketClient client, string content, Encoding? encoding = null, CancellationToken token = default)
{
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 = TcpSocketUtility.ConvertToIpEndPoint(ipString, port);
return client.ConnectAsync(endPoint, token);
}
private static readonly Dictionary<ITcpSocketClient, List<(IDataPackageAdapter Adapter, Func<ReadOnlyMemory<byte>, ValueTask> Callback)>> _cache = [];
/// <summary>
/// 增加 <see cref="ITcpSocketClient"/> 数据适配器及其对应的回调方法
/// </summary>
/// <param name="client"></param>
/// <param name="adapter"></param>
/// <param name="callback"></param>
public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, Func<ReadOnlyMemory<byte>, ValueTask> callback)
{
if (_cache.TryGetValue(client, out var list))
{
list.Add((adapter, cb));
}
else
{
_cache.Add(client, [(adapter, cb)]);
}
client.ReceivedCallBack += cb;
// 设置 DataPackageAdapter 的回调函数
adapter.ReceivedCallBack = callback;
async ValueTask cb(ReadOnlyMemory<byte> buffer)
{
// 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调
await adapter.HandlerAsync(buffer);
}
}
/// <summary>
/// 移除 <see cref="ITcpSocketClient"/> 数据适配器及其对应的回调方法
/// </summary>
/// <param name="client"></param>
/// <param name="callback"></param>
public static void RemoveDataPackageAdapter(this ITcpSocketClient client, Func<ReadOnlyMemory<byte>, ValueTask> callback)
{
if (_cache.TryGetValue(client, out var list))
{
var items = list.Where(i => i.Adapter.ReceivedCallBack == callback).ToList();
foreach (var c in items)
{
client.ReceivedCallBack -= c.Callback;
list.Remove(c);
}
}
}
/// <summary>
/// Configures the specified <see cref="ITcpSocketClient"/> to use the provided <see cref="IDataPackageAdapter"/>
/// for processing received data and sets a callback to handle processed data.
/// </summary>
/// <remarks>This method sets up a two-way data processing pipeline: <list type="bullet"> <item>
/// <description>The <paramref name="client"/> is configured to pass received data to the <paramref name="adapter"/>
/// for processing.</description> </item> <item> <description>The <paramref name="adapter"/> is configured to invoke
/// the provided <paramref name="callback"/> with the processed data.</description> </item> </list> Use this method
/// to integrate a custom data processing adapter with a TCP socket client.</remarks>
/// <param name="client">The <see cref="ITcpSocketClient"/> instance to configure.</param>
/// <param name="adapter">The <see cref="IDataPackageAdapter"/> used to process incoming data.</param>
/// <param name="callback">A callback function invoked with the processed data. The function receives a <see cref="ReadOnlyMemory{T}"/>
/// containing the processed data and returns a <see cref="ValueTask"/>.</param>
public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, Func<ReadOnlyMemory<byte>, ValueTask> callback)
{
// 释放缓存
if (_cache.TryGetValue(client, out var list))
{
foreach (var (Adapter, Callback) in list)
{
client.ReceivedCallBack -= Callback;
}
list.Clear();
}
// 设置 ITcpSocketClient 的回调函数
client.ReceivedCallBack = async buffer =>
{
// 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调
await adapter.HandlerAsync(buffer);
};
// 设置 DataPackageAdapter 的回调函数
adapter.ReceivedCallBack = callback;
}
/// <summary>
/// 通过指定 <see cref="IDataPackageHandler"/> 数据处理实例,设置数据适配器并配置回调方法
/// </summary>
/// <param name="client"><see cref="ITcpSocketClient"/> 实例</param>
/// <param name="handler"><see cref="IDataPackageHandler"/> 数据处理实例</param>
/// <param name="callback">回调方法</param>
public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageHandler handler, Func<ReadOnlyMemory<byte>, ValueTask> callback)
{
client.SetDataPackageAdapter(new DataPackageAdapter(handler), callback);
}
/// <summary>
/// Configures the specified <see cref="ITcpSocketClient"/> to use a data package adapter and a callback function
/// for processing received data.
/// </summary>
/// <remarks>This method sets up the <paramref name="client"/> to process incoming data using the
/// specified <paramref name="adapter"/> and <paramref name="socketDataConverter"/>. The <paramref
/// name="callback"/> is called with the converted entity whenever data is received.</remarks>
/// <typeparam name="TEntity">The type of the entity that the data will be converted to.</typeparam>
/// <param name="client">The TCP socket client to configure.</param>
/// <param name="adapter">The data package adapter responsible for handling incoming data.</param>
/// <param name="socketDataConverter">The converter used to transform the received data into the specified entity type.</param>
/// <param name="callback">The callback function to be invoked with the converted entity.</param>
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageAdapter adapter, IDataConverter<TEntity> socketDataConverter, Func<TEntity?, Task> callback)
{
// 释放缓存
if (_cache.TryGetValue(client, out var list))
{
foreach (var (Adapter, Callback) in list)
{
client.ReceivedCallBack -= Callback;
}
list.Clear();
}
// 设置 ITcpSocketClient 的回调函数
client.ReceivedCallBack = async buffer =>
{
// 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调
await adapter.HandlerAsync(buffer);
};
// 设置 DataPackageAdapter 的回调函数
adapter.ReceivedCallBack = async buffer =>
{
TEntity? ret = default;
if (socketDataConverter.TryConvertTo(buffer, out var t))
{
ret = t;
}
await callback(ret);
};
}
/// <summary>
/// 通过指定 <see cref="IDataPackageHandler"/> 数据处理实例,设置数据适配器并配置回调方法
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="client"></param>
/// <param name="handler"></param>
/// <param name="socketDataConverter"></param>
/// <param name="callback"></param>
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageHandler handler, IDataConverter<TEntity> socketDataConverter, Func<TEntity?, Task> callback)
{
client.SetDataPackageAdapter(new DataPackageAdapter(handler), socketDataConverter, callback);
}
/// <summary>
/// Configures the specified <see cref="ITcpSocketClient"/> to use a custom data package adapter and callback
/// function.
/// </summary>
/// <remarks>This method sets up the <paramref name="client"/> to use the specified <paramref
/// name="adapter"/> for handling incoming data. If the <typeparamref name="TEntity"/> type is decorated with a <see
/// cref="DataTypeConverterAttribute"/>, the associated converter is used to transform the data before invoking
/// the <paramref name="callback"/>. The callback is called with the converted entity or <see langword="null"/> if
/// conversion fails.</remarks>
/// <typeparam name="TEntity">The type of entity that the data package adapter will handle.</typeparam>
/// <param name="client">The TCP socket client to configure.</param>
/// <param name="adapter">The data package adapter responsible for processing incoming data.</param>
/// <param name="callback">The callback function to invoke with the processed entity of type <typeparamref name="TEntity"/>.</param>
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageAdapter adapter, Func<TEntity?, Task> callback)
{
// 释放缓存
if (_cache.TryGetValue(client, out var list))
{
foreach (var (Adapter, Callback) in list)
{
client.ReceivedCallBack -= Callback;
}
list.Clear();
}
// 设置 ITcpSocketClient 的回调函数
client.ReceivedCallBack = async buffer =>
{
// 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调
await adapter.HandlerAsync(buffer);
};
IDataConverter<TEntity>? converter = null;
var type = typeof(TEntity);
var converterType = type.GetCustomAttribute<DataTypeConverterAttribute>();
if (converterType is { Type: not null })
{
// 如果类型上有 SocketDataTypeConverterAttribute 特性则使用特性中指定的转换器
if (Activator.CreateInstance(converterType.Type) is IDataConverter<TEntity> socketDataConverter)
{
converter = socketDataConverter;
}
}
else
{
// 如果没有特性则从 ITcpSocketClient 中的服务容器获取转换器
converter = client.GetSocketDataConverter<TEntity>();
}
if (converter == null)
{
// 设置正常回调
adapter.ReceivedCallBack = async buffer => await callback(default);
}
else
{
// 设置转化器
adapter.SetDataAdapterCallback(converter, callback);
}
}
/// <summary>
/// 通过指定 <see cref="IDataPackageHandler"/> 数据处理实例,设置数据适配器并配置回调方法
/// </summary>
/// <param name="client"><see cref="ITcpSocketClient"/> 实例</param>
/// <param name="handler"><see cref="IDataPackageHandler"/> 数据处理实例</param>
/// <param name="callback">回调方法</param>
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageHandler handler, Func<TEntity?, Task> callback)
{
client.SetDataPackageAdapter(new DataPackageAdapter(handler), callback);
}
private static void SetDataAdapterCallback<TEntity>(this IDataPackageAdapter adapter, IDataConverter<TEntity> converter, Func<TEntity?, Task> callback)
{
adapter.ReceivedCallBack = async buffer =>
{
TEntity? ret = default;
if (converter.TryConvertTo(buffer, out var t))
{
ret = t;
}
await callback(ret);
};
}
private static IDataConverter<TEntity>? GetSocketDataConverter<TEntity>(this ITcpSocketClient client)
{
IDataConverter<TEntity>? converter = null;
if (client is IServiceProvider provider)
{
var converters = provider.GetRequiredService<IOptions<DataConverterCollections>>().Value;
if (converters.TryGetTypeConverter<TEntity>(out var v))
{
converter = v;
}
}
return converter;
}
}