2010-03-16 119 views
5

我有以下問題。我的客戶端程序監視本地網絡中服務器的可用性(使用Bonjour,但它不會反彈)。只要服務器被客戶端應用程序「注意到」,客戶端就會嘗試創建一個套接字:Socket(serverIP,serverPort);我應該關閉兩端的套接字嗎?

在某些時候客戶端可能會丟失服務器(Bonjour說服務器在網絡中不再可見)。所以,客戶決定使用套接字close,因爲它不再有效。

在某些時候服務器再次出現。因此,客戶端嘗試創建與此服務器關聯的新套接字。但!服務器可以拒絕創建此套接字,因爲它(服務器)已經有一個與客戶端IP和客戶端端口關聯的套接字。發生這種情況是因爲客戶端關閉了套接字,而不是由服務器關閉。它能發生嗎?如果是這樣的話,這個問題如何解決?

那麼,我知道客戶端不太可能嘗試從同一端口(客戶端端口)連接到服務器,因爲客戶端隨機選擇其端口。但它仍然會發生(偶然)。對?

回答

1

簡答:是的,你應該關閉兩端的插座。

雖然答案很簡單,但實際上,如果您不在客戶端/服務器協議中構建一些ACK/NACK方案,可能很難檢測到對等方已停止響應。

即使您的協議確認您的處理線程可能正在等待ACK永遠不會來自客戶端,反之亦然。

如果您使用阻塞I/O,我將首先在套接字上設置讀超時。不幸的是,如果對方變得沒有響應,那麼寫入沒有相應的超時。 我在我們的環境中發現了一個鈍器,它的價值在於通過java.nio方法創建阻塞套接字,然後以可配置的間隔中斷處理線程。

中斷處理線程將關閉套接字,但是如果選擇足夠大的超時時間,您將知道存在問題。我們選擇這種方法是因爲應用程序最初是用阻塞I/O編寫的,而將其轉換爲非阻塞的成本非常高。

但是,對於非阻塞I/O,您可以在更細粒度的時間間隔內檢查連接狀態,並更加智能地對緩慢/無響應的連接作出反應。

雖然非阻塞I/O需要較高的前期投資,但我認爲它在後期的可靠性和吞吐量方面將帶來更好的回報。

1

客戶端操作系統將不會盡快將相同的端口分配給新的套接字。有幾種機制可以阻止它。其中之一是TIME_WAIT狀態,用於在連接關閉後保留一段時間的端口。

我不會擔心它。

如果您確實需要檢測斷開連接,則必須實施由客戶端和服務器啓動的ping/pong協議。

2

是的,一旦檢測到故障,請關閉插座。

如果未正確關閉,套接字將被「卡住」在「close_wait」中。 即使套接字已關閉,它的狀態也會在短時間內處於time_wait狀態。

但是,如果您將應用程序設計爲對每個新連接使用不同的本地端口,則不需要等待舊套接字關閉。

(如當時你正在創建一個完全地不同的插座,因爲插座被遠程IP,遠程端口,本地IP和本地端口識別。)

+1

「如果設計使用不同的本地端口爲每個新連接的應用程序」 至於這就是自動發生的,沒有必要爲它設計的。避免在構建套接字時完全指定本地端口。 – EJP 2010-03-17 00:42:21

+0

哦,是的,我的意思是,不應該設計一個應用程序來依賴於來自哪個端口的請求。 – KarlP 2010-03-17 08:06:47

1

這聽起來像您的客戶端檢測連接丟失到服務器(使用Bonjour),但您沒有相應的功能在另一個方向。

當然,你也希望服務器端的非活動連接有一些超時,否則死連接將永遠停留。除了你提到的潛在的IP地址/端口#衝突的問題之外,還有一個事實,即死連接消耗操作系統和應用程序資源(例如打開的文件描述符)

相反,你可能也想考慮不是當Bonjour表示該服務不再可見時,也很積極地從客戶端關閉連接。如果您處於無線場景中,暫時性的連接丟失並不罕見,並且在連接恢復後(假設客戶端仍具有相同的IP地址),TCP連接可能保持打開並且有效。最佳策略取決於你在談論什麼樣的連接。如果它是一個相對無狀態的連接,丟棄連接和重試的成本很低(如HTTP),那麼在出現問題的第一個標誌時拋出連接是有意義的。但是,如果它是一個具有重要用戶狀態的長期連接(如SSH登錄會話),則更努力地保持連接處於活動狀態是合理的。

2

爲什麼這是不可能發生的(注意,客戶端強制使用相同的本地端口在其連接)的快速/骯髒的例證:

public class Server{ 

public static void main(String[] args) throws Exception { 
    new Thread(){ 
     java.net.ServerSocket server = new java.net.ServerSocket(12345); 
     java.util.ArrayList<java.net.Socket> l = new java.util.ArrayList<java.net.Socket>(); 
     public void run() { 
      try{ 
      while(true){ 
       java.net.Socket client = server.accept(); 
       System.out.println("Connection Accepted: S: "+client.getLocalPort()+", C: "+client.getPort()); 
       l.add(client); 
      } 
      }catch(Exception e){e.printStackTrace();} 
     } 
    }.start(); 
} 

和客戶端(的東西有效替代服務器地址):

import java.net.InetAddress; 
import java.net.Socket; 


public class SocketTest { 
    public static void main(String[] args) throws Exception { 
     InetAddress server = InetAddress.getByName("192.168.0.256"); 
     InetAddress localhost = InetAddress.getLocalHost(); 
     Socket s = new Socket(server, 12345, localhost, 54321); 
     System.out.println("Client created socket"); 
     s.close(); 
     s = null; 
     System.gc(); 
     System.gc(); 
     Thread.sleep(1000); 
     s = new Socket(server, 12345, localhost, 54321); 
     System.out.println("Client created second socket"); 
     s.close(); 
     System.exit(55); 
    } 
} 

如果你啓動服務器,然後嘗試運行客戶端的第一個連接會成功,但第二個將失敗,「java.net.BindException:已使用的地址:連接」

-1

如果僅在阻塞套接字的情況下關閉服務器套接字,則客戶端套接字將被關閉,但反之亦然。

否則它會在兩端都更好。因爲套接字對你來說是一個沉重的系統。它將永遠使用系統的本地端口和遠程端口。

感謝 蘇尼爾·庫馬爾Sahoo