Java 套接字

原文地址:http://blog.csdn.net/carolzhang8406/article/details/6772812

套接字(socket)爲兩臺計算機之間的通信提供了一種機制,在 James Gosling 注意到 Java 語言之前,套接字就早已赫赫有名。該語言只是讓您不必瞭解底層操作系統的細節就能有效地使用套接字。多數着重討論 Java 編碼的書或者未涵蓋這個主題,或者給讀者留下很大的想象空間。本教程將告訴您開始在代碼中有效地使用套接字時,您真正需要知道哪些知識。我們將專門討論以下問題:

什麼是套接字 它位於您可能要寫的程序的什麼地方 能工作的最簡單的套接字實現 ― 以幫助您理解基礎知識 詳細剖析另外兩個探討如何在多線程和具有連接池環境中使用套接字的示例 簡要討論一個現實世界中的套接字應用程序

如果您能夠描述如何使用 java.net 包中的類,那麼本教程對您來說也許基礎了點,雖然用它來提高一下還是不錯的。如果您在 PC 和其它平臺上使用套接字已經幾年,那麼最初的部分也許會使您覺得煩。但如果您不熟悉套接字,而且只是想知道什麼是套接字以及如何在 Java 代碼中有效地使用它們,那麼本教程就是一個開始的好地方。


套接字基礎 
 1. 介紹

多數程序員,不管他們是否使用 Java 語言進行編碼,都不想很多知道關於不同計算機上的應用程序彼此間如何通信的低級細節。程序員們希望處理更容易理解的更高級抽象。Java 程序員希望能用他們熟悉的 Java 構造,通過直觀接口與對象交互。

套接字在兩個領域中都存在 ― 我們寧願避開的低級細節和我們更願處理的抽象層。本教程討論的低級細節將只限於理解抽象應用程序所必須的部分。


 2. 計算機組網 101

計算機以一種非常簡單的方式進行相互間的操作和通信。計算機芯片是以 1 和 0 的形式存儲並傳輸數據的開―閉轉換器的集合。當計算機想共享數據時,它們所需做的全部就是以一致的速度、順序、定時等等來回傳輸幾百萬比特和字節的數據流。每次想在兩個應用程序之間進行信息通信時,您怎麼會願意擔心那些細節呢?

爲免除這些擔心,我們需要每次都以相同方式完成該項工作的一組包協議。這將允許我們處理應用程序級的工作,而不必擔心低級網絡細節。這些成包協議稱爲協議棧(stack)。TCP/IP 是當今最常見的協議棧。多數協議棧(包括 TCP/IP)都大致對應於國際標準化組織(International Standards Organization,ISO)的開放系統互連參考模型(Open Systems Interconnect Reference Model,OSIRM)。OSIRM 認爲在一個可靠的計算機組網中有七個邏輯層(見圖)。各個地方的公司都對這個模型某些層的實現做了一些貢獻,從生成電子信號(光脈衝、射頻等等)到提供數據給應用程序。TCP/IP 映射到 OSI 模型中的兩層的情形如圖所示。

我們不想涉及層的太多細節,但您應該知道套接字位於什麼地方。



 3. 套接字位於什麼地方

套接字大致駐留在 OSI 模型的會話層(見圖)。會話層夾在其上面嚮應用的層和其下的實時數據通信層之間。會話層爲兩臺計算機之間的數據流提供管理和控制服務。作爲該層的一部分,套接字提供一個隱藏從導線上獲取比特和字節的複雜性的抽象。換句話說,套接字允許我們讓應用程序表明它想發送一些字節即可傳輸數據。套接字隱藏了完成該項工作的具體細節。

當您打電話時,您的聲音傳到傳感器,傳感器把它轉換成可以傳輸的電數據。電話機是人與電信網絡的接口。您無須知道聲音如何傳輸的細節,只要知道想打電話給誰就行了。同樣地,套接字扮演隱藏在未知通道上傳輸 1 和 0 的複雜性的高級接口的角色。



 4. 把套接字暴露給應用程序

使用套接字的代碼工作於表示層。表示層提供應用層能夠使用的信息的公共表示。假設您打算把應用程序連接到只能識別 EBCDIC 的舊的銀行系統。應用程序的域對象以 ASCII 格式存儲信息。在這種情況下,您得負責在表示層上編寫把數據從 EBCDIC 轉換成 ASCII 的代碼,然後(比方說)給應用層提供域對象。應用層然後就可以用域對象來做它想做的任何事情。

您編寫的套接字處理代碼只存在於表示層中。您的應用層無須知道套接字如何工作的任何事情。



 5. 什麼是套接字?

既然我們已經知道套接字扮演的角色,那麼剩下的問題是:什麼是套接字?Bruce Eckel 在他的《Java 編程思想》一書中這樣描述套接字:


套接字是一種軟件抽象,用於表達兩臺機器之間的連接「終端」。對於一個給定的連接,每臺機器上都有一個套接字,您也可以想象它們之間有一條虛擬的「電纜」,「電纜」的每一端都插入到套接字中。當然,機器之間的物理硬件和電纜連接都是完全未知的。抽象的全部目的是使我們無須知道不必知道的細節。

簡言之,一臺機器上的套接字與另一臺機器上的套接字交談就創建一條通信通道。程序員可以用該通道來在兩臺機器之間發送數據。當您發送數據時,TCP/IP 協議棧的每一層都會添加適當的報頭信息來包裝數據。這些報頭幫助協議棧把您的數據送到目的地。好消息是 Java 語言通過"流"爲您的代碼提供數據,從而隱藏了所有這些細節,這也是爲什麼它們有時候被叫做流套接字(streaming socket)的原因。

把套接字想成兩端電話上的聽筒 ― 我和您通過專用通道在我們的電話聽筒上講話和聆聽。直到我們決定掛斷電話,對話纔會結束(除非我們在使用蜂窩電話)。而且我們各自的電話線路都佔線,直到我們掛斷電話。

如果想在沒有更高級機制如 ORB(以及 CORBA、RMI、IIOP 等等)開銷的情況下進行兩臺計算機之間的通信,那麼套接字就適合您。套接字的低級細節相當棘手。幸運的是,Java 平臺給了您一些雖然簡單但卻強大的更高級抽象,使您可以容易地創建和使用套接字。


 6. 套接字的類型

一般而言,Java 語言中的套接字有以下兩種形式:

TCP 套接字(由  Socket 類實現,稍後我們將討論這個類) UDP 套接字(由  DatagramSocket 類實現)

TCP 和 UDP 扮演相同角色,但做法不同。兩者都接收傳輸協議數據包並將其內容向前傳送到表示層。TCP 把消息分解成數據包(數據報,datagrams)並在接收端以正確的順序把它們重新裝配起來。TCP 還處理對遺失數據包的重傳請求。有了 TCP,位於上層的層要擔心的事情就少多了。UDP 不提供裝配和重傳請求這些功能。它只是向前傳送信息包。位於上層的層必須確保消息是完整的並且是以正確的順序裝配的。

一般而言,UDP 強加給您的應用程序的性能開銷更小,但只在應用程序不會突然交換大量數據並且不必裝配大量數據報以完成一條消息的時候。否則,TCP 纔是最簡單或許也是最高效的選擇。

因爲多數讀者都喜歡 TCP 勝過 UDP,所以我們將把討論限制在 Java 語言中面向 TCP 的類。


一個祕密的套接字
 1. 介紹

Java 平臺在 java.net 包中提供套接字的實現。在本教程中,我們將與 java.net 中的以下三個類一起工作:

URLConnection   Socket   ServerSocket

java.net 中還有更多的類,但這些是您將最經常碰到的。讓我們從 URLConnection 開始。這個類爲您不必瞭解任何底層套接字細節就能在 Java 代碼中使用套接字提供一種途徑。


 2. 甚至不用嘗試就可使用套接字

URLConnection 類是所有在應用程序和 URL 之間創建通信鏈路的類的抽象超類。URLConnection 在獲取 Web 服務器上的文檔方面特別有用,但也可用於連接由 URL 標識的任何資源。該類的實例既可用於從資源中讀,也可用於往資源中寫。例如,您可以連接到一個 servlet 併發送一個格式良好的 XML String 到服務器上進行處理。URLConnection 的具體子類(例如 HttpURLConnection)提供特定於它們實現的額外功能。對於我們的示例,我們不想做任何特別的事情,所以我們將使用 URLConnection 本身提供的缺省行爲。

連接到 URL 包括幾個步驟:

創建  URLConnection  用各種 setter 方法配置它 連接到 URL 用各種 getter 方法與它交互

接着,我們將看一些演示如何用 URLConnection 來從服務器請求文檔的樣本代碼


 3. URLClient 類

我們將從 URLClient 類的結構講起。

Java代碼   收藏代碼
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class URLClient {  
  5.     protected URLConnection connection;  
  6.   
  7.     public static void main(String[] args) {  
  8.     }  
  9.     public String getDocumentAt(String urlString) {  
  10.     }  
  11. }  
[java]  view plain  copy
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class URLClient {  
  5.     protected URLConnection connection;  
  6.   
  7.     public static void main(String[] args) {  
  8.     }  
  9.     public String getDocumentAt(String urlString) {  
  10.     }  
  11. }  

要做的第一件事是導入 java.net 和 java.io

我們給我們的類一個實例變量以保存一個 URLConnection

