繁体   English   中英

Java InputStream 阻塞读

[英]Java InputStream blocking read

根据 java api, InputStream.read()被描述为:

如果由于到达 stream 的末尾而没有可用的字节,则返回值 -1。 此方法阻塞,直到输入数据可用、检测到 stream 的结尾或抛出异常。

我有一个while(true)循环进行读取,当没有任何内容通过 stream 发送时,我总是得到 -1。 这是预期的。

我的问题是 read() 什么时候会阻塞? 因为如果它没有得到任何数据,它会返回-1。 我希望阻塞读取要等到收到数据。 如果您已经到达输入 stream 的末尾,那么 read() 不应该只是等待数据而不是返回 -1 吗?

或者 read() 只有在有另一个线程访问 stream 并且您的 read() 无法访问 stream 时才会阻塞?


这引出了我的下一个问题。 我曾经有事件监听器(由我的库提供),它会在数据可用时通知我。 当我收到通知时,我会调用while((aByte = read()) > -1)存储字节。 当我在非常接近的时间内获得两个事件并且并未显示我的所有数据时,我感到很困惑。 似乎只显示第二个事件数据的尾部,并且缺少 rest。

我最终更改了我的代码,以便当我收到一个事件时,我调用了if(inputStream.available() > 0) while((aByte = read()) > -1)存储字节。 现在它工作正常,我的所有数据都显示出来了。

有人可以解释这种行为吗? 据说InputStream.available()返回在阻塞下一个调用者(stream?)之前可以读取的字节数。 即使我不使用.available(),我也希望读取第一个事件只会阻止读取第二个事件,但不会擦除或消耗太多 stream 数据。 为什么这样做会导致无法显示我的所有数据?

InputStream的某些实现的底层数据源可以发出信号,表明 ZF7B44CFFAFD5C52223D5498196C8A2E7BZ 已到达末尾,将不再发送数据。 在收到此信号之前,对此类 stream 的读取操作可能会阻塞。

例如,来自Socket套接字的InputStream将阻塞,而不是返回 EOF,直到收到设置了 FIN 标志的 TCP 数据包。 当从这样的 stream 接收到 EOF 时,您可以确信在该套接字上发送的所有数据都已可靠接收,您将无法读取更多数据。 (另一方面,如果阻塞读取导致异常,则可能会丢失一些数据。)

其他流,例如来自原始文件或串行端口的流,可能缺少类似的格式或协议,以表明没有更多数据可用。 当当前没有数据可用时,此类流可以立即返回 EOF (-1) 而不是阻塞。 但是,在没有这种格式或协议的情况下,您无法确定对方何时完成发送数据。


关于你的第二个问题,听起来你可能有比赛条件。 在没有看到有问题的代码的情况下,我猜测问题实际上在于您的“显示”方法。 也许第二个通知显示的尝试以某种方式破坏了第一个通知期间完成的工作。

如果它是 stream 的结尾,则返回 -1。 如果 stream 仍然打开(即套接字连接)但没有数据到达读取端(服务器速度慢,网络速度慢,...) read() 阻塞。

你不需要调用available()。 我很难理解您的通知设计,但是除了 read() 本身之外,您不需要任何调用。 方法 available() 只是为了方便。

好的,这有点乱,所以首先让我们澄清一下: InputStream.read()阻塞与多线程无关。 如果您有多个线程从同一个输入 stream 读取,并且您触发了两个彼此非常接近的事件 - 每个线程都试图消耗一个事件,那么您会得到损坏:第一个读取的线程将获得一些字节(可能全部字节),当第二个线程被调度时,它将读取字节的 rest。 如果您计划在多个线程中使用单个 IO stream,则始终在某些外部约束上使用synchronized() {}

其次,如果您可以从InputStream中读取直到获得 -1,然后等待并稍后再读取,那么您正在使用的 InputStream 实现已损坏! InputStream的合同明确规定InputStream.read()只应在没有更多数据要读取时返回 -1,因为 stream 的末尾已经到达并且不再有数据可用 - 就像你从文件,你到达终点(1)

“现在没有更多数据可用,请稍候,你会得到更多”的行为是read()阻塞并且在有一些数据可用(或抛出异常)之前不返回。

  1. 正如在对erickson(目前最热门)答案的讨论中所指出的那样, FileInputStream实现实际上可以读取“文件末尾”并在read()返回-1后提供更多数据 - 如果稍后将数据添加到文件中。 这是一个边缘情况,基本上是常见InputStream实现中唯一的这种情况(或者在最坏的情况下 - 非常非常罕见)。 如果您知道您使用FileInputStream期望从中读取的文件添加了其他数据(一个常见的示例是拖尾日志文件),则应该考虑到这一点,但否则它只是InputStream API 的缺陷,无论如何 - you'd be better off if you can stop using the java.io style blocking IO APIs and use java.nio non-blocking IO APIs.

默认情况下,提供的 RXTX InputStream 的行为是不兼容的。

您必须将接收阈值设置为 1 并禁用接收超时:

serialPort.enableReceiveThreshold(1);
serialPort.disableReceiveTimeout();

