2010-01-08 175 views
13

檢測套接字是否被丟棄的最合適的方法是什麼?或者一個數據包是否確實發送了?Java套接字和丟棄的連接

我有一個庫,用於通過Apple gatways向iPhone發送Apple推送通知(available on GitHub)。客戶需要打開一個套接字併發送每條消息的二進制表示;但不幸的是,蘋果公司並沒有回覆任何承認。該連接也可以重複使用以發送多條消息。我正在使用簡單的Java Socket連接。相關代碼如下:

Socket socket = socket(); // returns an reused open socket, or a new one 
socket.getOutputStream().write(m.marshall()); 
socket.getOutputStream().flush(); 
logger.debug("Message \"{}\" sent", m); 

在某些情況下,如果在發送消息時或之前發生連接斷開; Socket.getOutputStream().write()成功完成。我預計這是由於TCP窗口尚未耗盡。

有沒有一種方法可以確定數據包是否確實存在於網絡中?我嘗試了以下兩種解決方案:

  1. 插入額外的socket.getInputStream().read()操作,其中250ms超時。這會強制在連接斷開時失敗的讀取操作,但在250毫秒內掛起。

  2. 將TCP發送緩衝區大小(例如Socket.setSendBufferSize())設置爲消息二進制大小。

這兩種方法都能正常工作,但會顯着降低服務質量;吞吐量從100條/秒到最多約10條/秒。

有什麼建議嗎?

更新:

由多個答案質疑所描述的可能性的挑戰。我構建了我描述的行爲的「單元」測試。請查看Gist 273786的單位案例。

兩個單元測試都有兩個線程,一個服務器和一個客戶端。服務器關閉,而客戶端正在發送無拋出IOException異常的數據。這裏是主要的方法:

public static void main(String[] args) throws Throwable { 
    final int PORT = 8005; 
    final int FIRST_BUF_SIZE = 5; 

    final Throwable[] errors = new Throwable[1]; 
    final Semaphore serverClosing = new Semaphore(0); 
    final Semaphore messageFlushed = new Semaphore(0); 

    class ServerThread extends Thread { 
     public void run() { 
      try { 
       ServerSocket ssocket = new ServerSocket(PORT); 
       Socket socket = ssocket.accept(); 
       InputStream s = socket.getInputStream(); 
       s.read(new byte[FIRST_BUF_SIZE]); 

       messageFlushed.acquire(); 

       socket.close(); 
       ssocket.close(); 
       System.out.println("Closed socket"); 

       serverClosing.release(); 
      } catch (Throwable e) { 
       errors[0] = e; 
      } 
     } 
    } 

    class ClientThread extends Thread { 
     public void run() { 
      try { 
       Socket socket = new Socket("localhost", PORT); 
       OutputStream st = socket.getOutputStream(); 
       st.write(new byte[FIRST_BUF_SIZE]); 
       st.flush(); 

       messageFlushed.release(); 
       serverClosing.acquire(1); 

       System.out.println("writing new packets"); 

       // sending more packets while server already 
       // closed connection 
       st.write(32); 
       st.flush(); 
       st.close(); 

       System.out.println("Sent"); 
      } catch (Throwable e) { 
       errors[0] = e; 
      } 
     } 
    } 

    Thread thread1 = new ServerThread(); 
    Thread thread2 = new ClientThread(); 

    thread1.start(); 
    thread2.start(); 

    thread1.join(); 
    thread2.join(); 

    if (errors[0] != null) 
     throw errors[0]; 
    System.out.println("Run without any errors"); 
} 

[順便說一句,我也有一個併發測試庫,這使得安裝好一點,更清晰。在gist上也檢出樣品]。

在運行時我得到以下輸出:

+0

已更新的答案。 – 2010-01-11 01:11:23

+0

正如我在我的回答中所述,如果您想確保沒有錯誤,您必須執行正常的連接終止。執行shutdownOutput(),然後讀取,直到您收到EOF,然後關閉套接字。 優雅的連接終止本質上是從對等方接收到確認,它收到你確定,這正是你想要的。 – 2010-01-11 04:06:08