我們的類有一個 main() 方法,它處理瀏覽文檔的邏輯流。我們的類還有一個 getDocumentAt() 方法,該方法連接到服務器並向它請求給定文檔。下面我們將分別探究這些方法的細節。

 4. 瀏覽文檔

main() 方法處理瀏覽文檔的邏輯流:

Java代碼   收藏代碼
  1. public static void main(String[] args) {  
  2.     URLClient client = new URLClient();  
  3.     String yahoo = client.getDocumentAt("http://www.yahoo.com");  
  4.     System.out.println(yahoo);  
  5. }  
[java]  view plain  copy
  1. public static void main(String[] args) {  
  2.     URLClient client = new URLClient();  
  3.     String yahoo = client.getDocumentAt("http://www.yahoo.com");  
  4.     System.out.println(yahoo);  
  5. }  

 

我們的 main() 方法只是創建一個新的 URLClient 並用一個有效的 URL String 調用 getDocumentAt()。當調用返回該文檔時,我們把它存儲在 String,然後將它打印到控制檯。然而,實際的工作是在getDocumentAt() 方法中完成的。

 5. 從服務器請求一個文檔

getDocumentAt() 方法處理獲取 Web 上的文檔的實際工作:

Java代碼   收藏代碼
  1. public String getDocumentAt(String urlString) {  
  2.     StringBuffer document = new StringBuffer();  
  3.     try {  
  4.         URL url = new URL(urlString);  
  5.         URLConnection conn = url.openConnection();  
  6.         BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));  
  7.   
  8.         String line = null;  
  9.         while ((line = reader.readLine()) != null)  
  10.           document.append(line + "\n");  
  11.         reader.close();  
  12.     } catch (MalformedURLException e) {  
  13.         System.out.println("Unable to connect to URL: " + urlString);  
  14.     } catch (IOException e) {  
  15.         System.out.println("IOException when connecting to URL: " + urlString);  
  16.     }  
  17.     return document.toString();  
  18. }  
[java]  view plain  copy
  1. public String getDocumentAt(String urlString) {  
  2.     StringBuffer document = new StringBuffer();  
  3.     try {  
  4.         URL url = new URL(urlString);  
  5.         URLConnection conn = url.openConnection();  
  6.         BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));  
  7.   
  8.         String line = null;  
  9.         while ((line = reader.readLine()) != null)  
  10.           document.append(line + "\n");  
  11.         reader.close();  
  12.     } catch (MalformedURLException e) {  
  13.         System.out.println("Unable to connect to URL: " + urlString);  
  14.     } catch (IOException e) {  
  15.         System.out.println("IOException when connecting to URL: " + urlString);  
  16.     }  
  17.     return document.toString();  
  18. }  
 

 

getDocumentAt() 方法有一個 String 參數,該參數包含我們想獲取的文檔的 URL。我們在開始時創建一個StringBuffer 來保存文檔的行。然後我們用我們傳進去的 urlString 創建一個新 URL。接着創建一個 URLConnection 並打開它:

Java代碼   收藏代碼
  1. URLConnection conn = url.openConnection();  
[java]  view plain  copy
  1. URLConnection conn = url.openConnection();  

一旦有了一個 URLConnection,我們就獲取它的 InputStream 幷包裝進 InputStreamReader,然後我們又把 InputStreamReader 包裝進 BufferedReader 以使我們能夠讀取想從服務器上獲取的文檔的行。在 Java 代碼中處理套接字時,我們將經常使用這種包裝技術,但我們不會總是詳細討論它。在我們繼續往前講之前,您應該熟悉它:

Java代碼   收藏代碼
  1. BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));  
[java]  view plain  copy
  1. BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));  
 

有了 BufferedReader,就使得我們能夠容易地讀取文檔內容。我們在 while 循環中調用 reader 上的 readLine()

Java代碼   收藏代碼
  1. String line = null;  
  2. while ((line = reader.readLine()) != null)  
  3.     document.append(line + "\n");  
[java]  view plain  copy
  1. String line = null;  
  2. while ((line = reader.readLine()) != null)  
  3.     document.append(line + "\n");  
 

對 readLine() 的調用將直至碰到一個從 InputStream 傳入的行終止符(例如換行符)時才阻塞。如果沒碰到,它將繼續等待。只有當連接被關閉時,它纔會返回 null。在這個案例中,一旦我們獲取一個行(line),我們就把它連同一個換行符一起附加(append)到名爲 document 的 StringBuffer 上。這保留了服務器端上讀取的文檔的格式。

我們在讀完行之後關閉 BufferedReader

Java代碼   收藏代碼
  1. reader.close();  
[java]  view plain  copy
  1. reader.close();  

如果提供給 URL 構造器的 urlString 是無效的,那麼將拋出 MalformedURLException。如果發生了別的錯誤,例如當從連接上獲取 InputStream 時,那麼將拋出 IOException


 6. 總結

實際上,URLConnection 使用套接字從我們指定的 URL 中讀取信息(它只是解析成 IP 地址),但我們無須瞭解它,我們也不關心。但有很多事;我們馬上就去看看。

在繼續往前講之前,讓我們回顧一下創建和使用 URLConnection 的步驟:

用您想連接的資源的有效 URL  String  實例化一個  URL (如有問題則拋出  MalformedURLException )。

打開該  URL  上的一個連接。

把該連接的  InputStream  包裝進  BufferedReader  以使您能夠讀取行。

用  BufferedReader  讀文檔。

關閉  BufferedReader

附: URLClient 的完整代碼清單:

Java代碼   收藏代碼
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class URLClient {  
  5.     protected HttpURLConnection connection;  
  6.     public String getDocumentAt(String urlString) {  
  7.         StringBuffer document = new StringBuffer();  
  8.         try {  
  9.             URL url = new URL(urlString);  
  10.             URLConnection conn = url.openConnection();  
  11.             BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));  
  12.   
  13.             String line = null;  
  14.             while ((line = reader.readLine()) != null)  
  15.                 document.append(line + "\n");  
  16.   
  17.             reader.close();  
  18.         } catch (MalformedURLException e) {  
  19.             System.out.println("Unable to connect to URL: " + urlString);  
  20.         } catch (IOException e) {  
  21.             System.out.println("IOException when connecting to URL: " + urlString);  
  22.         }  
  23.   
  24.         return document.toString();  
  25.     }  
  26.     public static void main(String[] args) {  
  27.         URLClient client = new URLClient();  
  28.         String yahoo = client.getDocumentAt("http://www.yahoo.com");  
  29.   
  30.         System.out.println(yahoo);  
  31.     }  
  32. }  


一個簡單示例
 1. 背景

我們將在本部分討論的示例將闡明在 Java 代碼中如何使用 Socket 和 ServerSocket。客戶機用 Socket 連接到服務器。服務器用 ServerSocket 在端口 3000 偵聽。客戶機請求服務器 C: 驅動器上的文件內容。

爲清楚起見,我們把示例分解成客戶機端和服務器端。最後我們將把它們組合起來以使您能看到整體模樣。

我們在使用 JDK 1.2 的 IBM VisualAge for Java 3.5 上開發這些代碼。要自己創建這個示例,您應有完好的 JDK 1.1.7 或更高版本。客戶機和服務器將只在一臺機器上運行,所以您不必擔心是否有一個可用的網絡。


 2. 創建 RemoteFileClient 類

這裏是 RemoteFileClient 類的結構:

Java代碼   收藏代碼
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class RemoteFileClient {  
  5.     protected String hostIp;  
  6.     protected int hostPort;  
  7.     protected BufferedReader socketReader;  
  8.     protected PrintWriter socketWriter;  
  9.   
  10.     public RemoteFileClient(String aHostIp, int aHostPort) {  
  11.         hostIp = aHostIp;  
  12.         hostPort = aHostPort;  
  13.     }  
  14.     public static void main(String[] args) {  
  15.     }  
  16.     public void setUpConnection() {  
  17.     }  
  18.     public String getFile(String fileNameToGet) {  
  19.     }  
  20.     public void tearDownConnection() {  
  21.     }  
  22. }  
[java]  view plain  copy
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class RemoteFileClient {  
  5.     protected String hostIp;  
  6.     protected int hostPort;  
  7.     protected BufferedReader socketReader;  
  8.     protected PrintWriter socketWriter;  
  9.   
  10.     public RemoteFileClient(String aHostIp, int aHostPort) {  
  11.         hostIp = aHostIp;  
  12.         hostPort = aHostPort;  
  13.     }  
  14.     public static void main(String[] args) {  
  15.     }  
  16.     public void setUpConnection() {  
  17.     }  
  18.     public String getFile(String fileNameToGet) {  
  19.     }  
  20.     public void tearDownConnection() {  
  21.     }  
  22. }  

首先我們導入 java.net 和 java.iojava.net 包爲您提供您需要的套接字工具。java.io 包爲您提供對流進行讀寫的工具,這是您與 TCP 套接字通信的唯一途徑。

我們給我們的類實例變量以支持對套接字流的讀寫和存儲我們將連接到的遠程主機的詳細信息。

我們類的構造器有兩個參數:遠程主機的 IP 地址和端口號各一個,而且構造器將它們賦給實例變量。

我們的類有一個 main() 方法和三個其它方法。稍後我們將探究這些方法的細節。現在您只需知道 setUpConnection() 將連接到遠程服務器,getFile() 將向遠程服務器請求 fileNameToGet 的內容以及 tearDownConnection() 將從遠程服務器上斷開。


 3. 實現 main()

這裏我們實現 main() 方法,它將創建 RemoteFileClient 並用它來獲取遠程文件的內容,然後打印結果:

