You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TCP客户端断开触发System.StackOverflowException异常求助

TCP客户端断开引发StackOverflowException的修复及重连实现

异常原因分析

当TCP客户端断开连接时,stream.EndRead()会返回0(表示无更多数据可读),但当前代码未处理该情况,而是直接再次调用BeginRead()。同时_reading始终为true,导致readCompleteCallback与BeginRead无限循环调用,最终触发栈溢出异常。此外,连接断开时EndRead()还可能抛出IOException,未被捕获也会导致异常扩散。

修复方案

  1. 处理连接断开信号:判断bytesRead == 0时,停止读取流程,清理资源。
  2. 捕获IO异常:包裹EndRead()调用,捕获连接断开时的异常。
  3. 实现重连逻辑:断开后启动重试连接机制,带间隔延迟避免频繁重试。

修改后的完整代码

private bool _reading;
private Stream _stream;
private TcpClient _client;
private IAsyncResult _readResult;
private bool _disposed;
private CancellationTokenSource _reconnectCts; // 用于取消重连

private byte[] _parseBuffer = new byte[0];
private byte[] _buffer = new byte[1024];
private int _maxBytesRead;

private void readCompleteCallback(IAsyncResult readAsyncResult)
{
    if (_reading)
    {   
        Stream stream = (Stream)readAsyncResult.AsyncState;
        try
        {
            int bytesRead = stream.EndRead(readAsyncResult);

            // 客户端断开连接:bytesRead为0
            if (bytesRead == 0)
            {
                StopReading();
                StartReconnect();
                return;
            }

            if (bytesRead > _maxBytesRead || bytesRead == _buffer.Length)
            {
                _maxBytesRead = bytesRead;
            }
            
            byte[] newParseBuffer = new byte[_parseBuffer.Length + bytesRead];
            _parseBuffer.CopyTo(newParseBuffer, 0);
            Array.Copy(_buffer, 0, newParseBuffer, _parseBuffer.Length, bytesRead);
            _parseBuffer = newParseBuffer;

            // 解析消息
            int bytesConsumed = MessageFactory.GetNextMessages(
                _parseBuffer,        
                0,                  
                _parseBuffer.Length,
                out var messages);

            // 处理未消费的缓冲区数据
            if (bytesConsumed < _parseBuffer.Length)
            {
                byte[] remainingBuffer = new byte[_parseBuffer.Length - bytesConsumed];
                Array.Copy(_parseBuffer, bytesConsumed, remainingBuffer, 0, remainingBuffer.Length);
                _parseBuffer = remainingBuffer;
            }
            else
            {
                _parseBuffer = new byte[0];
            }

            // 继续读取下一批数据
            _readResult = stream.BeginRead(
                _buffer,
                0,
                _buffer.Length,
                new AsyncCallback(readCompleteCallback),
                stream);
        }
        catch (IOException ex)
        {
            // 连接异常断开,比如客户端强制关闭
            StopReading();
            StartReconnect();
        }
    }
}

/// <summary>
/// 停止读取并清理资源
/// </summary>
private void StopReading()
{
    _reading = false;
    _stream?.Close();
    _client?.Close();
    _disposed = true;
}

/// <summary>
/// 启动重连逻辑
/// </summary>
private async void StartReconnect()
{
    _reconnectCts?.Cancel();
    _reconnectCts = new CancellationTokenSource();
    var token = _reconnectCts.Token;

    while (!token.IsCancellationRequested)
    {
        try
        {
            // 替换为你的服务器地址和端口
            _client = new TcpClient();
            await _client.ConnectAsync("127.0.0.1", 8888);
            
            // 重连成功,恢复读取
            _stream = _client.GetStream();
            _disposed = false;
            _reading = true;
            _parseBuffer = new byte[0]; // 重置解析缓冲区
            _readResult = _stream.BeginRead(
                _buffer,
                0,
                _buffer.Length,
                new AsyncCallback(readCompleteCallback),
                _stream);
            break;
        }
        catch (SocketException)
        {
            // 连接失败,延迟重试
            await Task.Delay(3000, token);
        }
    }
}

/// <summary>
/// 手动停止重连(比如程序退出时)
/// </summary>
public void StopReconnect()
{
    _reconnectCts?.Cancel();
    _reconnectCts?.Dispose();
}

关键修改点说明

  • 断开检测:通过bytesRead == 0和捕获IOException判断连接断开,立即停止读取并触发重连。
  • 资源清理:StopReading方法统一关闭流和客户端,标记状态。
  • 重连机制:使用异步重试,每次失败后延迟3秒,支持通过CancellationToken取消重连。
  • 缓冲区优化:用Array.Copy替代手动循环,提升性能。

内容的提问来源于stack exchange,提问作者John Pendarvis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 09:58:25