[C#] NetworkStream.Write()存在严重bug

本文揭示了.NET中NetworkStream.Write()方法存在的问题:当抛出IOException时,已发送字节数未知,导致数据重复或丢失。建议使用BeginWrite()替代。

NetworkStream.Write()方法实际上是不可用的,因为它无法保证数据的连续性。


先给大家看一段代码:

//cln is an instance of TcpClient
NetworkStream st = cln.GetStream();
st.Write(tosend, 0, tosend.Length);

这属于NetworkStream.Write()的标准调用方法。可是在catch到IOException之后,我们就会遇上大麻烦。
MSDN上没有对NetworkStream抛出的IOException做详细说明,因此我们只能参考它的基类Stream抛出的IOException的说明,MSDN上是这样说的:If an exception occurs, the position within the stream remains unchanged.

听起来很不错是不是?抓到IOException之后,再重新发送就可以了。可事实并不是这样的。事实是:当NetworkStream.Write()抛出一个IOException的时候,谁也不知道到底有多少个字节已经被发送出去了!
我们用了两天的时间来追踪数据重复的问题,最终的结果让人非常恼火。微软的NetworkStream没有做到他在MSDN里面承诺的“If an exception occurs, the position within the stream remains unchanged.”。

没办法,只能用下面这个方法凑合了:

NetworkStream st = cln.GetStream();
IAsyncResult ar = st.BeginWrite(tosend, 0, tosend.Length, null, null);
if(!ar.AsyncWaitHandle.WaitOne())
{
ErrorLog.WriteError("fail to wait");
}
st.EndWrite(ar);

至少目前看来NetworkStream.BeginWrite()抛出的异常还是比较准确的。
#region 发送请求 /// <summary> /// 发送 Modbus 请求并等待响应 /// </summary> /// <param name="request">请求报文</param> /// <param name="expectedResponseLength">期望响应长度(用于校验)</param> /// <returns>响应字节数组,失败返回 null</returns> private async Task<byte[]> SendRequestAsync(byte[] request, int expectedResponseLength) { try { // 假设 tcpClient 已连接,networkStream = tcpClient.GetStream() if (stream == null || !stream.CanWrite) return null; // 设置事务ID(大端字节序) ushort transactionId = GenerateTransactionId(); // 自增或随机生成 request[0] = (byte)(transactionId >> 8); request[1] = (byte)(transactionId & 0xFF); // 发送请求 await stream.WriteAsync(request, 0, request.Length).ConfigureAwait(false); // 读取响应头(至少 9 字节 MBAP + PDU header) byte[] header = new byte[9]; int read = await ReadExactAsync(stream, header, 0, 9).ConfigureAwait(false); if (read != 9) return null; // 解析后续长度(第5-6字节) int pduLength = (header[4] << 8) | header[5]; // 后续数据长度 int totalResponseLength = 6 + pduLength; // MBAP(6) + PDU if (totalResponseLength > expectedResponseLength) { // 扩展缓冲区以接收完整响应 byte[] fullResponse = new byte[totalResponseLength]; Array.Copy(header, 0, fullResponse, 0, 9); int remaining = totalResponseLength - 9; read = await ReadExactAsync(stream, fullResponse, 9, remaining).ConfigureAwait(false); if (read != remaining) return null; return fullResponse; } return header; // 如果已足够 } catch (Exception ex) { Console.WriteLine("通信异常: " + ex.Message); connected = false; return null; } } /// <summary> /// 确保异步读取指定数量的字节 /// </summary> private async Task<int> ReadExactAsync(NetworkStream stream, byte[] buffer, int offset, int count) { int totalRead = 0; while (totalRead < count) { int read = await stream.ReadAsync(buffer, offset + totalRead, count - totalRead).ConfigureAwait(false); if (read == 0) break; // 连接关闭 totalRead += read; } return totalRead; } #endregion排查下这段代码有问题吗
10-14
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值