Java代碼   收藏代碼
  1. public static void main(String[] args) {  
  2.     RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1"3000);  
  3.     remoteFileClient.setUpConnection();  
  4.     String fileContents =  
  5.         remoteFileClient.getFile("C:\\WINNT\\Temp\\RemoteFile.txt");  
  6.     remoteFileClient.tearDownConnection();  
  7.   
  8.     System.out.println(fileContents);  
  9. }   
[java]  view plain  copy
  1. public static void main(String[] args) {  
  2.     RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1"3000);  
  3.     remoteFileClient.setUpConnection();  
  4.     String fileContents =  
  5.         remoteFileClient.getFile("C:\\WINNT\\Temp\\RemoteFile.txt");  
  6.     remoteFileClient.tearDownConnection();  
  7.   
  8.     System.out.println(fileContents);  
  9. }   

main() 方法用主機的 IP 地址和端口號實例化一個新 RemoteFileClient(客戶機)。然後,我們告訴客戶機建立一個到主機的連接(稍後有更詳細的討論)。接着,我們告訴客戶機獲取主機上一個指定文件的內容。最後,我們告訴客戶機斷開它到主機的連接。我們把文件內容打印到控制檯,只是爲了證明一切都是按計劃進行的。


 4. 建立連接

這裏我們實現 setUpConnection() 方法,它將創建我們的 Socket 並讓我們訪問該套接字的流:

Java代碼   收藏代碼
  1. public void setUpConnection() {  
  2.     try {  
  3.         Socket client = new Socket(hostIp, hostPort);  
  4.   
  5.         socketReader = new BufferedReader(  
  6.                    new InputStreamReader(client.getInputStream()));  
  7.         socketWriter = new PrintWriter(client.getOutputStream());  
  8.   
  9.     } catch (UnknownHostException e) {  
  10.         System.out.println("Error setting up socket connection: unknown host at " + hostIp + ":" + hostPort);  
  11.     } catch (IOException e) {  
  12.         System.out.println("Error setting up socket connection: " + e);  
  13.     }  
  14. }  
[java]  view plain  copy
  1. public void setUpConnection() {  
  2.     try {  
  3.         Socket client = new Socket(hostIp, hostPort);  
  4.   
  5.         socketReader = new BufferedReader(  
  6.                    new InputStreamReader(client.getInputStream()));  
  7.         socketWriter = new PrintWriter(client.getOutputStream());  
  8.   
  9.     } catch (UnknownHostException e) {  
  10.         System.out.println("Error setting up socket connection: unknown host at " + hostIp + ":" + hostPort);  
  11.     } catch (IOException e) {  
  12.         System.out.println("Error setting up socket connection: " + e);  
  13.     }  
  14. }  

setUpConnection() 方法用主機的 IP 地址和端口號創建一個 Socket

Java代碼   收藏代碼
  1. Socket client = new Socket(hostIp, hostPort);  
[java]  view plain  copy
  1. Socket client = new Socket(hostIp, hostPort);  

我們把 Socket 的 InputStream 包裝進 BufferedReader 以使我們能夠讀取流的行。然後,我們把Socket 的 OutputStream 包裝進 PrintWriter 以使我們能夠發送文件請求到服務器:

Java代碼   收藏代碼
  1. socketReader = new BufferedReader(new InputStreamReader(client.getInputStream()));  
  2. socketWriter = new PrintWriter(client.getOutputStream());  
[java]  view plain  copy
  1. socketReader = new BufferedReader(new InputStreamReader(client.getInputStream()));  
  2. socketWriter = new PrintWriter(client.getOutputStream());  

請記住我們的客戶機和服務器只是來回傳送字節。客戶機和服務器都必須知道另一方即將發送的是什麼以使它們能夠作出適當的響應。在這個案例中,服務器知道我們將發送一條有效的文件路徑。

當您實例化一個 Socket 時,將拋出 UnknownHostException。這裏我們不特別處理它,但我們打印一些信息到控制檯以告訴我們發生了什麼錯誤。同樣地,當我們試圖獲取 Socket 的 InputStream 或 OutputStream 時,如果拋出了一個一般 IOException,我們也打印一些信息到控制檯。這是本教程的一般做法。在產品代碼中,我們應該做得更完善些。


 5. 與主機交談

這裏我們實現 getFile() 方法,它將告訴服務器我們想要什麼文件並在服務器傳回其內容時接收該內容。

Java代碼   收藏代碼
  1. public String getFile(String fileNameToGet) {  
  2.     StringBuffer fileLines = new StringBuffer();  
  3.   
  4.     try {  
  5.         socketWriter.println(fileNameToGet);  
  6.         socketWriter.flush();  
  7.   
  8.         String line = null;  
  9.         while ((line = socketReader.readLine()) != null)  
  10.             fileLines.append(line + "\n");  
  11.     } catch (IOException e) {  
  12.         System.out.println("Error reading from file: " + fileNameToGet);  
  13.     }  
  14.   
  15.     return fileLines.toString();  
  16. }  
[java]  view plain  copy
  1. public String getFile(String fileNameToGet) {  
  2.     StringBuffer fileLines = new StringBuffer();  
  3.   
  4.     try {  
  5.         socketWriter.println(fileNameToGet);  
  6.         socketWriter.flush();  
  7.   
  8.         String line = null;  
  9.         while ((line = socketReader.readLine()) != null)  
  10.             fileLines.append(line + "\n");  
  11.     } catch (IOException e) {  
  12.         System.out.println("Error reading from file: " + fileNameToGet);  
  13.     }  
  14.   
  15.     return fileLines.toString();  
  16. }  

對 getFile() 方法的調用要求一個有效的文件路徑 String。它首先創建名爲 fileLines 的StringBufferfileLines 用於存儲我們讀自服務器上的文件的每一行。

Java代碼   收藏代碼
  1. StringBuffer fileLines = new StringBuffer();  
[java]  view plain  copy
  1. StringBuffer fileLines = new StringBuffer();  

在 try{}catch{} 塊中,我們用 PrintWriter 把請求發送到主機,PrintWriter 是我們在創建連接期間建立的。

Java代碼   收藏代碼
  1. socketWriter.println(fileNameToGet);  
  2. socketWriter.flush();  
[java]  view plain  copy
  1. socketWriter.println(fileNameToGet);  
  2. socketWriter.flush();  

請注意這裏我們是 flush() 該 PrintWriter,而不是關閉它。這迫使數據被髮送到服務器而不關閉Socket

一旦我們已經寫到 Socket,我們就希望有一些響應。我們不得不在 Socket 的 InputStream 上等待它,我們通過在 while 循環中調用 BufferedReader 上的 readLine() 來達到這個目的。我們把每一個返回行附加到 fileLines StringBuffer(帶有一個換行符以保護行):

Java代碼   收藏代碼
  1. String line = null;  
  2. while ((line = socketReader.readLine()) != null)  
  3.     fileLines.append(line + "\n");  
[java]  view plain  copy
  1. String line = null;  
  2. while ((line = socketReader.readLine()) != null)  
  3.     fileLines.append(line + "\n");  


 6. 斷開連接

這裏我們實現 tearDownConnection() 方法,它將在我們使用完畢連接後負責「清除」:

Java代碼   收藏代碼
  1. public void tearDownConnection() {  
  2.     try {  
  3.         socketWriter.close();  
  4.         socketReader.close();  
  5.     } catch (IOException e) {  
  6.         System.out.println("Error tearing down socket connection: " + e);  
  7.     }  
  8. }  
[java]  view plain  copy
  1. public void tearDownConnection() {  
  2.     try {  
  3.         socketWriter.close();  
  4.         socketReader.close();  
  5.     } catch (IOException e) {  
  6.         System.out.println("Error tearing down socket connection: " + e);  
  7.     }  
  8. }  

tearDownConnection() 方法只是分別關閉我們在 Socket 的 InputStream 和 OutputStream 上創建的 BufferedReader 和 PrintWriter。這樣做會關閉我們從 Socket 獲取的底層流,所以我們必須捕捉可能的 IOException


 7. 總結一下客戶機

我們的類研究完了。在我們繼續往前討論服務器端的情況之前,讓我們回顧一下創建和使用 Socket 的步驟:

用您想連接的機器的 IP 地址和端口實例化  Socket (如有問題則拋出  Exception )。

獲取  Socket  上的流以進行讀寫。

把流包裝進  BufferedReader / PrintWriter  的實例,如果這樣做能使事情更簡單的話。

對  Socket  進行讀寫。

關閉打開的流。

   附:RemoteFileClient 的代碼清單

