56

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

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

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

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

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


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

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

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

4

7 回答 7

48

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

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

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


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

于 2009-03-04T18:11:54.030 回答
19

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

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

于 2009-03-04T18:09:58.900 回答
14

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

其次,如果你可以从你的读取InputStream直到你得到-1,然后等待并稍后再读取,那么你正在使用的 InputStream 实现被破坏了!合同InputStream明确规定,InputStream.read()只有在没有更多数据要读取时才应该返回 -1,因为已经到达整个流的末尾并且不再有数据可用 - 比如当您从文件中读取并到达结尾。

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

于 2009-03-04T18:17:23.750 回答
7

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

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

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

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

于 2012-04-23T15:16:17.143 回答
4

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

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

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

现在,到细节:

Q:“好像只显示了第二个事件的数据的尾部,其余的都不见了。”

A:我猜你是在一个循环中,重复使用相同的 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 个字节的通知。

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

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

于 2009-09-01T02:28:04.253 回答
2

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

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

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

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

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

  • 文件(阅读器或流)将产生一个EOFException.

为通用解决方案工作:

  • 如果将上述任何流包装在 a 中DataInputStream,则可以使用readByte,readChar等方法。所有-1值都转换为EOFException. (PS:如果您执行大量小读取,那么最好将其包装在BufferedInputStream第一个中)
  • SocketTimeoutExceptionEOFExceptionextend都有IOException,还有其他几种可能IOException的。只检查IOException' 来检测通信问题很方便。

另一个敏感话题是潮红。flush就套接字而言意味着“现在发送”,但就 Serialio 而言,它意味着“丢弃缓冲区”。

于 2015-05-05T09:08:45.420 回答
-7

我认为如果你使用 thread.sleep() 你可以接收整个数据流

于 2010-11-23T15:56:56.113 回答