2012-12-23 156 views
12

我有點不高興,因爲在嘗試不同的解決方案(thisthis和其他幾個)在幾個SO問題的答案中提到不同的解決方案後,我仍然無法設法檢測套接字斷開(通過拔下電纜) 。檢測套接字斷開?

我正在使用NIO非阻塞套接字,除了找不到檢測服務器斷開連接的方法之外,一切都很完美。

我有以下代碼:

while (true) { 
    handlePendingChanges(); 

    int selectedNum = selector.select(3000); 
    if (selectedNum > 0) { 
     SelectionKey key = null; 
     try { 
      Iterator<SelectionKey> keyIterator = selector.selelctedKeys().iterator(); 
      while (keyIterator.hasNext()) { 
       key = keyIterator.next(); 
       if (!key.isValid()) 
        continue; 

       System.out.println("key state: " + key.isReadable() + ", " + key.isWritable()); 

       if (key.isConnectable()) { 
        finishConnection(key); 
       } else if (key.isReadable()) { 
        onRead(key); 
       } else if (key.isWritable()) { 
        onWrite(key); 
       } 
      } 
     } catch (Exception e) { 
      e.printStackTrace(); 
      System.err.println("I am happy that I can catch some errors."); 
     } finally { 
      selector.selectedKeys().clear(); 
     } 
    } 
} 

當正在讀SocketChannels,我拔掉數據線,並Selector.select()開始旋轉並返回0,現在我已經沒有機會閱讀因爲主要讀取&的編寫代碼是由守衛if (selectedNum > 0),現在這是第一個混淆出來的我的頭,從this answer,據說當頻道壞了,select()會返回,而選擇關鍵信道將指示讀/寫,但它顯然不是這裏的情況下,該鍵沒有被選擇,select()仍然返回0

此外,從EJP's answer到一個類似的問題:

如果對等關閉套接字:

  • read()返回-1
  • 的readLine()返回null
  • 的readXXX()THR OWS EOFException類,任何其他X上

不在這裏要麼這樣,我試着註釋掉if (selectedNum > 0)使用selector.keys().iterator()讓所有的鑰匙,無論他們是否被選中,從這些密鑰讀取不回-1(0代替),寫入這些鍵不會得到EOFException拋出。我只注意到一件事,即使沒有選擇鍵,key.isReadable()返回true,而key.isWritable()返回false(我猜這可能是因爲我沒有註冊OP_WRITE的鍵)。

我的問題是爲什麼Java套接字的行爲是這樣或有什麼我做錯了?

+1

可能是操作系統不聲明連接斷開:您可能會重新插入電纜,理論上連接可能會恢復。 – MvG

+0

是的,有時當我插入電纜時,連接可能會恢復,有時'select()'只是一直返回0,我不得不手動取消鍵。 – neevek

回答

19

你已經發現你需要定時器和TCP連接上的心跳。

如果拔下網線,則TCP連接可能不會中斷。如果你沒有東西要發送,那麼TCP/IP協議棧沒有任何要發送的東西,它不知道某個電纜是否在某處丟失,或者對端電腦突然燃燒。那個TCP連接可以被認爲是開放的,直到你幾年後重新啓動服務器。

想想這樣; TCP連接如何知道另一端斷開網絡 - 它離開了網絡,所以它不能告訴你這個事實。

如果您拔掉進入服務器的電纜,有些系統可能會檢測到這種情況,有些則不會。如果你拔掉另一端的電纜,例如一個以太網交換機,不會被檢測到。

這就是爲什麼一個總是需要主管定時器(即例如發送心跳消息給對端,或關閉基於對給定的時間量沒有活動TCP連接)的TCP連接,

一個很便宜至少要避免TCP連接,即只能讀取數據(從不寫入數據)以保持連續數年的TCP連接,這是爲了在TCP套接字上啓用TCP keepalive - 請注意,TCP keepalive的默認超時時間通常爲2小時。

+0

你的解釋完全清除了我的困惑。但仍然有一件事我想知道,有時當我插上電纜時,連接恢復,有時不連接,爲什麼? – neevek

+1

@neevek可能發送端超時。發送端會檢測到另一端已經消失,因爲它沒有收到任何消息,所以在其他情況下,它將取決於在tcp堆棧超時連接之前是否重新插入電纜。 – nos

+0

一個人並不總是需要主管定時器。例如,HTTP是這個星球上最常用的應用程序協議,它沒有一個。讀寫超時和IOExceptions就足夠了。 – EJP

8

這些答案都不適用。第一個涉及連接中斷的情況,第二個(我的)涉及對等關閉連接的情況。

在TCP連接中,除非正在發送或接收數據,否則原則上沒有任何關於應該斷開連接的電纜,因爲TCP被故意設計爲在這類事情中是穩健的,並且肯定存在沒有什麼關於它應該看到本地應用程序,如同行關閉。

在TCP中檢測斷開連接的唯一方法是嘗試通過它發送數據,或者在適當的時間間隔(應用程序決策)之後將讀取超時解釋爲丟失的連接。

您還可以設置TCP保持活動狀態以啓用斷開連接的檢測,並且在某些系統中,您甚至可以控制每個套接字的超時時間。但不是通過Java,所以你堅持系統默認,這應該是兩個小時,除非它已被修改。

你的代碼應該在調用keyIterator.next()後調用keyIterator.remove()。

+0

嗨,@EJP,我知道你會來拯救,謝​​謝。 *您還可以設置TCP保持活動狀態以啓用對連接斷開的檢測*,在連接斷開檢測中,保持活動在此處扮演什麼角色?設置和不設置保持活動有什麼區別?至於'keyIterator.remove()',我已經在finally塊中使用了'selector.selectedKeys()。clear()'。 – neevek

+0

@neveek錯過了。 TCP keep-alive會不時發送一個數據包,需要響應的數據包,如果沒有到達(考慮重試和超時),連接將被視爲中斷:您將在下一個I/O。 – EJP

+0

我沒有實現一個自定義協議,我使用的是HTTP,所以如果一個數據包通過網絡發送,那麼這個數據包會被解釋爲HTTP頭部還是身體的一部分?如果我作爲客戶端收到保活包,我該如何處理? – neevek