回答

17

這沒有多大幫助你,但技術上對你提出的解決方案是不正確的。 OutputStream.flush()和其他任何你可以想到的API調用都不會做你所需要的。

確定對方是否接收到數據包的唯一便攜式可靠方法是等待來自對等方的確認。此確認可以是實際響應,也可以是正常的套接字關閉。故事結束 - 實際上沒有其他方式,這不是Java特有的 - 它是基本的網絡編程。

如果這不是一個持久連接 - 也就是說,如果你只是發送一些東西然後關閉連接 - 你這樣做的方式就是捕獲所有IOExceptions(它們中的任何一個表示錯誤),並且執行一個優雅的套接字關機:

1. socket.shutdownOutput(); 
2. wait for inputStream.read() to return -1, indicating the peer has also shutdown its socket 
+1

完全正確。如果應用層沒有給你任何確認,那麼協議就是一個不可靠的協議,你必須忍受這一點(只知道數據包發佈到網絡上並沒有告訴你它實際上畢竟,我們已經到了另一端)。 – caf 2010-01-11 03:39:17

2

如果您使用TCP/IP協議向蘋果發送信息,您必須收到確認。但是你說:

蘋果不返回任何 確認任何

您的意思是什麼? TCP/IP保證傳送,因此接收方必須確認收到。但是,不能保證交付何時發生。

如果您向Apple發送通知,並且在收到ACK之前斷開連接,則無法分辨您是否成功,因此您只需再次發送。如果兩次推送相同的信息是有問題的或者設備沒有正確處理,則存在問題。解決方案是修復重複推送通知的設備處理:在推送方面你無能爲力。

@評論澄清/問題

好的。你理解的第一部分是你對第二部分的回答。只有已收到ACKS的數據包才能正確發送和接收。我敢肯定,我們可以想到一些非常複雜的方案來跟蹤每個單獨的數據包,但TCP會將這個圖層抽象出來併爲您處理。在你的最後,你只需要處理可能發生的大量失敗(在Java中,如果任何這些失敗發生了異常)。如果沒有異常,您剛剛嘗試發送的數據將通過TCP/IP協議發送。

有沒有數據看似「發送」但不能保證在沒有異常引發的情況下收到的情況?答案應該是否定的。

@實例

不錯的例子,這很好地闡明瞭一些事情。我會認爲會引發錯誤。在該示例中,第二次寫入時發生錯誤,但不是第一次寫入。這是一個有趣的行爲...我無法找到許多信息來解釋它爲什麼會這樣。但它解釋了爲什麼我們必須開發自己的應用程序級協議來驗證交付。

看起來你是正確的,沒有協議的確認他們不能保證蘋果設備將收到通知。蘋果也只是排隊的最後一條消息。稍微看一下服務,我能夠確定這項服務對客戶來說更方便,但不能用於保證服務,並且必須與其他方法結合使用。我從以下來源閱讀。

http://blog.boxedice.com/2009/07/10/how-to-build-an-apple-push-notification-provider-server-tutorial/

好像答案是否定的,你是否能確切地回答。您可以使用像Wireshark這樣的數據包嗅探器來判斷它是否被髮送,但由於服務的性質,這仍然不能保證它被接收併發送到設備。

+0

我的意思是Apple不會在TCP ACK之後返回任何特定的協議確認。我也不知道如何分辨哪些數據包正確發送,哪些數據包沒有正確發送。 – notnoop 2010-01-10 05:27:03

+0

感謝您的更新。我意識到我應該把這個問題的重點放在TCP上,而不是討論Apple的服務。我想我現在會放棄掉線連接檢測支持。 – notnoop 2010-01-11 02:33:55

3

與掉線的麻煩後,我搬到我的代碼use the enhanced format,這幾乎意味着你改變你的包看起來像這樣:

enter image description here

這樣,蘋果將不會下降,如果一個連接發生錯誤,但會將反饋代碼寫入套接字。