Java代碼   收藏代碼
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class RemoteFileClient {  
  5.     protected BufferedReader socketReader;  
  6.     protected PrintWriter socketWriter;  
  7.     protected String hostIp;  
  8.     protected int hostPort;  
  9.   
  10.     public RemoteFileClient(String aHostIp, int aHostPort) {  
  11.         hostIp = aHostIp;  
  12.         hostPort = aHostPort;  
  13.     }  
  14.     public String getFile(String fileNameToGet) {  
  15.         StringBuffer fileLines = new StringBuffer();  
  16.   
  17.         try {  
  18.             socketWriter.println(fileNameToGet);  
  19.             socketWriter.flush();  
  20.   
  21.             String line = null;  
  22.             while ((line = socketReader.readLine()) != null)  
  23.                 fileLines.append(line + "\n");  
  24.         } catch (IOException e) {  
  25.             System.out.println("Error reading from file: " + fileNameToGet);  
  26.         }  
  27.   
  28.         return fileLines.toString();  
  29.     }  
  30.     public static void main(String[] args) {  
  31.         RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1"3000);  
  32.         remoteFileClient.setUpConnection();  
  33.         String fileContents = remoteFileClient.getFile("C:\\WINNT\\Temp\\RemoteFile.txt");  
  34.         remoteFileClient.tearDownConnection();  
  35.   
  36.         System.out.println(fileContents);  
  37.     }  
  38.     public void setUpConnection() {  
  39.         try {  
  40.             Socket client = new Socket(hostIp, hostPort);  
  41.   
  42.             socketReader = new BufferedReader(new InputStreamReader(client.getInputStream()));  
  43.             socketWriter = new PrintWriter(client.getOutputStream());  
  44.   
  45.         } catch (UnknownHostException e) {  
  46.             System.out.println("Error setting up socket connection: unknown host at " + hostIp + ":" + hostPort);  
  47.         } catch (IOException e) {  
  48.             System.out.println("Error setting up socket connection: " + e);  
  49.         }  
  50.     }  
  51.     public void tearDownConnection() {  
  52.         try {  
  53.             socketWriter.close();  
  54.             socketReader.close();  
  55.         } catch (IOException e) {  
  56.             System.out.println("Error tearing down socket connection: " + e);  
  57.         }  
  58.     }  
  59. }  
[java]  view plain  copy
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class RemoteFileClient {  
  5.     protected BufferedReader socketReader;  
  6.     protected PrintWriter socketWriter;  
  7.     protected String hostIp;  
  8.     protected int hostPort;  
  9.   
  10.     public RemoteFileClient(String aHostIp, int aHostPort) {  
  11.         hostIp = aHostIp;  
  12.         hostPort = aHostPort;  
  13.     }  
  14.     public String getFile(String fileNameToGet) {  
  15.         StringBuffer fileLines = new StringBuffer();  
  16.   
  17.         try {  
  18.             socketWriter.println(fileNameToGet);  
  19.             socketWriter.flush();  
  20.   
  21.             String line = null;  
  22.             while ((line = socketReader.readLine()) != null)  
  23.                 fileLines.append(line + "\n");  
  24.         } catch (IOException e) {  
  25.             System.out.println("Error reading from file: " + fileNameToGet);  
  26.         }  
  27.   
  28.         return fileLines.toString();  
  29.     }  
  30.     public static void main(String[] args) {  
  31.         RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1"3000);  
  32.         remoteFileClient.setUpConnection();  
  33.         String fileContents = remoteFileClient.getFile("C:\\WINNT\\Temp\\RemoteFile.txt");  
  34.         remoteFileClient.tearDownConnection();  
  35.   
  36.         System.out.println(fileContents);  
  37.     }  
  38.     public void setUpConnection() {  
  39.         try {  
  40.             Socket client = new Socket(hostIp, hostPort);  
  41.   
  42.             socketReader = new BufferedReader(new InputStreamReader(client.getInputStream()));  
  43.             socketWriter = new PrintWriter(client.getOutputStream());  
  44.   
  45.         } catch (UnknownHostException e) {  
  46.             System.out.println("Error setting up socket connection: unknown host at " + hostIp + ":" + hostPort);  
  47.         } catch (IOException e) {  
  48.             System.out.println("Error setting up socket connection: " + e);  
  49.         }  
  50.     }  
  51.     public void tearDownConnection() {  
  52.         try {  
  53.             socketWriter.close();  
  54.             socketReader.close();  
  55.         } catch (IOException e) {  
  56.             System.out.println("Error tearing down socket connection: " + e);  
  57.         }  
  58.     }  
  59. }  


 8. 創建 RemoteFileServer 類

這裏是 RemoteFileServer 類的結構:

 

Java代碼   收藏代碼
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class RemoteFileServer {  
  5.     protected int listenPort = 3000;  
  6.     public static void main(String[] args) {  
  7.     }  
  8.     public void acceptConnections() {  
  9.     }  
  10.     public void handleConnection(Socket incomingConnection) {  
  11.     }  
  12. }   
[java]  view plain  copy
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class RemoteFileServer {  
  5.     protected int listenPort = 3000;  
  6.     public static void main(String[] args) {  
  7.     }  
  8.     public void acceptConnections() {  
  9.     }  
  10.     public void handleConnection(Socket incomingConnection) {  
  11.     }  
  12. }   

跟客戶機中一樣,我們首先導入 java.net 的 java.io。接着,我們給我們的類一個實例變量以保存端口,我們從該端口偵聽進入的連接。缺省情況下,端口是 3000。

我們的類有一個 main() 方法和兩個其它方法。稍後我們將探究這些方法的細節。現在您只需知道 acceptConnections() 將允許客戶機連接到服務器以及 handleConnection() 與客戶機 Socket 交互以將您所請求的文件的內容發送到客戶機。


 9. 實現 main()

這裏我們實現 main() 方法,它將創建 RemoteFileServer 並告訴它接受連接:

 

Java代碼   收藏代碼
  1. public static void main(String[] args) {  
  2.     RemoteFileServer server = new RemoteFileServer();  
  3.     server.acceptConnections();  
  4. }   
[java]  view plain  copy
  1. public static void main(String[] args) {  
  2.     RemoteFileServer server = new RemoteFileServer();  
  3.     server.acceptConnections();  
  4. }   

服務器端的 main() 方法甚至比客戶機端的更簡單。我們實例化一個新 RemoteFileServer,它將在缺省偵聽端口上偵聽進入的連接請求。然後我們調用 acceptConnections() 來告訴該 server 進行偵聽。


 10. 接受連接

這裏我們實現 acceptConnections() 方法,它將創建一個 ServerSocket 並等待連接請求:

Java代碼   收藏代碼
  1. public void acceptConnections() {  
  2.     try {  
  3.         ServerSocket server = new ServerSocket(listenPort);  
  4.         Socket incomingConnection = null;  
  5.         while (true) {  
  6.             incomingConnection = server.accept();  
  7.             handleConnection(incomingConnection);  
  8.         }  
  9.     } catch (BindException e) {  
  10.         System.out.println("Unable to bind to port " + listenPort);  
  11.     } catch (IOException e) {  
  12.         System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);  
  13.     }  
  14. } <span>   
  15. </span>  
[java]  view plain  copy
  1. public void acceptConnections() {  
  2.     try {  
  3.         ServerSocket server = new ServerSocket(listenPort);  
  4.         Socket incomingConnection = null;  
  5.         while (true) {  
  6.             incomingConnection = server.accept();  
  7.             handleConnection(incomingConnection);  
  8.         }  
  9.     } catch (BindException e) {  
  10.         System.out.println("Unable to bind to port " + listenPort);  
  11.     } catch (IOException e) {  
  12.         System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);  
  13.     }  
  14. } <span>   
  15. </span>  

 

acceptConnections() 用欲偵聽的端口號來創建 ServerSocket。然後我們通過調用該 ServerSocket 的 accept() 來告訴它開始偵聽。accept() 方法將造成阻塞直到來了一個連接請求。此時,accept() 返回一個新的 Socket,這個 Socket 綁定到服務器上一個隨機指定的端口,返回的 Socket 被傳遞給handleConnection()。請注意我們在一個無限循環中處理對連接的接受。這裏不支持任何關機。

無論何時如果您創建了一個無法綁定到指定端口(可能是因爲別的什麼控制了該端口)的 ServerSocket,Java 代碼都將拋出一個錯誤。所以這裏我們必須捕捉可能的 BindException。就跟在客戶機端上時一樣,我們必須捕捉 IOException,當我們試圖在 ServerSocket 上接受連接時,它就會被拋出。請注意,您可以通過用毫秒數調用 setSoTimeout() 來爲 accept() 調用設置超時,以避免實際長時間的等待。調用 setSoTimeout() 將使 accept() 經過指定佔用時間後拋出 IOException


 11. 處理連接

這裏我們實現 handleConnection() 方法,它將用連接的流來接收輸入和寫輸出:

Java代碼   收藏代碼
  1. public void handleConnection(Socket incomingConnection) {  
  2.     try {  
  3.         OutputStream outputToSocket = incomingConnection.getOutputStream();  
  4.         InputStream inputFromSocket = incomingConnection.getInputStream();  
  5.   
  6.         BufferedReader streamReader =  
  7.             new BufferedReader(new InputStreamReader(inputFromSocket));  
  8.   
  9.         FileReader fileReader = new FileReader(new File(streamReader.readLine()));  
  10.   
  11.         BufferedReader bufferedFileReader = new BufferedReader(fileReader);  
  12.         PrintWriter streamWriter =  
  13.             new PrintWriter(incomingConnection.getOutputStream());  
  14.         String line = null;  
  15.         while ((line = bufferedFileReader.readLine()) != null) {  
  16.             streamWriter.println(line);  
  17.         }  
  18.   
  19.         fileReader.close();  
  20.         streamWriter.close();  
  21.         streamReader.close();  
  22.     } catch (Exception e) {  
  23.         System.out.println("Error handling a client: " + e);  
  24.     }  
  25. }  
