【问题标题】:InputStream in.read() behaving differently than expectedInputStream in.read() 的行为与预期不同
【发布时间】:2015-07-18 17:21:21
【问题描述】:

我正在尝试使用 TCP 将文本文件传输到另一台服务器,但它的行为与预期不同。发送数据的代码是:

        System.out.println("sending file name...");
        String outputFileNameWithDelimiter = outputFileName + "\r\n"; //These 4 lines send the fileName with the delimiter
        byte[] fileNameData = outputFileNameWithDelimiter.getBytes("US-ASCII");
        outToCompression.write(fileNameData, 0, fileNameData.length);
        outToCompression.flush();

        System.out.println("sending content...");
        System.out.println(new String(buffer, dataBegin, dataEnd-dataBegin));
        outToCompression.write(buffer, dataBegin, dataEnd-dataBegin); //send the content
        outToCompression.flush();

        System.out.println("sending magic String...");
        byte[] magicStringData = "--------MagicStringCSE283Miami".getBytes("US-ASCII"); //sends the magic string to tell Compression server the data being sent is done
        outToCompression.write(magicStringData, 0, magicStringData.length);
        outToCompression.flush();

因为这是 TCP 并且您不能像在 UDP 中那样发送离散的数据包,所以我希望所有数据都在输入流中,我可以使用分隔符来分隔文件名、内容和结束字符串,然后每个 in.read() 只会给我下一个后续的数据量。

相反,这是我每次读取时获得的数据:

On the first in.read() byteBuffer appears to only have "fileName\r\n". 
On the second in.read() byteBuffer still has the same information. 
On the third in.read() byteBuffer now holds the content I sent. 
On the fourth in.read() byteBuffer holds the content I sent minus a few letters.
On the fifth in.read() I get the magicString + part of  the message.

我在每次从 Web 服务器发送时都会刷新,但输入流似乎无法实现可刷新。

谁能解释为什么会这样?

编辑: 这就是我读取内容的方式。基本上这是循环然后写入文件。

 in.read(byteBuffer, 0, BUFSIZE);

【问题讨论】:

  • 您能在客户端显示您是如何从in 读取数据的吗?我强烈怀疑您没有检查 in.read() 的返回值(实际从流中读取的字节数)。
  • 我不是,但为什么要检查这件事呢?那不是让我调试它吗(我觉得看到 in.read() 是问题是看到计数有用的),而不是解释它为什么会这样?现在发布代码。
  • 如果你知道的话,我不会发送一个魔术字符串而是提前发送长度。并且您应该将 tcp 视为流而不是数据包(即输入可能具有与接收到的读取不同的块)。

标签: java sockets networking inputstream


【解决方案1】:

当您从 InputStream 读取数据时,您将为其提供一个字节数组以写入(以及可选的偏移量和最大读取量)。 InputStream 不保证数组将被新数据填充。返回值是实际读入的字节数。

您的示例中发生的情况是这样的:

  • 第一个 TCP 数据包带有"fileName\r\n",被写入缓冲区,到目前为止一切正常。
  • 您再次调用read(),但下一个数据包尚未到达。 read() 将返回0,因为它不想在数据到达之前阻塞。所以缓冲区仍然包含"fileName\r\n"。编辑:正如所指出的,read() 总是阻塞,直到它读取至少一个字节。真的不知道为什么缓冲区没有改变。
  • 三读时,内容已到。
  • 内容的第一位被消息的第二部分覆盖,最后一位仍然包含旧消息的一部分(我认为这就是您的意思)。
  • 等等,你懂的

您需要检查返回值,等待数据到达,并且只使用最后一个read() 写入的缓冲区。

【讨论】:

  • InputStream.read() 阻塞,直到至少有一个字节可用或流结束或异常。它可以返回零的唯一方法是如果您提供零长度来读取。如果没有数据,它不会返回零。只有非阻塞通道读取才能做到这一点。
  • 至少在 oder java 中它可以返回 0 字节(尤其是当发送方发送 0 字节大小的段时)。我知道它在文档中发生了变化,但我不会依赖它(事实上,当您处理部分读取时,您也可以处理 0 次读取)。
  • @eckes 不,它不能。你误会了。自 1997 年以来我一直在阅读的文档中它没有改变,在旧版本的 Java 中也没有什么不同。
【解决方案2】:

如果您的期望是读取将填满缓冲区,或者接收对等方由单个 write() 发送的确切内容,那么您的期望是这里的错误,而不是 read(). 它没有指定为一次传输超过一个字节,并且无法保证保留写边界。

如果不将read() 的结果存储到变量中,就不可能编写正确的代码。

【讨论】:

  • @Eckes 仅当您提供零长度时。否则不可能,因为底层 recv() 也不会这样做。
  • hm 可能,我不记得我必须处理 0 时的情况。(但是 read() 的 POSIX 定义明确定义了 0 字节读取(至少在 STREAMS 上):"How read() handles zero-byte STREAMS messages is determined by the current read mode setting..."。(在Java 你不能发送那些。)
  • @eckes 您不能以任何方式在 TCP 中发送零长度段。是不可能的。 Ergo 你也收不到它们。您可以发送长度为零的数据报。
  • 是的,我猜那是一个 TLS 套接字。记不住细节:-/
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-11
  • 2013-10-14
  • 2021-09-25
  • 1970-01-01
  • 2019-08-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多