2013-07-21 45 views
0

我在java中創建了一個到服務器的套接字,並且在套接字連接之後它創建了一個可以訪問套接字輸入和輸出流的新線程,然後此線程在它們到來時阻止並處理輸入行in。在java中處理套接字

據我所知,當輸入流結束時,BufferedReader上的readln方法將返回null。這並不一定意味着套接字是封閉的,但它呢?這是什麼意思?所以我想要在套接字上運行close方法來很好地關閉它。

我也明白,readln方法可以拋出一個IOException,並且在套接字上調用close方法(如果它當前被阻塞)後拋出。什麼時候可以拋出?插入後仍然可以打開它,或者它會一直關閉並準備好進行垃圾回收等。

這是我現在的代碼,我真的不知道如何正確處理斷開連接。目前我認爲如果在套接字正在等待線路時調用disconnect方法,則可能最終陷入死鎖,因爲disconnect將在套接字上調用close。然後這將把IOException放在readLine上,然後這將導致該catch塊再次調用disconnect

public class SocketManager { 

    private Socket socket = null; 
    private PrintWriter out = null; 
    private BufferedReader in = null; 

    private String ip; 
    private int port; 

    private Object socketLock = new Object(); 

    public SocketManager(String ip, int port) { 
     this.ip = ip; 
     this.port = port; 
    } 

    public void connect() throws UnableToConnectException, AlreadyConnectedException { 
     synchronized(socketLock) { 
      if (socket == null || socket.isClosed()) { 
       throw (new AlreadyConnectedException()); 
      } 
      try { 
       socket = new Socket(ip, port); 
       out = new PrintWriter(socket.getOutputStream(), true); 
       in = new BufferedReader(new InputStreamReader(socket.getInputStream())); 
      } catch (IOException e) { 
       throw (new UnableToConnectException()); 
      } 
      new Thread(new SocketThread()).start(); 
     } 
    } 

    public void disconnect() throws NotConnectedException { 
     synchronized(socketLock) { 
      if (isConnected()) { 
       throw (new NotConnectedException()); 
      } 
      try { 
       socket.close(); 
      } catch (IOException e) {} 
     } 
    } 

    public boolean isConnected() { 
     synchronized(socketLock) { 
      return (socket != null && !socket.isClosed()); 
     } 
    } 

    private class SocketThread implements Runnable { 

     @Override 
     public void run() { 
      String inputLine = null; 
      try { 
       while((inputLine = in.readLine()) != null) { 
        // do stuff 
       } 
       if (isConnected()) { 
        try { 
         disconnect(); 
        } catch (NotConnectedException e) {} 
       } 
      } catch (IOException e) { 
       // try and disconnect (if not already disconnected) and end thread 
       if (isConnected()) { 
        try { 
         disconnect(); 
        } catch (NotConnectedException e1) {} 
       } 
      } 
     } 

    } 
} 

我基本上想知道達到以下的最佳方式:編寫一個連接到插座,並啓動一個單獨的線程監聽輸入連接方法

  • 編寫一個從套接字斷開並終止正在偵聽輸入的線程的斷開連接方法。
  • 處理與遠程套接字連接中斷的情況。

我已通讀java tutorial on sockets,但在我看來,它並沒有真正涵蓋這些細節。

謝謝!

回答

2

當我說它最終可能會陷入僵局時,我認爲我錯了。

會出現什麼情況是:

  1. 斷開()呼籲,同時in.readLine()阻擋
  2. socket.close()執行。
  3. in.readline()拋出IOException。

我當時認爲SocketThread中的異常處理程序會調用斷開連接,而斷開連接正在等待該異常完成。因爲它們都是不同的線程,所以disconnect()中的代碼會在SocketThread中捕獲異常的同時繼續執行。 SocketThread然後會調用disconnect(),但是必須等到disconnect()的第一個實例完成。然後disconnect()會再次執行,但會得到拋出的NotConnectedException,這將在SocketThread中被捕獲,並且什麼都不會發生。 SocketThread會退出,這就是想要的結果。