[java]  view plain  copy
  1. public void handleConnection(Socket incomingConnection) {  
  2.     try {  
  3.         OutputStream outputToSocket = incomingConnection.getOutputStream();  
  4.         InputStream inputFromSocket = incomingConnection.getInputStream();  
  5.   
  6.         BufferedReader streamReader =  
  7.             new BufferedReader(new InputStreamReader(inputFromSocket));  
  8.   
  9.         FileReader fileReader = new FileReader(new File(streamReader.readLine()));  
  10.   
  11.         BufferedReader bufferedFileReader = new BufferedReader(fileReader);  
  12.         PrintWriter streamWriter =  
  13.             new PrintWriter(incomingConnection.getOutputStream());  
  14.         String line = null;  
  15.         while ((line = bufferedFileReader.readLine()) != null) {  
  16.             streamWriter.println(line);  
  17.         }  
  18.   
  19.         fileReader.close();  
  20.         streamWriter.close();  
  21.         streamReader.close();  
  22.     } catch (Exception e) {  
  23.         System.out.println("Error handling a client: " + e);  
  24.     }  
  25. }  

跟在客戶機中一樣,我們用 getOutputStream() 和 getInputStream() 來獲取與我們剛創建的 Socket 相關聯的流。跟在客戶機端一樣,我們把 InputStream 包裝進 BufferedReader,把 OutputStream 包裝進 PrintWriter。在服務器端上,我們需要添加一些代碼,用來讀取目標文件和把內容逐行發送到客戶機。這裏是重要的代碼:

Java代碼   收藏代碼
  1. FileReader fileReader = new FileReader(new File(streamReader.readLine()));  
  2. BufferedReader bufferedFileReader = new BufferedReader(fileReader);  
  3. String line = null;  
  4. while ((line = bufferedFileReader.readLine()) != null) {  
  5.      streamWriter.println(line);  
  6. }  
[java]  view plain  copy
  1. FileReader fileReader = new FileReader(new File(streamReader.readLine()));  
  2. BufferedReader bufferedFileReader = new BufferedReader(fileReader);  
  3. String line = null;  
  4. while ((line = bufferedFileReader.readLine()) != null) {  
  5.      streamWriter.println(line);  
  6. }  

這些代碼值得詳細解釋。讓我們一點一點來看:

 

Java代碼   收藏代碼
  1. FileReader fileReader = new FileReader(new File(streamReader.readLine()));  
[java]  view plain  copy
  1. FileReader fileReader = new FileReader(new File(streamReader.readLine()));  

 首先,我們使用 Socket 的 InputStream 的 BufferedReader。我們應該獲取一條有效的文件路徑,所以我們用該路徑名構造一個新 File。我們創建一個新 FileReader 來處理讀文件的操作。

Java代碼   收藏代碼
  1. BufferedReader bufferedFileReader = new BufferedReader(fileReader);  
[java]  view plain  copy
  1. BufferedReader bufferedFileReader = new BufferedReader(fileReader);  

這裏我們把 FileReader 包裝進 BufferedReader 以使我們能夠逐行地讀該文件。

接着,我們調用 BufferedReader 的 readLine()。這個調用將造成阻塞直到有字節到來。我們獲取一些字節之後就把它們放到本地的 line 變量中,然後再寫出到客戶機上。完成讀寫操作之後,我們就關閉打開的流。

請注意我們在完成從 Socket 的讀操作之後關閉 streamWriter 和 streamReader。您或許會問我們爲什麼不在讀取文件名之後立刻關閉 streamReader。原因是當您這樣做時,您的客戶機將不會獲取任何數據。如果您在關閉 streamWriter 之前關閉 streamReader,則您可以往 Socket 寫任何東西,但卻沒有任何數據能通過通道(通道被關閉了)。




 12. 總結一下服務器

在我們接着討論另一個更實際的示例之前,讓我們回顧一下創建和使用 ServerSocket 的步驟:

用一個您想讓它偵聽傳入客戶機連接的端口來實例化一個  ServerSocket (如有問題則拋出  Exception )。

調用  ServerSocket  的  accept()  以在等待連接期間造成阻塞。

獲取位於該底層  Socket  的流以進行讀寫操作。

按使事情簡單化的原則包裝流。

對  Socket  進行讀寫。

關閉打開的流(並請記住, 永遠不要 在關閉 Writer 之前關閉 Reader)。

  附: RemoteFileServer 的完整的代碼清單

Java代碼   收藏代碼
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class RemoteFileServer {  
  5.     int listenPort;  
  6.     public RemoteFileServer(int aListenPort) {  
  7.         listenPort = aListenPort;  
  8.     }  
  9.     public void acceptConnections() {  
  10.         try {  
  11.             ServerSocket server = new ServerSocket(listenPort);  
  12.             Socket incomingConnection = null;  
  13.             while (true) {  
  14.                 incomingConnection = server.accept();  
  15.                 handleConnection(incomingConnection);  
  16.             }  
  17.         } catch (BindException e) {  
  18.             System.out.println("Unable to bind to port " + listenPort);  
  19.         } catch (IOException e) {  
  20.             System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);  
  21.         }  
  22.     }  
  23.     public void handleConnection(Socket incomingConnection) {  
  24.         try {  
  25.             OutputStream outputToSocket = incomingConnection.getOutputStream();  
  26.             InputStream inputFromSocket = incomingConnection.getInputStream();  
  27.   
  28.             BufferedReader streamReader = new BufferedReader(new InputStreamReader(inputFromSocket));  
  29.   
  30.             FileReader fileReader = new FileReader(new File(streamReader.readLine()));  
  31.   
  32.             BufferedReader bufferedFileReader = new BufferedReader(fileReader);  
  33.             PrintWriter streamWriter = new PrintWriter(incomingConnection.getOutputStream());  
  34.             String line = null;  
  35.             while ((line = bufferedFileReader.readLine()) != null) {  
  36.                 streamWriter.println(line);  
  37.             }  
  38.   
  39.             fileReader.close();  
  40.             streamWriter.close();  
  41.             streamReader.close();  
  42.         } catch (Exception e) {  
  43.             System.out.println("Error handling a client: " + e);  
  44.         }  
  45.     }  
  46.     public static void main(String[] args) {  
  47.         RemoteFileServer server = new RemoteFileServer(3000);  
  48.         server.acceptConnections();  
  49.     }  
  50. }  
[java]  view plain  copy
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class RemoteFileServer {  
  5.     int listenPort;  
  6.     public RemoteFileServer(int aListenPort) {  
  7.         listenPort = aListenPort;  
  8.     }  
  9.     public void acceptConnections() {  
  10.         try {  
  11.             ServerSocket server = new ServerSocket(listenPort);  
  12.             Socket incomingConnection = null;  
  13.             while (true) {  
  14.                 incomingConnection = server.accept();  
  15.                 handleConnection(incomingConnection);  
  16.             }  
  17.         } catch (BindException e) {  
  18.             System.out.println("Unable to bind to port " + listenPort);  
  19.         } catch (IOException e) {  
  20.             System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);  
  21.         }  
  22.     }  
  23.     public void handleConnection(Socket incomingConnection) {  
  24.         try {  
  25.             OutputStream outputToSocket = incomingConnection.getOutputStream();  
  26.             InputStream inputFromSocket = incomingConnection.getInputStream();  
  27.   
  28.             BufferedReader streamReader = new BufferedReader(new InputStreamReader(inputFromSocket));  
  29.   
  30.             FileReader fileReader = new FileReader(new File(streamReader.readLine()));  
  31.   
  32.             BufferedReader bufferedFileReader = new BufferedReader(fileReader);  
  33.             PrintWriter streamWriter = new PrintWriter(incomingConnection.getOutputStream());  
  34.             String line = null;  
  35.             while ((line = bufferedFileReader.readLine()) != null) {  
  36.                 streamWriter.println(line);  
  37.             }  
  38.   
  39.             fileReader.close();  
  40.             streamWriter.close();  
  41.             streamReader.close();  
  42.         } catch (Exception e) {  
  43.             System.out.println("Error handling a client: " + e);  
  44.         }  
  45.     }  
  46.     public static void main(String[] args) {  
  47.         RemoteFileServer server = new RemoteFileServer(3000);  
  48.         server.acceptConnections();  
  49.     }  
  50. }  
 

一個多線程的示例 
 1. 介紹

前面的示例教給您基礎知識,但並不能令您更深入。如果您到此就停止了,那麼您一次只能處理一臺客戶機。原因是 handleConnection() 是一個阻塞方法。只有當它完成了對當前連接的處理時,服務器才能接受另一個客戶機。在多數時候,您將需要(也有必要)一個多線程服務器。

要開始同時處理多臺客戶機,並不需要對 RemoteFileServer 作太多改變。事實上,要是我們前面討論過待發(backlog),那我們就只需改變一個方法,雖然我們將需要創建一些新東西來處理進入的連接。這裏我們還將向您展示 ServerSocket 如何處理衆多等待(備份)使用服務器的客戶機。本示例對線程的低效使用,所以請耐心點。


 2. 接受(太多)連接

這裏我們實現改動過的 acceptConnections() 方法,它將創建一個能夠處理待發請求的 ServerSocket,並告訴 ServerSocket 接受連接:

Java代碼   收藏代碼
  1. public void acceptConnections() {  
  2.         try {  
  3.         ServerSocket server = new ServerSocket(listenPort, 5);  
  4.         Socket incomingConnection = null;  
  5.         while (true) {  
  6.             incomingConnection = server.accept();  
  7.             handleConnection(incomingConnection);  
  8.         }  
  9.     } catch (BindException e) {  
  10.     System.out.println("Unable to bind to port " + listenPort);  
  11.     } catch (IOException e) {  
  12.     System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);  
  13.     }  
  14. }  
