【问题标题】:Read from TcpClient.GetStream() without knowing the length在不知道长度的情况下从 TcpClient.GetStream() 读取
【发布时间】:2017-01-12 07:17:20
【问题描述】:

我正在研究基于 tcp 的通信协议。我所知 有很多方法可以确定何时结束阅读。

  1. 在消息结束时关闭连接
  2. 将消息的长度放在数据本身之前
  3. 使用分隔符;一些在正常数据中永远不会出现的值(或者总是以某种方式被转义)

通常我会尝试通过 WiFi 网络发送文件(可能不稳定且速度慢)

  • RSA 和 AES 通信的原因我不喜欢每次都关闭连接(不能使用 1
  • 这是一个大文件,我无法预测它的长度,所以我无法采取行动 as 方法(不能使用 2
  • 读取时检查特殊内容,写入时转义需要大量过程(不能使用 3
  • 这个方法应该兼容c#和java。

你有什么建议?


更一般的问题:

How to identify end of InputStream in java

C# - TcpClient - Detecting end of stream?

更多信息

我正在编写一个 TCP 客户端服务器通信

首先服务器生成并发送一个RSA公共代码给客户端。

然后客户端会生成 AES(key,IV) 并使用 RSA 加密发回。

到这里一切都很好。

但是我想通过这个网络发送一个文件。这是我当前的数据包 EncryptUsingAES(new AES.IV(16 byte) +file.content(any size))

在服务器中,我无法捕获客户端发送的所有数据。所以我需要知道要读取多少数据 (TcpClient.GetStream().read(buffer , 0 , buffersize) ) 当前代码:

List<byte> message = new List<byte>();
    int bytes = -1;
    do
    {
        byte[] buffer = new byte[bufferrSize];
        bytes = stream.Read(buffer, 0, bufferrSize);
        if (bytes > 0)
        {
            byte[] tmp = new byte[bytes];
            Array.Copy(buffer, tmp, bytes);
            message.AddRange(tmp);
        }
    } while (bytes == bufferrSize);

【问题讨论】:

  • “这是一个大文件,我无法预测它的长度,所以我不能作为方法” - 不要一次发送整个文件,读取几 kB每次并在发送数据包之前插入长度。您必须至少能够知道一定数量的字节,否则您正在尝试的内容几乎是不可能的,不管是否有长度前缀。
  • @VisualVincent 是的,您有权分块阅读,但我也使用 AES,因此很难确定总长度是多少。 AES 添加一些填充取决于模型和配置。并且不同的缓冲区大小使其更加复杂。
  • 插入长度之后你已经执行了AES加密,所以你发送加密消息的长度。忽略使用缓冲区以外的任何缓冲区来读取整个消息(“数据包”)。
  • 让你的结构看起来像这样:[Encrypted msg length][Encrypted data].
  • 这是@VisualVincent 我无法对大文件执行 AES 的棘手部分,它既耗时又需要内存或硬盘。 2)我想使用动态缓冲区大小导致性能

标签: java c# tcp stream tcpclient


【解决方案1】:

你的第二种方法是最好的。用数据包的长度为每个数据包添加前缀将创建一个可靠的消息帧协议,如果正确完成,该协议将确保即使以您发送的相同大小接收所有数据(即,没有部分数据或数据集中在一起)。

  • 推荐的数据包结构:

    [Data length (4 bytes)][Header (1 byte)][Data (?? bytes)]
    

    - 所讨论的标头是一个单字节,您可以使用它来指示这是什么类型的数据包,以便端点知道如何处理它。


发送文件

文件的发送者在 90% 的情况下都知道它将要发送的数据量(毕竟,它通常将文件存储在本地),这意味着将知道已经发送了多少文件没有问题。

我使用和推荐的方法是首先发送一个“信息包”,它向端点解释它将要接收一个文件以及该文件包含多少字节。之后,您开始发送实际数据 - 最好以块的形式发送,因为一次处理整个文件效率低下(至少如果它是一个大文件)。

  • 始终跟踪到目前为止您收到的文件的字节数。通过这样做,接收方可以自动判断它何时收到了整个文件。
  • 一次发送几千字节的文件(我使用 8192 字节 = 8 kB 作为文件缓冲区)。这样您就不必将整个文件读入内存,也不必同时对其进行加密。


加密数据

处理加密不会有问题。如果您使用长度前缀,只需加密数据本身并保持数据长度标题不变。然后必须根据加密数据的大小生成数据长度标头,如下所示:

  1. 加密数据。
  2. 获取加密数据的长度。
  3. 产生以下数据包:

    [Encrypted data length][Encrypted data]
    

    (如果需要,在其中插入一个标头字节)


接收加密文件

接收加密文件并知道何时收到所有内容实际上并不难。假设您使用上述方法发送文件,您只需:

  1. 接收加密数据包→解密。
  2. 获取解密数据的长度。
  3. 增加一个变量来跟踪接收到的文件字节的数量。
  4. 如果收到的金额等于预期金额:关闭文件。


其他资源/参考

您可以参考我之前写的关于 TCP 长度前缀消息帧的两个答案:

【讨论】:

  • 我从不严格跟踪解密包而不是加密包。谢谢
  • @MahdiRafatjah:没问题!祝你的项目好运!
【解决方案2】:

最简单的方法是使用您的#2。如果您无法预测消息长度,请缓冲最多一定数量的字节(例如 1 KiB 或类似的内容),并为每个块插入一个长度标头,而不是为整个消息添加一次前缀。

【讨论】:

  • 那么我怎么知道什么时候没有更多的块了?我想最好使用msdn.microsoft.com/en-us/library/…中的networkstream@
  • 最后一个块是唯一一个长度小于 1 KiB 的块。
  • 是否有可能由于网络速度低,我收到了一半的一些块?例如 2048 字节块可能是 2000 字节和 48 字节块??
  • 您的数据块是 TCP 有效负载,保证与原始数据块相同(通过校验和、重新排序等)。
  • 很高兴知道。那么我也可以在消息末尾使用固定长度的固定数据块吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-07
  • 1970-01-01
  • 2021-11-09
  • 2019-05-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多