/// <summary>
/// 稳定的长链接
/// 通用设备 TCP 客户端(无心跳、无协议假设、支持大流量)
/// 适用于 Modbus、PLC、传感器、摄像头等任意 TCP 设备
/// </summary>
internal class DeviceTcpClient : IDisposable
{
private TcpClient? _TcpClient;
private NetworkStream? _NetworkStream;
private CancellationTokenSource? _ReceiveCts;
private readonly SemaphoreSlim _SendSemaphore = new(1, 1);
private bool _IsDisposed = false;
public IPEndPoint? LocalEndPoint => _TcpClient?.Client.LocalEndPoint as IPEndPoint;
public IPEndPoint? RemoteEndPoint => _TcpClient?.Client.RemoteEndPoint as IPEndPoint;
public bool IsConnected => _TcpClient?.Connected == true && _NetworkStream?.CanRead == true;
/// <summary>
/// 接收到原始数据块时触发(可能是一次 TCP 读取的结果,需上层拼包)
/// </summary>
public event Func<ReadOnlySpan<byte>, Task>? OnDataReceived;
/// <summary>
/// 连接意外断开时触发
/// </summary>
public event Action? OnConnectionLost;
#region 连接
// ========== 连接管理 ==========
public async Task<bool> ConnectAsync(IPEndPoint remoteEndPoint)
{
if (_IsDisposed) throw new ObjectDisposedException(nameof(DeviceTcpClient));
DisconnectInternal();
try
{
var client = new TcpClient();
await client.ConnectAsync(remoteEndPoint.Address, remoteEndPoint.Port).ConfigureAwait(false);
_TcpClient = client;
_NetworkStream = client.GetStream();
_ReceiveCts = new CancellationTokenSource();
_ = Task.Run(ReceiveLoop, _ReceiveCts.Token);
return true;
}
catch
{
DisconnectInternal();
return false;
}
}
public Task<bool> ConnectAsync(IPAddress address, int port) =>
ConnectAsync(new IPEndPoint(address, port));
public async Task<bool> ConnectAsync(string host, int port)
{
if (IPAddress.TryParse(host, out var ip))
return await ConnectAsync(ip, port).ConfigureAwait(false);
var addresses = await Dns.GetHostAddressesAsync(host).ConfigureAwait(false);
return addresses.Length > 0 && await ConnectAsync(addresses[0], port).ConfigureAwait(false);
}
public bool Connect(IPEndPoint remoteEndPoint) =>
ConnectAsync(remoteEndPoint).GetAwaiter().GetResult();
public bool Connect(IPAddress address, int port) =>
Connect(new IPEndPoint(address, port));
public bool Connect(string host, int port) =>
ConnectAsync(host, port).GetAwaiter().GetResult();
#endregion
// ========== 接收循环 ==========
private async Task ReceiveLoop()
{
var buffer = new byte[1024 * 10];
try
{
while (!_ReceiveCts!.IsCancellationRequested)
{
var n = await _NetworkStream!
.ReadAsync(buffer, 0, buffer.Length, _ReceiveCts.Token)
.ConfigureAwait(false); /* 这里已经是异步等待回复了,不会占用 cup 资源,不需要再加 Task.Delay */
if (n == 0) break;
var memory = buffer.AsMemory(0, n);
OnDataReceived?.Invoke(memory.Span);
}
}
catch (Exception ex) when (ex is not OperationCanceledException) { }
finally { HandleConnectionLost(); }
}
private void HandleConnectionLost()
{
if (!_IsDisposed) { DisconnectInternal(); OnConnectionLost?.Invoke(); }
}
public void Dispose()
{
if (_IsDisposed) return;
_IsDisposed = true;
DisconnectInternal();
_SendSemaphore.Dispose();
GC.SuppressFinalize(this);
}
public void Disconnect() => DisconnectInternal();
private void DisconnectInternal()
{
_ReceiveCts?.Cancel();
_ReceiveCts?.Dispose();
_ReceiveCts = null;
_NetworkStream?.Dispose();
_NetworkStream = null;
_TcpClient?.Dispose();
_TcpClient = null;
}
// ========== 发送方法 ==========
/// <summary>
/// 异步发送数据(fire-and-forget)
/// </summary>
public async Task SendAsync(byte[] data)
{
if (data == null) throw new ArgumentNullException(nameof(data));
if (_NetworkStream == null || !_NetworkStream.CanWrite)
throw new InvalidOperationException("Not connected.");
await _SendSemaphore.WaitAsync().ConfigureAwait(false);
try
{
await _NetworkStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
await _NetworkStream.FlushAsync().ConfigureAwait(false);
}
finally
{
_SendSemaphore.Release();
}
}
// 减少重复创建对象的消耗
MemoryStream _ReceiveStream = new();
Task onSendReceived(ReadOnlySpan<byte> bytes)
{
_ReceiveStream.Write(bytes);
return Task.CompletedTask;
}
public async Task<byte[]> SendAsync(byte[] data, TimeSpan timeout)
{
if (!IsConnected)
throw new InvalidOperationException("Not connected.");
await _SendSemaphore.WaitAsync().ConfigureAwait(false);
CancellationTokenSource _timeoutCTS = new(timeout);
try
{
_ReceiveStream.SetLength(0);
OnDataReceived += onSendReceived;
await _NetworkStream!.WriteAsync(data).ConfigureAwait(false);
/* 因为数据在异步收取,DataAvailable==true 的瞬间可能错过,不能用 DataAvailable 判断数据收取完毕 */
long receiveLength = _ReceiveStream.Length;
int equalTimes = 0;
while (!_timeoutCTS.Token.IsCancellationRequested)
{
await Task.Delay(30, _timeoutCTS.Token);
if (_ReceiveStream.Length == 0) continue; // 没收到就继续等
//Console.WriteLine($"equalTimes:{equalTimes}");
if (receiveLength == _ReceiveStream.Length)
{
if (equalTimes > 5) break;
equalTimes++;
}
else
{
receiveLength = _ReceiveStream.Length;
equalTimes = 0;
}
}
return _ReceiveStream.ToArray();
}
catch (TaskCanceledException) when (_timeoutCTS.Token.IsCancellationRequested)
{
return _ReceiveStream.ToArray();
}
catch
{
throw;
}
finally
{
OnDataReceived -= onSendReceived;
_SendSemaphore.Release();
}
}
}