[java]  view plain  copy
  1. public void acceptConnections() {  
  2.         try {  
  3.         ServerSocket server = new ServerSocket(listenPort, 5);  
  4.         Socket incomingConnection = null;  
  5.         while (true) {  
  6.             incomingConnection = server.accept();  
  7.             handleConnection(incomingConnection);  
  8.         }  
  9.     } catch (BindException e) {  
  10.     System.out.println("Unable to bind to port " + listenPort);  
  11.     } catch (IOException e) {  
  12.     System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);  
  13.     }  
  14. }  

新的 server 仍然需要 acceptConnections(),所以這些代碼實際上是一樣的。突出顯示的行表示一個重大的不同。對這個多線程版,我們現在可以指定客戶機請求的最大數目,這些請求都能在實例化 ServerSocket 期間處於待發狀態。如果我們沒有指定客戶機請求的最大數目,則我們假設使用缺省值 50。

這裏是它的工作機制。假設我們指定待發數(backlog 值)是 5 並且有五臺客戶機請求連接到我們的服務器。我們的服務器將着手處理第一個連接,但處理該連接需要很長時間。由於我們的待發值是 5,所以我們一次可以放五個請求到隊列中。我們正在處理一個,所以這意味着還有其它五個正在等待。等待的和正在處理的一共有六個。當我們的服務器仍忙於接受一號連接(記住隊列中還有 2―6 號)時,如果有第七個客戶機提出連接申請,那麼,該第七個客戶機將遭到拒絕。我們將在帶有連接池服務器示例中說明如何限定能同時連接的客戶機數目。


 3. 處理連接:第 1 部分

這裏我們將討論 handleConnection() 方法的結構,這個方法生成一個新的 Thread 來處理每個連接。我們將分兩部分討論這個問題。這一屏我們將着重該方法本身,然後在下一屏研究該方法所使用的ConnectionHandler 助手類的結構。

Java代碼   收藏代碼
  1. public void handleConnection(Socket connectionToHandle) {  
  2.      new Thread(new ConnectionHandler(connectionToHandle)).start();  
  3. }  
[java]  view plain  copy
  1. public void handleConnection(Socket connectionToHandle) {  
  2.      new Thread(new ConnectionHandler(connectionToHandle)).start();  
  3. }  

我們對 RemoteFileServer 所做的大改動就體現在這個方法上。我們仍然在服務器接受一個連接之後調用 handleConnection(),但現在我們把該 Socket 傳遞給 ConnectionHandler 的一個實例,它是 Runnable的。我們用 ConnectionHandler 創建一個新 Thread 並啓動它。ConnectionHandler 的 run() 方法包含Socket 讀/寫和讀 File 的代碼,這些代碼原來在 RemoteFileServer 的 handleConnection() 中。


 4. 處理連接:第 2 部分

這裏是 ConnectionHandler 類的結構:

Java代碼   收藏代碼
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class ConnectionHandler implements Runnable{  
  5.    Socket socketToHandle;  
  6.   
  7.    public ConnectionHandler(Socket aSocketToHandle) {  
  8.       socketToHandle = aSocketToHandle;  
  9.    }  
  10.   
  11.    public void run() {  
  12.    }  
  13. }  
[java]  view plain  copy
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class ConnectionHandler implements Runnable{  
  5.    Socket socketToHandle;  
  6.   
  7.    public ConnectionHandler(Socket aSocketToHandle) {  
  8.       socketToHandle = aSocketToHandle;  
  9.    }  
  10.   
  11.    public void run() {  
  12.    }  
  13. }  

這個助手類相當簡單。跟我們到目前爲止的其它類一樣,我們導入 java.net 和 java.io。該類只有一個實例變量 socketToHandle,它保存由該實例處理的 Socket

類的構造器用一個 Socket 實例作參數並將它賦給 socketToHandle

請注意該類實現了 Runnable 接口。實現這個接口的類都必須實現 run() 方法,我們的類就是這樣做的。稍後我們將探究 run() 的細節。現在只需知道它將實際處理連接,所用的代碼跟我們先前在 RemoteFileServer 類中看到的是一樣的。


 5. 實現 run()

這裏我們實現 run() 方法,它將攫取我們的連接的流,用它來讀寫該連接,並在任務完成之後關閉它:

Java代碼   收藏代碼
  1. public void run() {  
  2.         try {  
  3.             PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream());  
  4.             BufferedReader streamReader =  
  5.                 new BufferedReader(new InputStreamReader(socketToHandle.getInputStream()));  
  6.   
  7.             String fileToRead = streamReader.readLine();  
  8.             BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));  
  9.   
  10.             String line = null;  
  11.             while ((line = fileReader.readLine()) != null)  
  12.                 streamWriter.println(line);  
  13.   
  14.             fileReader.close();  
  15.             streamWriter.close();  
  16.             streamReader.close();  
  17.         } catch (Exception e) {  
  18.             System.out.println("Error handling a client: " + e);  
  19.         }  
  20.     }  
[java]  view plain  copy
  1. public void run() {  
  2.         try {  
  3.             PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream());  
  4.             BufferedReader streamReader =  
  5.                 new BufferedReader(new InputStreamReader(socketToHandle.getInputStream()));  
  6.   
  7.             String fileToRead = streamReader.readLine();  
  8.             BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));  
  9.   
  10.             String line = null;  
  11.             while ((line = fileReader.readLine()) != null)  
  12.                 streamWriter.println(line);  
  13.   
  14.             fileReader.close();  
  15.             streamWriter.close();  
  16.             streamReader.close();  
  17.         } catch (Exception e) {  
  18.             System.out.println("Error handling a client: " + e);  
  19.         }  
  20.     }  

ConnectionHandler 的 run() 方法所做的事情就是 RemoteFileServer 上的 handleConnection() 所做的事情。首先,我們把 InputStream 和 OutputStream 分別包裝(用 Socket 的 getOutputStream() 和 getInputStream())進 BufferedReader 和 PrintWriter。然後我們用這些代碼逐行地讀目標文件:

Java代碼   收藏代碼
  1. FileReader fileReader = new FileReader(new File(streamReader.readLine()));  
  2.         BufferedReader bufferedFileReader = new BufferedReader(fileReader);  
  3.         String line = null;  
  4.         while ((line = bufferedFileReader.readLine()) != null) {  
  5.              streamWriter.println(line);  
  6.         }   
[java]  view plain  copy
  1. FileReader fileReader = new FileReader(new File(streamReader.readLine()));  
  2.         BufferedReader bufferedFileReader = new BufferedReader(fileReader);  
  3.         String line = null;  
  4.         while ((line = bufferedFileReader.readLine()) != null) {  
  5.              streamWriter.println(line);  
  6.         }   

請記住我們應該從客戶機獲取一條有效的文件路徑,這樣用該路徑名構造一個新 File,把它包裝進 FileReader 以處理讀文件的操作,然後把它包裝進 BufferedReader 以讓我們逐行地讀該文件。我們在 while 循環中調用 BufferedReader 上的 readLine() 直到不再有要讀的行。請記注,對 readLine() 的調用將造成阻塞,直到有字節來到爲止。我們獲取一些字節之後就把它們放到本地的 line 變量中,然後寫出到客戶機上。完成讀寫操作之後,我們關閉打開的流。


 6. 總結一下多線程服務器

我們的多線程服務器研究完了。在我們接着討論帶有連接池示例之前,讓我們回顧一下創建和使用「多線程版」的服務器的步驟:

修改  acceptConnections()  以用缺省爲 50(或任何您想要的大於 1 的指定數字)實例化  ServerSocket
修改  ServerSocket  的  handleConnection()  以用  ConnectionHandler  的一個實例生成一個新的 Thread

借用  RemoteFileServer  的  handleConnection()  方法的代碼實現  ConnectionHandler  類。

   附: MultithreadedRemoteFileServer 的完整代碼清單

Java代碼   收藏代碼
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class MultithreadedRemoteFileServer {  
  5.     protected int listenPort;  
  6.     public MultithreadedRemoteFileServer(int aListenPort) {  
  7.         listenPort = aListenPort;  
  8.     }  
  9.     public void acceptConnections() {  
  10.         try {  
  11.             ServerSocket server = new ServerSocket(listenPort, 5);  
  12.             Socket incomingConnection = null;  
  13.             while (true) {  
  14.                 incomingConnection = server.accept();  
  15.                 handleConnection(incomingConnection);  
  16.             }  
  17.         } catch (BindException e) {  
  18.             System.out.println("Unable to bind to port " + listenPort);  
  19.         } catch (IOException e) {  
  20.             System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);  
  21.         }  
  22.     }  
  23.     public void handleConnection(Socket connectionToHandle) {  
  24.         new Thread(new ConnectionHandler(connectionToHandle)).start();  
  25.     }  
  26.     public static void main(String[] args) {  
  27.         MultithreadedRemoteFileServer server = new MultithreadedRemoteFileServer(3000);  
  28.         server.acceptConnections();  
  29.     }  
  30. }  