但是我已經調查了socket class和它也包含這些方法:

  • shutdownInput()
  • shutdownOutput()

shutdownInput()發送端EOF符號到輸入流的含義in.readline()返回null並且循環乾淨地退出。 shutdownOutput()發送TCP終止序列,通知服務器它已斷開連接。

socket.close()之前調用這兩個函數更有意義,因爲它意味着線程將很好地退出,而不是因拋出的異常而退出,這會導致更多開銷。

因此,這是修改後的代碼:

public class SocketManager { 

    private Socket socket = null; 
    private PrintWriter out = null; 
    private BufferedReader in = null; 

    private String ip; 
    private int port; 

    private Object socketLock = new Object(); 

    public SocketManager(String ip, int port) { 
     this.ip = ip; 
     this.port = port; 
    } 

    public void connect() throws UnableToConnectException, AlreadyConnectedException { 
     synchronized(socketLock) { 
      if (socket == null || socket.isClosed()) { 
       throw (new AlreadyConnectedException()); 
      } 
      try { 
       socket = new Socket(ip, port); 
       out = new PrintWriter(socket.getOutputStream(), true); 
       in = new BufferedReader(new InputStreamReader(socket.getInputStream())); 
      } catch (IOException e) { 
       throw (new UnableToConnectException()); 
      } 
      new Thread(new SocketThread()).start(); 
     } 
    } 

    public void disconnect() throws NotConnectedException { 
     synchronized(socketLock) { 
      if (isConnected()) { 
       throw (new NotConnectedException()); 
      } 
      try { 
       socket.shutdownInput(); 
      } catch (IOException e) {} 
      try { 
       socket.shutdownOutput(); 
      } catch (IOException e) {} 
      try { 
       socket.close(); 
      } catch (IOException e) {} 
     } 
    } 

    public boolean isConnected() { 
     synchronized(socketLock) { 
      return (socket != null && !socket.isClosed()); 
     } 
    } 

    private class SocketThread implements Runnable { 

     @Override 
     public void run() { 
      String inputLine = null; 
      try { 
       while((inputLine = in.readLine()) != null) { 
        // do stuff (probably in another thread) 
       } 

       // it will get here if socket.shutdownInput() has been called (in disconnect) 
       // or possibly when the server disconnects the clients 


       // if it is here as a result of socket.shutdownInput() in disconnect() 
       // then isConnected() will block until disconnect() finishes. 
       // then isConnected() will return false and the thread will terminate. 

       // if it ended up here because the server disconnected the client then 
       // isConnected() won't block and return true meaning that disconnect() 
       // will be called and the socket will be completely closed 

       if (isConnected()) { 
        try { 
         disconnect(); 
        } catch (NotConnectedException e) {} 
       } 
      } catch (IOException e) { 
       // try and disconnect (if not already disconnected) and end thread 
       if (isConnected()) { 
        try { 
         disconnect(); 
        } catch (NotConnectedException e1) {} 
       } 
      } 
     } 

    } 
} 
0

爲了確保與套接字關聯的所有資源都已關閉,您必須在完成對該套接字的工作時調用close()方法。 典型的IO異常處理模式是,你抓住它,然後盡最大努力清理所有調用close()方法。

所以你唯一要做的就是確保你在每個套接字的生命週期中調用close()。

0

你在正確的軌道上。我不會使用「readline」,只有原始讀取,並且「do stuff」 應限於構建接收數據的隊列。同樣,寫回復 應該是一個單獨的線程,用於清空要發送的數據隊列。

儘管套接字的完整性保證,東西會出錯,有時您會收到無意義的數據。在「讀」和「寫」下面有一堆東西,沒有系統是完美的或沒有錯誤的。在閱讀和書寫的層面添加自己的包含校驗和的包裝,這樣您就可以確定您正在接收準備發送的內容。