簡體   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