[java]  view plain  copy
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class MultithreadedRemoteFileServer {  
  5.     protected int listenPort;  
  6.     public MultithreadedRemoteFileServer(int aListenPort) {  
  7.         listenPort = aListenPort;  
  8.     }  
  9.     public void acceptConnections() {  
  10.         try {  
  11.             ServerSocket server = new ServerSocket(listenPort, 5);  
  12.             Socket incomingConnection = null;  
  13.             while (true) {  
  14.                 incomingConnection = server.accept();  
  15.                 handleConnection(incomingConnection);  
  16.             }  
  17.         } catch (BindException e) {  
  18.             System.out.println("Unable to bind to port " + listenPort);  
  19.         } catch (IOException e) {  
  20.             System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);  
  21.         }  
  22.     }  
  23.     public void handleConnection(Socket connectionToHandle) {  
  24.         new Thread(new ConnectionHandler(connectionToHandle)).start();  
  25.     }  
  26.     public static void main(String[] args) {  
  27.         MultithreadedRemoteFileServer server = new MultithreadedRemoteFileServer(3000);  
  28.         server.acceptConnections();  
  29.     }  
  30. }  
 

        ConnectionHandler 的完整代碼清單

Java代碼   收藏代碼
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class ConnectionHandler implements Runnable {  
  5.     protected Socket socketToHandle;  
  6.     public ConnectionHandler(Socket aSocketToHandle) {  
  7.         socketToHandle = aSocketToHandle;  
  8.     }  
  9.     public void run() {  
  10.         try {  
  11.             PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream());  
  12.             BufferedReader streamReader = new BufferedReader(new InputStreamReader(socketToHandle.getInputStream()));  
  13.   
  14.             String fileToRead = streamReader.readLine();  
  15.             BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));  
  16.   
  17.             String line = null;  
  18.             while ((line = fileReader.readLine()) != null)  
  19.                 streamWriter.println(line);  
  20.   
  21.             fileReader.close();  
  22.             streamWriter.close();  
  23.             streamReader.close();  
  24.         } catch (Exception e) {  
  25.             System.out.println("Error handling a client: " + e);  
  26.         }  
  27.     }  
  28. }  
[java]  view plain  copy
  1. import java.io.*;  
  2. import java.net.*;  
  3.   
  4. public class ConnectionHandler implements Runnable {  
  5.     protected Socket socketToHandle;  
  6.     public ConnectionHandler(Socket aSocketToHandle) {  
  7.         socketToHandle = aSocketToHandle;  
  8.     }  
  9.     public void run() {  
  10.         try {  
  11.             PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream());  
  12.             BufferedReader streamReader = new BufferedReader(new InputStreamReader(socketToHandle.getInputStream()));  
  13.   
  14.             String fileToRead = streamReader.readLine();  
  15.             BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));  
  16.   
  17.             String line = null;  
  18.             while ((line = fileReader.readLine()) != null)  
  19.                 streamWriter.println(line);  
  20.   
  21.             fileReader.close();  
  22.             streamWriter.close();  
  23.             streamReader.close();  
  24.         } catch (Exception e) {  
  25.             System.out.println("Error handling a client: " + e);  
  26.         }  
  27.     }  
  28. }  
 

一個帶有連接池的示例 
 1. 介紹

我們現在已經擁有的 MultithreadedServer 每當有客戶機申請一個連接時都在一個新 Thread 中創建一個新 ConnectionHandler。這意味着可能有一捆 Thread 「躺」在我們周圍。而且創建 Thread 的系統開銷並不是微不足道的。如果性能成爲了問題(也請不要事到臨頭才意識到它),更高效地處理我們的服務器是件好事。那麼,我們如何更高效地管理服務器端呢?我們可以維護一個進入的連接池,一定數量的ConnectionHandler 將爲它提供服務。這種設計能帶來以下好處:

它限定了允許同時連接的數目。 我們只需啓動  ConnectionHandler   Thread  一次。

幸運的是,跟在我們的多線程示例中一樣,往代碼中添加「池」不需要來一個大改動。事實上,應用程序的客戶機端根本就不受影響。在服務器端,我們在服務器啓動時創建一定數量的 ConnectionHandler,我們把進入的連接放入「池」中並讓 ConnectionHandler 打理剩下的事情。這種設計中有很多我們不打算討論的可能存在的技巧。例如,我們可以通過限定允許在「池」中建立的連接的數目來拒絕客戶機。

請注意:我們將不會再次討論 acceptConnections()。這個方法跟前面示例中的完全一樣。它無限循環地調用 ServerSocket 上的 accept() 並把連接傳遞到 handleConnection()


 2. 創建 PooledRemoteFileServer 類

這裏是 PooledRemoteFileServer 類的結構:

Java代碼   收藏代碼
  1. import java.io.*;  
  2. import java.net.*;  
  3. import java.util.*;  
  4.   
  5. public class PooledRemoteFileServer {  
  6.     protected int maxConnections;  
  7.     protected int listenPort;  
  8.     protected ServerSocket serverSocket;  
  9.   
  10.     public PooledRemoteFileServer(int aListenPort, int maxConnections) {  
  11.         listenPort = aListenPort;  
  12.         this.maxConnections = maxConnections;  
  13.     }  
  14.     public static void main(String[] args) {  
  15.     }  
  16.     public void setUpHandlers() {  
  17.     }  
  18.     public void acceptConnections() {  
  19.     }  
  20.     protected void handleConnection(Socket incomingConnection) {  
  21.     }  
  22. }  
[java]  view plain  copy
  1. import java.io.*;  
  2. import java.net.*;  
  3. import java.util.*;  
  4.   
  5. public class PooledRemoteFileServer {  
  6.     protected int maxConnections;  
  7.     protected int listenPort;  
  8.     protected ServerSocket serverSocket;  
  9.   
  10.     public PooledRemoteFileServer(int aListenPort, int maxConnections) {  
  11.         listenPort = aListenPort;  
  12.         this.maxConnections = maxConnections;  
  13.     }  
  14.     public static void main(String[] args) {  
  15.     }  
  16.     public void setUpHandlers() {  
  17.     }  
  18.     public void acceptConnections() {  
  19.     }  
  20.     protected void handleConnection(Socket incomingConnection) {  
  21.     }  
  22. }  
 

請注意一下您現在應該熟悉了的 import 語句。我們給類以下實例變量以保存:

我們的服務器能同時處理的活動客戶機連接的最大數目

進入的連接的偵聽端口(我們沒有指定缺省值,但如果您想這樣做,並不會受到限制)

將接受客戶機連接請求的  ServerSocket

類的構造器用的參數是偵聽端口和連接的最大數目

我們的類有一個 main() 方法和三個其它方法。稍後我們將探究這些方法的細節。現在只須知道 setUpHandlers() 創建數目爲 maxConnections 的大量 PooledConnectionHandler,而其它兩個方法則與我們前面已經看到的相似:acceptConnections() 在 ServerSocket 上偵聽傳入的客戶機連接,而 handleConnection 則在客戶機連接一旦被建立後就實際處理它。


 3. 實現 main()

這裏我們實現需作改動的 main() 方法,該方法將創建能夠處理給定數目的客戶機連接的 PooledRemoteFileServer,並告訴它接受連接:

Java代碼   收藏代碼
  1. public static void main(String[] args) {  
  2.     PooledRemoteFileServer server = new PooledRemoteFileServer(30003);  
  3.     server.setUpHandlers();  
  4.     server.acceptConnections();  
  5. }  
[java]  view plain  copy
  1. public static void main(String[] args) {  
  2.     PooledRemoteFileServer server = new PooledRemoteFileServer(30003);  
  3.     server.setUpHandlers();  
  4.     server.acceptConnections();  
  5. }  

我們的 main() 方法很簡單。我們實例化一個新的 PooledRemoteFileServer,它將通過調用setUpHandlers() 來建立三個 PooledConnectionHandler。一旦服務器就緒,我們就告訴它acceptConnections()


 4. 建立連接處理程序

Java代碼   收藏代碼
  1. public void setUpHandlers() {  
  2.     for (int i = 0; i < maxConnections; i++) {  
  3.         PooledConnectionHandler currentHandler = new PooledConnectionHandler();  
  4.         new Thread(currentHandler, "Handler " + i).start();  
  5.     }  
  6. }  
[java]  view plain  copy
  1. public void setUpHandlers() {  
  2.     for (int i = 0; i < maxConnections; i++) {  
  3.         PooledConnectionHandler currentHandler = new PooledConnectionHandler();  
  4.         new Thread(currentHandler, "Handler " + i).start();  
  5.     }  
  6. }  
 

setUpHandlers() 方法創建 maxConnections(例如 3)個 PooledConnectionHandler 並在新 Thread中激活它們。用實現了 Runnable 的對象來創建 Thread 使我們可以在 Thread 調用 start() 並且可以期望在 Runnable 上調用了 run()。換句話說,我們的 PooledConnectionHandler 將等着處理進入的連接,每個都在它自己的 Thread 中進行。我們在示例中只創建三個 Thread,而且一旦服務器運行,這就不能被改變。


 5. 處理連接

這裏我們實現需作改動的 handleConnections() 方法,它將委派 PooledConnectionHandler 處理連接:

Java代碼   收藏代碼
  1. protected void handleConnection(Socket connectionToHandle) {  
  2. PooledConnectionHandler.processRequest(connectionToHandle);  
  3. }  
[java]  view plain  copy
  1. protected void handleConnection(Socket connectionToHandle) {  
  2. PooledConnectionHandler.processRequest(connectionToHandle);  
  3. }  
 

我們現在叫 PooledConnectionHandler 處理所有進入的連接(processRequest() 是一個靜態方法)。

這裏是 PooledConnectionHandler 類的結構:

