socket連接與多線程


  socket連接是Java中進行通信的基本方式,也是效率最高的方式,雖然他有http等讓是進行http請求,但是如果是進行tcp、下載等通信,還是使用socket更好。Java中封裝了非常完美的socket機制,使用也非常簡單。主要包括socket和serversocket。

  socket的使用非常簡單,主要包括的構造方法有:socket(),socket(string host,string port),socket(Inetaddress address,int port)等,非常明白了,通過傳入host和port進行socket的請求,當在創建相應的套接字實例的時候,會自動去對相應的ip和port進行連接,只有當連接成功,才表示相應的套接字建立成功,才可以進行相應的I/O操作。通過getOutputStream和getInputStream獲取相應的輸入輸出流,進行相應的I/O操作,但是有一個是比較特別的,getChannel用來獲取SocketChannel,他之所以特別是因為他屬於java.nio.channels下面的類,其繼承於java.nio.channels.SelectableChannel,就是說在進行nio非阻塞式的請求連接時,他非常有用,具體參見http://www.cnblogs.com/likwo/archive/2010/06/29/1767814.html。可能有些人會問,對於可以通過設置服務器連接的timeout來防止過多的阻塞,但是如果對於超過timeout,socket一般是拋出超時異常,這樣就算對異常進行了處理,也將會從新建立socket連接,浪費消費 重建的資源。例如QQ聊天,當你打開一個聊天面板,很久不說話的時候,並不會自動為你斷開socket連接,而是一直處於阻塞狀態,直到你發送了新的信息,再進行處理,因此nio的阻塞方式會更好些。

  對於serversocket也比較簡單,常用的只有四個構造函數:

l  ServerSocket()throws IOException
l  ServerSocket(int port) throws IOException
l  ServerSocket(int port, int backlog) throws IOException
l  ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException  

分別對這幾個構造進行簡單的解釋:

第一個無參構造,只是創建一個serversocket實例,但是不進行任何端口的監聽,你還必須通過bind方法進行端口的綁定,好處就是在綁定之前可以進行相應屬性的設置,例如so_reuseaddress等;

第二個構造函數需要一個端口(1024-65535),一般不使用0-1023之間的,這個屬於系統占用的預留端口,但是如果你傳入的端口號為0,則會默認使用匿名端口,就是系統隨機分配一個端口,進行暫時通信,這個匿名方式,一般情況下不使用;

第三個構造函數需要一個端口,和一個backlog(監聽對列大小),serversocket進行某端口的監聽,當有多個連接請求是,每個請求默認都會放入一個請求隊列里,如果你沒有設置這個值,則默認為操作系統的值,根據不同的系統有所不同,例如40等,有幾種情況,這個值將會失效:大於操作系統默認值|小於等於0|沒有設置。如果設置了,而沒有及時對隊列進行處理,則會報ConnectException異常;

第四個構造函數除了具有端口、隊列大小外,還具有一個參數是ip地址,就是進行相應ip地址的綁定。當然,這個進行的是服務器ip地址的綁定,不會限制客戶端的ip訪問。當一台服務器存在多個網卡的時候,就需要通過這個參數來設置客戶端訪問的ip。

服務器socket的關閉,通過使用close進行關閉,使用isclosed進行判斷,還可以進行端口的綁定判斷。

對於服務器close方法,需要有一點進行說明:調用close方法之后,操作系統並不會立即進行端口的釋放,依舊會對舊端口占用一段時間,以防止客戶端發送的數據有延遲現象。因此有時候,就算你進行了close方法的調用,進行了端口的釋放,但是如果你立即進行同一個端口的連接時,依舊會包端口占用異常,這個是可以理解的。

serversocket通過使用accept方法進行客戶端請求的處理,每當請求隊列里有客戶端請求時,serversocket就會從隊列頂端取一個socket請求進行處理,生成一個socket來負責與客戶端通信。如果一個時間只能處理一個socket,當有多個客戶端請求時,則必須要排隊處理,等待所有前面的socket處理完,這是個極其痛苦,並且不合理的過程。因此,引入了線程的概念。

  為了實現客戶端請求的快速相應和快速處理,據是高並發,則必須使用多線程機制。主題思想是:serversocket通過accept建立一個socket,然后起一個現成,把這個socket扔給新建的線程進行處理,而serversocket主線程,則繼續去監聽端口,以此實現多線程通信。一般有三種方式:

  1、每一個socket請求就建立一個線程。這個是最簡單的方式,大致代碼如下:

  • public void service() {   
  •   while (true) {   
  •     Socket socket=null;   
  •     try {   
  •       socket = serverSocket.accept();                                            //接收客戶連接   
  •       Thread workThread=new Thread(new Handler(socket));            //創建一個工作線程   
  •       workThread.start();                                                            //啟動工作線程   
  •     }catch (IOException e) {   
  •        e.printStackTrace();   
  •     }   
  •   }   

  上面的方式非常簡單,能夠處理基本的多線程問題,當數據量不大時,應該沒有什么問題,但是如果數據量過大時,就會出現嚴重的性能,甚至是宕機問題。其缺點主要有如下幾個:

  a:每個socket請求,建立一個連接,當每個都是進行簡短的通信時,則異常的耗費系統建立、銷毀線程資源。

  b:如果建立線程太多,每個線程都會占用一定的系統內存,這樣將導致內存溢出。

  c:頻繁地對線程進行建立 銷毀,會導致操作系統進行頻繁的cpu切換線程切換,這樣也會非常耗費系統資源。

2、自己實現線程池。

  自己寫線程池,能夠對線程池的工作原理以及工作情況,更加的了解和控制,但是由於線程池必然涉及到多線程問題,因此為了防止出現死鎖、線程泄漏、並發錯誤、任務過載等問題,需要性能非常好的機制,一般不推薦個人現實。如果非要實現,可以通過使用linkedlist<runnable>的數據結構來實現一個多線程隊列。下面,還是主要推薦jdk已經幫你實現的線程池。

3、使用jdk自帶的線程池。jar包是:java.util.concurrent

  這個jar包都是一些並發編程會經常使用到的工具類,主要有阻塞隊列,原子操作的map以及線程池等。其基本包括Executor、ExecutorService接口和Executors類,兩個接口定義了執行線程的方法,而Executors則定義了管理線程池的方法,主要可以創建的常用線程池有:

newSingleThreadScheduledExecutor() 創建一個可以延遲執行和定時執行的單線程線程池

newSingleThreadExecutor() 創建一個運行單線程的線程池

newScheduledThreadPool(int corePoolSize)  創建一個可以延遲執行和定時執行的線程池,設定線程數

newFixedThreadPool(int nThreads)  創建一個固定線程數的線程池

newCachedThreadPool() 創建一個帶有緩沖區的線程池

然后通過生成的線程池的execute方法進行線程的執行,具體可以百度下哈哈

使用線程池有以下幾點風險:

1、死鎖。任何多線程都不可避免的問題。但是對於線程池可能會存在另一種死鎖:就是線程池中的線程都在等待一個資源,而這個資源需要執行A后得到,而由於線程池沒有可用的線程,導致A無法執行,故而也會發生死鎖。

2、系統資源不足。多線程必定需要大量的內存資源,可能出現內存泄漏問題。

3、並發錯誤。

4、線程泄漏。就是所有的線程池中的線程都在等待輸入資源,或者都拋出了異常而沒有捕獲,則會導致線程池中所有的線程假死。

5、線程過載。運行線程過多,導致過載,這個可以通過設置線程池的大小來進行一定成功的避免。

至於如何避免,主要是要在使用多線程時要小心,同時不要使用destroy despause 等操作,盡量使用sleep notify wait等操作,在這里不詳細說明了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM