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
相关产品推荐
相关产品推荐

