NetworkStream Read()过慢问题:7.5MB文件耗时约9分钟
问题分析与解决方案
首先,你的传输速度慢的核心问题出在接收端的逻辑错误,而不是缓冲区大小本身。不管是最初的一次性读取还是后来的循环读取,都没有正确处理TCP流的特性——TCP是字节流,NetworkStream.Read()方法不会保证一次性读取你请求的所有字节,它返回的是实际读取到的字节数,而你的代码完全忽略了这个返回值,导致数据丢失、重复读取无效内容,最终拖慢了整个传输过程。
你的接收代码存在的关键问题:
- 忽略
Read()的返回值:你一直假设每次都能读满整个缓冲区,但实际网络传输中,每次读取的字节数是不确定的,忽略返回值会导致你无法跟踪实际接收的数据量。 - 没有累加接收的数据:第二个版本的循环中,每次读取都会覆盖
tmp_bytesFrom,之前接收的内容全部丢失,永远凑不齐完整的文件数据+结束标记。 - 结束标记检测逻辑错误:只检测结束标记的第一个字节,且没有在已接收的完整数据中查找完整的结束序列,可能导致提前终止循环或者永远无法找到标记。
修正后的接收代码
下面是修复后的完整接收逻辑,解决了上述所有问题:
private static int CHUNK_SIZE = 4096; // 使用合理的缓冲区大小,4KB是常见的最优值 public static byte[] ReceiveBytes(TcpClient clientSocket) { Debug.WriteLine($"[{DateTime.Now:G}] - ReceiveBytes() started."); clientSocket.NoDelay = true; NetworkStream networkStream = clientSocket.GetStream(); var receivedData = new List<byte>(); // 用List来动态累加接收的数据 byte[] buffer = new byte[CHUNK_SIZE]; int bytesRead; // 循环读取数据,直到找到完整的结束标记 while (true) { bytesRead = networkStream.Read(buffer, 0, buffer.Length); if (bytesRead == 0) { // 连接已关闭,没有更多数据 throw new IOException("Connection closed before receiving complete data."); } // 将本次读取的有效数据添加到累加列表中 receivedData.AddRange(buffer.Take(bytesRead)); // 检查已接收的数据中是否包含完整的结束标记 int endMarkerIndex = FindEndMarker(receivedData, ProgramState.end_networkdata_bytes); if (endMarkerIndex != -1) { // 截取结束标记之前的数据 byte[] rawData = receivedData.Take(endMarkerIndex).ToArray(); // 解码后返回 byte[] decodedData = EncoderDecoder.Decoder(rawData); Debug.WriteLine($"[{DateTime.Now:G}] - ReceiveBytes() completed. Received raw bytes: {rawData.Length}, decoded bytes: {decodedData.Length}"); return decodedData; } } } // 辅助方法:在字节列表中查找完整的结束标记序列 private static int FindEndMarker(List<byte> data, byte[] endMarker) { if (data.Count < endMarker.Length) return -1; // 遍历所有可能的起始位置,检查是否匹配完整的结束标记 for (int i = 0; i <= data.Count - endMarker.Length; i++) { bool match = true; for (int j = 0; j < endMarker.Length; j++) { if (data[i + j] != endMarker[j]) { match = false; break; } } if (match) { return i; } } return -1; }
关键改进点说明:
- 跟踪实际读取的字节数:每次调用
networkStream.Read()后,用返回的bytesRead来确定本次读取的有效数据长度,避免处理缓冲区中未填充的无效字节。 - 动态累加数据:使用
List<byte>来逐步累加所有接收的数据,不会丢失之前读取的内容。 - 正确检测结束标记:通过辅助方法
FindEndMarker在已接收的完整数据中查找完整的结束标记序列,而不是单个字节,同时考虑了标记跨缓冲区的情况。 - 合理的缓冲区大小:使用4KB(4096字节)的缓冲区,这是网络IO中经过优化的常见大小,过大或过小的缓冲区都会影响效率。
额外建议
- 发送端可以保持现有逻辑,但建议同样检查
serverStream.Write()的返回值,确保所有字节都被发送(虽然TCP会保证可靠传输,但检查返回值是良好的编程习惯)。 - 如果传输大文件,还可以考虑在发送前先发送文件的长度信息,这样接收端可以知道需要接收多少数据,不需要依赖结束标记,进一步提升可靠性和效率。
内容的提问来源于stack exchange,提问作者Bủi Văn Thủ
相关产品推荐
相关产品推荐

