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

C#客户端服务器应用:服务器断开时终止Deserialize阻塞问题求助

解决C#客户端Deserialize阻塞问题的几种方案

这个问题我在做C#客户端服务端通信时碰到过好多次——当服务器意外宕机,客户端的反序列化线程会卡在Deserialize方法上,因为底层的NetworkStream在连接静默断开时不会主动抛出异常,就一直耗着。下面给你几个实用的解决思路:

1. 设置流读取超时

最简单的方法是给NetworkStream设置ReadTimeout,当超过指定时间没有读到数据时,会直接抛出IOException,你可以捕获这个异常来清理资源、终止线程:

public void ConnectToServer(string alias, string color)
{
    try
    {
        client = new TcpClient();
        client.Connect("服务器地址", 端口号);
        
        NetworkStream stream = client.GetStream();
        stream.ReadTimeout = 5000; // 设置5秒超时,可根据业务调整
        IFormatter formatter = new BinaryFormatter();

        MyMessage firstMsg = new MyMessage();
        firstMsg.Message = "MyNewClient";
        // 发送初始消息
        formatter.Serialize(stream, firstMsg);

        // 循环接收消息
        while (true)
        {
            try
            {
                MyMessage receivedMsg = (MyMessage)formatter.Deserialize(stream);
                // 处理收到的消息
            }
            catch (IOException)
            {
                // 超时或连接断开,退出循环并清理
                break;
            }
        }
    }
    catch (Exception ex)
    {
        // 处理其他异常
    }
    finally
    {
        // 确保资源被释放
        client?.Close();
        client?.Dispose();
    }
}

2. 结合异步操作与取消令牌

如果你的业务不适合设置固定超时,可以用异步方式包装反序列化操作,配合CancellationToken来主动终止阻塞。同时你可以单独开一个线程检测连接状态,一旦发现连接断开就触发取消:

private CancellationTokenSource _cts;

public void ConnectToServer(string alias, string color)
{
    _cts = new CancellationTokenSource();
    try
    {
        client = new TcpClient();
        client.Connect("服务器地址", 端口号);
        NetworkStream stream = client.GetStream();
        IFormatter formatter = new BinaryFormatter();

        // 发送初始消息
        MyMessage firstMsg = new MyMessage { Message = "MyNewClient" };
        formatter.Serialize(stream, firstMsg);

        // 启动异步接收任务
        Task.Run(() => ReceiveMessages(stream, formatter, _cts.Token), _cts.Token);

        // 启动连接检测线程
        Task.Run(() => CheckConnectionStatus(), _cts.Token);
    }
    catch (Exception ex)
    {
        // 处理异常
        _cts.Cancel();
    }
}

private void ReceiveMessages(NetworkStream stream, IFormatter formatter, CancellationToken token)
{
    try
    {
        while (!token.IsCancellationRequested)
        {
            if (stream.DataAvailable)
            {
                MyMessage receivedMsg = (MyMessage)formatter.Deserialize(stream);
                // 处理消息
            }
            else
            {
                // 没数据时短暂等待,避免空循环占用CPU
                Task.Delay(100, token).Wait(token);
            }
        }
    }
    catch (OperationCanceledException)
    {
        // 操作被取消,正常退出
    }
    finally
    {
        stream.Close();
        client.Close();
    }
}

private void CheckConnectionStatus()
{
    while (!_cts.Token.IsCancellationRequested)
    {
        Task.Delay(2000).Wait(_cts.Token);
        // 尝试发送空字节验证连接(client.Connected并非实时状态)
        try
        {
            stream.Write(new byte[1], 0, 0);
        }
        catch
        {
            // 发送失败,说明连接已断
            _cts.Cancel();
            break;
        }
    }
}

3. 实现心跳机制(最可靠)

静默断开的情况(比如服务器突然断电)下,client.Connected可能不会立即更新,这时候心跳机制是最靠谱的方案:客户端定期发送心跳包,服务器收到后回应,如果客户端在指定时间内没收到回应,就判定连接断开,主动终止操作:

private CancellationTokenSource _cts;
private int _heartbeatTimeoutCount = 0;

public void ConnectToServer(string alias, string color)
{
    _cts = new CancellationTokenSource();
    try
    {
        client = new TcpClient();
        client.Connect("服务器地址", 端口号);
        NetworkStream stream = client.GetStream();
        IFormatter formatter = new BinaryFormatter();

        // 发送初始消息
        MyMessage firstMsg = new MyMessage { Message = "MyNewClient" };
        formatter.Serialize(stream, firstMsg);

        // 启动接收任务和心跳任务
        Task.Run(() => ReceiveMessages(stream, formatter, _cts.Token), _cts.Token);
        Task.Run(() => SendHeartbeat(stream, formatter, _cts.Token), _cts.Token);
    }
    catch (Exception ex)
    {
        _cts.Cancel();
    }
}

private void ReceiveMessages(NetworkStream stream, IFormatter formatter, CancellationToken token)
{
    while (!token.IsCancellationRequested)
    {
        try
        {
            MyMessage receivedMsg = (MyMessage)formatter.Deserialize(stream);
            if (receivedMsg.Message == "HeartbeatResponse")
            {
                // 收到心跳回应,重置计数器
                _heartbeatTimeoutCount = 0;
            }
            else
            {
                // 处理业务消息
            }
        }
        catch (IOException)
        {
            break;
        }
    }
    _cts.Cancel();
    stream.Close();
    client.Close();
}

private void SendHeartbeat(NetworkStream stream, IFormatter formatter, CancellationToken token)
{
    while (!token.IsCancellationRequested)
    {
        Task.Delay(3000, token).Wait(token); // 每3秒发一次心跳
        try
        {
            formatter.Serialize(stream, new MyMessage { Message = "Heartbeat" });
            _heartbeatTimeoutCount++;
            if (_heartbeatTimeoutCount >= 3) // 连续3次没收到回应,判定断开
            {
                _cts.Cancel();
                break;
            }
        }
        catch
        {
            _cts.Cancel();
            break;
        }
    }
}

额外提醒

BinaryFormatter已经被微软标记为过时且不安全(存在序列化漏洞),建议换成更安全高效的序列化库,比如:

  • System.Text.Json:微软官方原生,支持异步和取消
  • Protobuf-net:高性能二进制序列化,适合跨平台通信
  • MessagePack:轻量级二进制序列化,速度快

这些库大多提供了原生的异步反序列化方法,配合取消令牌会更优雅。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 02:23:39