Java代碼   收藏代碼
  1. import java.io.*;  
  2. import java.net.*;  
  3. import java.util.*;  
  4.   
  5. public class PooledConnectionHandler implements Runnable {  
  6.     protected Socket connection;  
  7.     protected static List pool = new LinkedList();  
  8.   
  9.     public PooledConnectionHandler() {  
  10.     }  
  11.     public void handleConnection() {  
  12.     }  
  13.     public static void processRequest(Socket requestToHandle) {  
  14.     }  
  15.     public void run() {  
  16.     }  
  17. }  
[java]  view plain  copy
  1. import java.io.*;  
  2. import java.net.*;  
  3. import java.util.*;  
  4.   
  5. public class PooledConnectionHandler implements Runnable {  
  6.     protected Socket connection;  
  7.     protected static List pool = new LinkedList();  
  8.   
  9.     public PooledConnectionHandler() {  
  10.     }  
  11.     public void handleConnection() {  
  12.     }  
  13.     public static void processRequest(Socket requestToHandle) {  
  14.     }  
  15.     public void run() {  
  16.     }  
  17. }  
 

這個助手類與 ConnectionHandler 非常相似,但它帶有處理連接池的手段。該類有兩個實例變量:

connection  是當前正在處理的  Socket  名爲  pool  的靜態  LinkedList  保存需被處理的連接


 6. 填充連接池

這裏我們實現 PooledConnectionHandler 上的 processRequest() 方法,它將把傳入請求添加到池中,並告訴其它正在等待的對象該池已經有一些內容:

Java代碼   收藏代碼
  1. public static void processRequest(Socket requestToHandle) {  
  2.     synchronized (pool) {  
  3.         pool.add(pool.size(), requestToHandle);  
  4.         pool.notifyAll();  
  5.     }  
  6. }  
[java]  view plain  copy
  1. public static void processRequest(Socket requestToHandle) {  
  2.     synchronized (pool) {  
  3.         pool.add(pool.size(), requestToHandle);  
  4.         pool.notifyAll();  
  5.     }  
  6. }  
 

理解這個方法要求有一點關於 Java 的關鍵字 synchronized 如何工作的背景知識。我們將簡要講述一下線程。

先來看一些定義:

原子方法。 在執行過程中不能被中斷的方法(或代碼塊)  互斥鎖。 客戶機欲執行原子方法時必須獲得的單個「鎖」

因此,當對象 A 想使用對象 B 的 synchronized 方法 doSomething() 時,對象 A 必須首先嚐試獲取對象 B 的互斥鎖。是的,這意味着當對象 A 擁有該互斥鎖時,沒有其它對象可以調用對象 B 上任何其它synchronized 方法。

synchronized 塊是個稍微有些不同的東西。您可以同步任何對象上的一個塊,而不只是在本身的某個方法中含有該塊的對象。在我們的示例中,processRequest() 方法包含有一個 pool(請記住它是一個 LinkedList,保存等待處理的連接池)的 synchronized塊。我們這樣做的原因是確保沒有別人能跟我們同時修改連接池。

既然我們已經保證了我們是唯一「涉水」池中的人,我們就可以把傳入的 Socket 添加到 LinkedList 的尾端。一旦我們添加了新的連接,我們就用以下代碼通知其它正在等待該池的 Thread,池現在已經可用:

Java代碼   收藏代碼
  1. pool.notifyAll();  
[java]  view plain  copy
  1. pool.notifyAll();  
 

Object 的所有子類都繼承這個 notifyAll() 方法。這個方法,連同我們下一屏將要討論的 wait() 方法一起,就使一個 Thread 能夠讓另一個 Thread 知道一些條件已經具備。這意味着該第二個 Thread 一定正在等待那些條件的滿足。


 7. 從池中獲取連接

這裏我們實現 PooledConnectionHandler 上需作改動的 run()方法,它將在連接池上等待,並且池中一有連接就處理它:

Java代碼   收藏代碼
  1. public void run() {  
  2.         while (true) {  
  3.              synchronized (pool) {  
  4.                   while (pool.isEmpty()) {  
  5.                        try {  
  6.                             pool.wait();  
  7.                        } catch (InterruptedException e) {  
  8.                             return;  
  9.                        }  
  10.                    }  
  11.                    connection = (Socket) pool.remove(0);  
  12.              }  
  13.              handleConnection();  
  14.         }  
  15. }  
[java]  view plain  copy
  1. public void run() {  
  2.         while (true) {  
  3.              synchronized (pool) {  
  4.                   while (pool.isEmpty()) {  
  5.                        try {  
  6.                             pool.wait();  
  7.                        } catch (InterruptedException e) {  
  8.                             return;  
  9.                        }  
  10.                    }  
  11.                    connection = (Socket) pool.remove(0);  
  12.              }  
  13.              handleConnection();  
  14.         }  
  15. }  

 回想一下在前一屏講過的:一個 Thread 正在等待有人通知它連接池方面的條件已經滿足了。在我們的示例中,請記住我們有三個 PooledConnectionHandler 在等待使用池中的連接。每個 PooledConnectionHandler 都在它自已的 Thread 中運行,並通過調用 pool.wait() 產生阻塞。當我們的 processRequest() 在連接池上調用 notifyAll() 時,所有正在等待的 PooledConnectionHandler 都將得到「池已經可用」的通知。然後各自繼續前行調用 pool.wait(),並重新檢查 while(pool.isEmpty()) 循環條件。除了一個處理程序,其它池對所有處理程序都將是空的,因此,在調用 pool.wait() 時,除了一個處理程序,其它所有處理程序都將再次產生阻塞。恰巧碰上非空池的處理程序將跳出 while(pool.isEmpty()) 循環並攫取池中的第一個連接:

Java代碼   收藏代碼
  1. connection = (Socket) pool.remove(0);  
[java]  view plain  copy
  1. connection = (Socket) pool.remove(0);  
 

處理程序一旦有一個連接可以使用,就調用 handleConnection() 處理它。

在我們的示例中,池中可能永遠不會有多個連接,只是因爲事情很快就被處理掉了。如果池中有一個以上連接,那麼其它處理程序將不必等待新的連接被添加到池。當它們檢查 pool.isEmpty() 條件時,將發現其值爲假,然後就從池中攫取一個連接並處理它。

還有另一件事需注意。當 run() 擁有池的互斥鎖時,processRequest() 如何能夠把連接放到池中呢?答案是對池上的 wait() 的調用釋放鎖,而 wait() 接着就在自己返回之前再次攫取該鎖。這就使得池對象的其它同步代碼可以獲取該鎖。


 8. 處理連接:再一次

這裏我們實現需做改動的 handleConnection() 方法,該方法將攫取連接的流,使用它們,並在任務完成之後清除它們:

Java代碼   收藏代碼
  1. public void handleConnection() {  
  2.     try {  
  3.         PrintWriter streamWriter = new PrintWriter(connection.getOutputStream());  
  4.         BufferedReader streamReader =  
  5.             new BufferedReader(new InputStreamReader(connection.getInputStream()));  
  6.   
  7.         String fileToRead = streamReader.readLine();  
  8.         BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));  
  9.   
  10.         String line = null;  
  11.         while ((line = fileReader.readLine()) != null)  
  12.             streamWriter.println(line);  
  13.   
  14.         fileReader.close();  
  15.         streamWriter.close();  
  16.         streamReader.close();  
  17.     } catch (FileNotFoundException e) {  
  18.         System.out.println("Could not find requested file on the server.");  
  19.     } catch (IOException e) {  
  20.         System.out.println("Error handling a client: " + e);  
  21.     }  
  22. }  
[java]  view plain  copy
  1. public void handleConnection() {  
  2.     try {  
  3.         PrintWriter streamWriter = new PrintWriter(connection.getOutputStream());  
  4.         BufferedReader streamReader =  
  5.             new BufferedReader(new InputStreamReader(connection.getInputStream()));  
  6.   
  7.         String fileToRead = streamReader.readLine();  
  8.         BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));  
  9.   
  10.         String line = null;  
  11.         while ((line = fileReader.readLine()) != null)  
  12.             streamWriter.println(line);  
  13.   
  14.         fileReader.close();  
  15.         streamWriter.close();  
  16.         streamReader.close();  
  17.     } catch (FileNotFoundException e) {  
  18.         System.out.println("Could not find requested file on the server.");  
  19.     } catch (IOException e) {  
  20.         System.out.println("Error handling a client: " + e);  
  21.     }  
  22. }  
 

跟在多線程服務器中不同,我們的 PooledConnectionHandler 有一個 handleConnection() 方法。這個方法的代碼跟非池式的 ConnectionHandler 上的 run() 方法的代碼完全一樣。首先,我們把 OutputStream和 InputStream 分別包裝進(用 Socket 上的 getOutputStream() 和 getInputStream()BufferedReader 和 PrintWriter。然後我們逐行讀目標文件,就象我們在多線程示例中做的那樣。再一次,我們獲取一些字節之後就把它們放到本地的 line 變量中,然後寫出到客戶機。完成讀寫操作之後,我們關閉 FileReader 和打開的流。


 9. 總結一下帶有連接池的服務器 

我們的帶有連接池的服務器研究完了。讓我們回顧一下創建和使用「池版」服務器的步驟:

創建一個新種類的連接處理程序(我們稱之爲  PooledConnectionHandler )來處理池中的連接。

修改服務器以創建和使用一組  PooledConnectionHandler

   附: PooledRemoteFileServer 的完整代碼清單

Java代碼   收藏代碼
  1. import java.io.*;  
  2. import java.net.*;  
相關文章
相關標籤/搜索