TcpClient 一个TCP的收发类,支持长数据读写

/// <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();
        }
    }
}

 

posted @ 2025-03-06 18:19  cchong005  阅读(10)  评论(0)    收藏  举报