来源: RXTX 串行连接 - 阻塞 read() 的问题

是的。 不要放弃你的 stream 还是 Jbu。 我们在这里谈论的是串行通信,对于串行的东西,绝对可以/将在读取时返回-1。 但仍期待稍后的数据。 问题是大多数人习惯于处理 TCP/IP,除非 TCP/IP 断开连接,否则它应该始终返回 0..,然后是的。 -1 是有道理的,但是,对于 Serial,长时间没有数据流,也没有“HTTP Keep Alive”或 TCP/IP 心跳。 或者(在大多数情况下)没有硬件流控制,但链路是物理的。 并且仍然通过“铜”连接并且仍然完美地活着。

现在,如果他们所说的是正确的,即:串行应该在 -1 上关闭,那么为什么我们必须注意诸如 OnCTS、pmCarroerDetect、onDSR、onRingIndicator 等内容......哎呀,如果 0 意味着它在那里, -1 表示不是,然后拧所有这些检测功能::-)

您可能面临的问题可能在其他地方。

现在,到细节:

问:“似乎只显示第二个事件数据的尾部,并且缺少 rest。”

答:我猜你是在循环中,重复使用相同的 byte[] 缓冲区。 第一条消息进来,还没有显示在屏幕/日志/标准输出上(因为你在循环中),然后你读取第二条消息,替换缓冲区中的第一条消息数据。 再说一次,因为我猜你不会存储你读了多少,然后确保将你的存储缓冲区偏移上一个读取量。

问:“我最终更改了我的代码,以便当我得到一个事件时,我调用了 if(inputStream.available() > 0) while((aByte = read()) > -1) 来存储字节。”

A:好极了……这就是好东西。 现在,您的数据缓冲区位于 IF 语句中,您的第二条消息不会破坏您的第一条消息……好吧,实际上,它可能只是第一个位置的一条大(er)消息。 但现在,您将一口气读完所有内容,并保持数据完整。

C:“……比赛条件……”

A: 啊,好家伙抓住所有的替罪羊。 竞争条件..:,-) 是的,这可能是竞争条件。 事实上,它可能是,但是。 它也可能只是 RXTX 清除标志的方式。 清除“数据可用标志”可能不会像预期的那样快,例如? 任何人都知道 read VS readLine 与清除先前存储数据的缓冲区和重新设置事件标志之间的区别。 我也没有:.-) 我也找不到答案……但是……让我再啰嗦几句。 事件驱动编程仍然存在一些缺陷。 让我给你一个我最近不得不处理的真实世界的例子。

  • 我得到了一些 TCP/IP 数据,比如说 20 个字节。
  • 所以我收到了接收数据的 OnEvent。
  • 我什至在 20 个字节上开始我的“读取”。
  • 在我读完我的 20 个字节之前……我又得到了 10 个字节。
  • 然而,TCP/IP 看起来会通知我,哦,看到标志仍然是 SET,并且不会再次通知我。
  • 但是,我读完了我的 20 个字节(available() 说有 20 个)...
  • ...最后 10 个字节保留在 TCP/IP Q... 因为我没有收到通知。

看,通知被错过了,因为标志仍然设置......即使我已经开始读取字节。 如果我完成了字节,那么标志就会被清除,并且我会收到接下来 10 个字节的通知。

与你现在正在发生的事情完全相反。

所以是的,go 与 IF available()... 读取返回的数据长度。 然后,如果您偏执,请设置一个计时器并再次调用 available(),如果那里仍有数据,则不读取新数据。 如果 available() 返回 0(或 -1),则放松...坐等...等待下一个 OnEvent 通知。

InputStream只是一个抽象的 class ,不幸的是实现决定了会发生什么。

如果什么都没找到会发生什么:

  • Sockets (即SocketInputStream )将阻塞直到接收到数据(默认情况下)。 但是可以设置超时(参见: setSoTimeout ),然后read将阻塞 x 毫秒。 如果仍然没有收到任何内容,则会抛出SocketTimeoutException

    但无论是否超时,从SocketInputStream读取有时会导致-1 例如,当多个客户端同时连接到同一个host:port时,即使设备看起来已连接, read的结果也可能立即返回-1 (从不返回数据)。

  • Serialio通信将始终返回-1 您还可以设置超时(使用setTimeoutRx ), read将首先阻塞 x 毫秒,但如果未找到任何内容,结果仍将为-1 (备注:但有多个串行 io 类可用,行为可能取决于供应商。)

  • 文件(阅读器或流)将导致EOFException

为通用解决方案工作:

  • 如果将上述任何流包装在DataInputStream中,则可以使用readBytereadChar等方法。所有-1值都将转换为EOFException (PS:如果你执行了大量的小读取,那么最好先将它包装在BufferedInputStream中)
  • SocketTimeoutExceptionEOFException都扩展了IOException ,还有其他几个可能的IOException 只检查IOException来检测通信问题很方便。

另一个敏感话题是潮红。 就 sockets 而言, flush表示“立即发送”,但就 Serialio 而言,它表示“丢弃缓冲区”。

我想如果你使用 thread.sleep() 你可以收到整个数据 stream

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM