ServerSocket詳解
構造方法
ServerSocket()
ServerSocket(int port)
ServerSocket(int port ,int backlog)
serverSocket(int port,int backlog,InetAddress bindADDR)
port是服務器綁定的端口(服務器要監聽的端口);backlog是指定客戶連接請求隊列的長度(若設置為0,就是該服務器不能被客戶端訪問),bindAddr指定服務器要綁定的IP地址。
如果把參數port設為0,由操作系統分配端口也稱匿名端口,一般不會這樣設置,因為客戶端要訪問服務器。許多操作系統限定了隊列的最大長度,一般為50.當隊列中的鏈接請求達到了隊列的最大容量,服務器進程所在的主機會拒絕新的連接請求。
注:如果服務端Socket socket = serverSocket.accept()注釋掉,客戶端可以連接服務端,但服務端隊列中的連接請求永遠不會被取出,就是該請求的資源沒有被釋放
----
設定綁定的IP地址
如果主機只有一個IP地址,那么默認情況下,服務器程序就與該IP地址綁定。如果有主機有多個IP,假定一個主機有兩個網卡,一個網卡連接到internet,ip地址222.67.5.94,一個網卡用於連接到本地局域網,ip地址為192.168.3.4;如果服務器僅僅被本地局域網中的客戶訪問,那么可以按如下方式創建ServerScoket:
ServerSocket serverSocket = new ServerSocket(8000,10,InetAddress.getByName("192.168.3.4"));
----
默認構造方法
ServerSocket不帶參數構造方法,不與任何端口綁定,作用是在綁定端口前,設置一些ServerSocket的一些選項;
ServerSocket serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true);
serverSocket.bind(new InetSocketAddress(8800));
-------
接收和關閉與客戶的連接
ServerSocket的accept()方法從連接請求隊列中取出一個客戶的連接請求,然后創建與客戶連接的Socket對象;如果這時將請求放入多線程中處理,其他請求就不需要等待。
while(true){
Socket socket = null;
try {
socket = serverSocket.accept();
System.out.println("new connection accepted"+
socket.getInetAddress()+":"+socket.getPort());
BufferedReader br = getReader(socket);
PrintWriter pw = getWriter(socket);
String msg = null;
while((msg = br.readLine()) != null){
System.out.println(msg);
pw.println(echo(msg));
if(msg.equals("bye")){
break;
}
}
} catch (Exception e) {
// TODO: handle exception
}finally {
if(socket != null){
socket.close();//關閉單個用戶與服務之間的連接,這里是放在try-catch-finally語句,不影響其他客戶的連接
}
}
}
----------
關閉ServerSocket;
若關閉ServerSocket,斷開所有客戶的連接
-----
獲取ServerSocket信息
若服務端使用匿名端口(port=0);可以通過ServerSocket中的getLocalPort()獲取系統給服務器分配的端口;多次啟用服務器,分配的端口可能都不同。
匿名端口適用於服務器與客戶之間的臨時通信,通信結束,就端口連接,並且ServerSocket占用的臨時端口也被釋放。(客戶端如何獲取服務器端口呢)
FTP(文件傳輸協議)就使用了匿名端口。
FTP協議用於在本地文件系統與遠程文件系統之間傳送文件。
FTP使用兩個並行的TCP連接:一個控制連接,一個是數據連接。控制連接用於在客戶和服務器之間發送控制信息,如用戶和口令、改變遠程目錄的命令或上傳和下載命令。數據連接用於傳送文件。TCP服務器再21端口上監聽控制連接,如果有客戶要求上傳或下載文件,就另外建立一個數據連接,通過它來傳送文件。
--------
創建多線程服務器:
用並發性能來衡量一個服務器同時響應多個客戶的能力,一個具有好的並發性能的服務器,必須符合兩個條件:
能同時接收並處理多個客戶連接;
對於每個客戶,都會迅速給予響應。
服務器同時處理的客戶連接數目越多,並且對每個客戶作出響應越快,就並發性能越高。
用多個線程來同時為多個客戶提供服務,這是提高服務器的並發性能最常用的手段。
1、為每個客戶分配一個工作線程。
2、創建一個線程池,由其中的工作線程為客戶服務。
3、利用JDK的java類庫現成的線程池,由它的工作線程為客戶服務。
----
為每個客戶分配一個工作線程。
while(true){
Socket socket = null;
try {
socket = serverSocket.accept();
Thread workThread = new Thread(new Handler(socket));//線程處理類new Handler
workThread.start();//啟動線程
} catch (Exception e) {
// TODO: handle exception
}finally {
if(socket != null){
socket.close();
}
}
}
---
class Handler(Socket socket) implements Runnable{
socket與客戶之間的通信;
}
----------
創建線程池;在java網絡編程的P70頁中;
線程池的好處就是:頻繁創建、銷毀線程銷毀系統資源,當某一時期訪問量變大,創建線程過多,導致內存不足,系統可能會崩潰。一般操作系統、java虛擬機中的線程切換是20毫秒。
---
非阻塞的原理,當某個線程阻塞了,正在讀取數據,那么先讓出cpu資源,讓其他等待的線程執行。
線程池,服務器接收一個任務,創建一個線程負責這個任務處理,並且將這個線程放入線程池中,如果線程池滿了,就不在接收任務。
非阻塞的通信機制主要由java包(新I/O包)的類實現,主要的類包括ServerSocketChannel、SocketChannel、Selector、SelectionKey和ByteBuffer等。
---
線程阻塞的概念:線程在運行中因為某些原因而阻塞。所有處於阻塞狀態的線程的共同特征是:放棄CPU,暫停運行,只有等到導致阻塞的原因消除,才能恢復運行。
線程阻塞的原因:
1、線程執行了Thread.sleep(int n)方法,線程放棄CPU,睡眠n毫秒,然后恢復運行。
2、線程要執行一段同步代碼,由於無法獲得相關的同步鎖,只要進入阻塞狀態,等到獲得了同步鎖,才能恢復運行。
3、線程執行了一個對象的wait()方法,進入阻塞狀態,只有等到其他線程執行了該對象的notify()或notifyAll()方法,才可能將其喚醒。
4、線程執行I/O操作或進行遠程通信時,會因為等待相關的資源而進入阻塞狀態。
-------
進行遠程通信時,在客戶程序中,線程在以下情況可能進入阻塞狀態。
1、請求與服務器建立連接時,即當線程執行Socket的帶參數構造方法,或執行Socket的Connect()方法時,會進入阻塞狀態,此線程才從Socket的構造方法或Connect()方法返回。
2、線程從Socket的輸入流讀入數據時,如果沒有足夠的數據,就會進入阻塞狀態,直到讀到了足夠數據,或者達到輸入流的末尾,或者出現了異常才從輸入流的read()方法返回或異常中斷。輸入流中有多少數據才算足夠呢?這要看線程執行的read()方法的類型。
1)、int read():只要輸入流中有一個字節,就算足夠。
2)、int read(byte[] buff):只要輸入流中的字節數目與參數buff數組的長度相同,就算足夠。
3)、String readLine():只要輸入流有一行字符串,就算足夠。值得注意的是,InoutStream類並沒有readLine()方法,在過濾流BufferedReader類中才有此方法。
----
服務器端線程可能產生的阻塞:
1、線程執行ServerSocket的accept方法時,等待客戶的連接,直到接收到了客戶連接,才從accept()方法返回。
2、線程Socket的輸入流讀入數據時,如果輸入流沒有足夠的數據,就會進入阻塞狀態。
3、線程向Socket的輸出流寫一批數據時,可能會進入阻塞狀態,等到輸出了所有數據,或者出現異常,才從輸出流的write()方法返回或異常中斷。
------
服務器程序用多線程來處理阻塞I/O,盡管能滿足同時響應多個客戶請求的需求,但是有以下局限:
1、java虛擬機會為每個線程分配獨立的堆棧空間,工作線程數目越多,系統開銷就越大,而且增加了java虛擬機調度線程的負擔,增強了線程之間同步復雜性,提高了線程死鎖的可能性。
2、工作線程的許多時間都浪費在阻塞I/O操作上,java虛擬機需要頻繁地轉讓CPU的使用權,使進入阻塞狀態的線程放棄CPU,再把CPU分配給處於可運行狀態的線程。
----------
非阻塞I/O,服務器程序只需要一個線程就能同時負責接收客戶的連接,接收各個客戶發送的數據,以及向各個客戶發送響應數據。服務程序的處理的流程:
while(一直等待,直到有接收連接就緒的事件、讀就緒事件或寫就緒事件發生){
if(有客戶連接)
接收客戶的連接;
if(某個Socket的輸入流中有可讀數據)
從輸入流中讀數據;
if(某個Socket的輸出流可以寫數據)
向輸出流寫數據;
}
以上處理流程采用了輪詢的工作方式,當某一種操作就緒時,就執行該操作,否則就查看是否還有其他就緒的操作可以執行。
為了使輪詢的工作方式順利進行,接收客戶連接、從輸入流讀數據,以及向輸出流寫數據的操作都應該以非阻塞的方式運行。