原文地址:http://blog.csdn.net/lidh04/archive/2008/12/11/3500708.aspx
Java Socket
套接字(socket)為兩臺(tái)計(jì)算機(jī)之間的通信提供了一種機(jī)制,在James Gosling注意到Java 語(yǔ)言之前,套接字就早已赫赫有名。該語(yǔ)言只是讓您不必了解底層操作系統(tǒng)的細(xì)節(jié)就能有效地使用套接字。
1 客戶機(jī)/服務(wù)器模型
在飯店里,菜單上各種具有異國(guó)情調(diào)的食品映入你的眼簾,于是你要了一份pizza。幾分鐘后,你用力咀嚼澆著融化的乳酪和其他你喜歡的配料的熱pizza。你不知道,也不想知道:侍者從那里弄來(lái)了pizza,在制作過(guò)程中加進(jìn)了什么,以及配料是如何獲得的。
上例中包含的實(shí)體有:美味的pizza、接受你定餐的侍者、制作pizza的廚房,當(dāng)然還有你。你是定pizza的顧客或客戶。制作pizza的過(guò)程對(duì)于你而言是被封裝的。你的請(qǐng)求在廚房中被處理,pizza制作完成后,由侍者端給你。
你所看到的就是一個(gè)客戶機(jī)/服務(wù)器模型。客戶機(jī)向服務(wù)器發(fā)送一個(gè)請(qǐng)求或命令。服務(wù)器處理客戶機(jī)的請(qǐng)求。客戶機(jī)和服務(wù)器之間的通訊是客戶機(jī)/服務(wù)器模型中的一個(gè)重要組成部分,通常通過(guò)網(wǎng)絡(luò)進(jìn)行。
客戶機(jī)/服務(wù)器模型是一個(gè)應(yīng)用程序開(kāi)發(fā)框架,該框架是為了將數(shù)據(jù)的表示與其內(nèi)部的處理和存儲(chǔ)分離開(kāi)來(lái)而設(shè)計(jì)的。客戶機(jī)請(qǐng)求服務(wù),服務(wù)器為這些請(qǐng)求服務(wù)。請(qǐng)求通過(guò)網(wǎng)絡(luò)從客戶機(jī)傳遞到服務(wù)器。服務(wù)器所進(jìn)行的處理對(duì)客戶機(jī)而言是隱藏的。一個(gè)服務(wù)器可以為多臺(tái)客戶機(jī)服務(wù)。
多臺(tái)客戶機(jī)訪問(wèn)服務(wù)器
服務(wù)器和客戶機(jī)不一定是硬件組件。它們可以是工作啊同一機(jī)器或不同機(jī)器上的程序。、
考慮一個(gè)航空定票系統(tǒng)中的數(shù)據(jù)輸入程序:數(shù)據(jù)----乘客名、航班號(hào)、飛行日期、目的地等可以被輸入到前端----客戶機(jī)的應(yīng)用程序中。一旦數(shù)據(jù)輸入之后,客戶機(jī)將數(shù)據(jù)發(fā)送到后端----服務(wù)器端。服務(wù)器處理數(shù)據(jù)并在數(shù)據(jù)庫(kù)中保存數(shù)據(jù)。客戶機(jī)/服務(wù)器模型的重要性在于所有的數(shù)據(jù)都存放在同一地點(diǎn)。客戶機(jī)從不同的地方訪問(wèn)同一數(shù)據(jù)源,服務(wù)器對(duì)所有的輸入數(shù)據(jù)應(yīng)用同樣的檢驗(yàn)規(guī)則。
萬(wàn)維網(wǎng)為‘為什么要將數(shù)據(jù)的表示與其存儲(chǔ)、處理分離開(kāi)來(lái)’提供了一個(gè)很好的例子。在Web上,你無(wú)需控制最終用戶用來(lái)訪問(wèn)你數(shù)據(jù)的平臺(tái)和軟件。你可以考慮編寫(xiě)出適用與每一種潛在的目標(biāo)平臺(tái)的應(yīng)用程序。
‘客戶機(jī)/服務(wù)器應(yīng)用程序的服務(wù)器部分’管理通過(guò)多個(gè)客戶機(jī)訪問(wèn)服務(wù)器的、多個(gè)用戶共享的資源。表明‘客戶機(jī)/服務(wù)器程序的服務(wù)器部分’強(qiáng)大功能的最好例子應(yīng)該是Web服務(wù)器,它通過(guò)Internet將HTML頁(yè)傳遞給不同的Web用戶。
Java 編程語(yǔ)言中最基本的特點(diǎn)是在Java中創(chuàng)建的程序的代碼的可移植性。因?yàn)榫哂衅渌Z(yǔ)言所不具備的代碼可移植性,Java允許用戶只要編寫(xiě)一次應(yīng)用程序,就可以在任何客戶機(jī)系統(tǒng)上發(fā)布它,并可以讓客戶機(jī)系統(tǒng)解釋該程序。這意味著:你只要寫(xiě)一次代碼,就能使其在任何平臺(tái)上運(yùn)行。
2 協(xié)議
當(dāng)你同朋友交談時(shí),你們遵循一些暗含的規(guī)則(或協(xié)議)。例如:你們倆不能同時(shí)開(kāi)始說(shuō)話,或連續(xù)不間斷地說(shuō)話。如果你們這樣作的話,誰(shuí)也不能理解對(duì)方所說(shuō)的東西。當(dāng)你說(shuō)話時(shí),你的朋友傾聽(tīng),反之亦然。你們以雙方都能理解的語(yǔ)言和速度進(jìn)行對(duì)話。
當(dāng)計(jì)算機(jī)之間進(jìn)行通訊的時(shí)候,也需要遵循一定的規(guī)則。數(shù)據(jù)以包的形式從一臺(tái)機(jī)器發(fā)送到另一臺(tái)。這些規(guī)則管理數(shù)據(jù)打包、數(shù)據(jù)傳輸速度和重新 數(shù)據(jù)將其恢復(fù)成原始形式。這些規(guī)則被稱為網(wǎng)絡(luò)協(xié)議。網(wǎng)絡(luò)協(xié)議是通過(guò)網(wǎng)絡(luò)進(jìn)行通訊的系統(tǒng)所遵循的一系列規(guī)則和慣例。連網(wǎng)軟件通常實(shí)現(xiàn)有高低層次之分的多層協(xié)議。網(wǎng)絡(luò)協(xié)議的例子有:TCP/IP、UDP、Apple Talk和NetBEUI。
Java提供了一個(gè)豐富的、支持網(wǎng)絡(luò)的類庫(kù),這些類使得應(yīng)用程序能方便地訪問(wèn)網(wǎng)絡(luò)資源。Java提供了兩種通訊工具。它們是:使用用戶報(bào)文協(xié)議(UDP)的報(bào)文和使用傳輸控制協(xié)議/因特網(wǎng)協(xié)議(TCP/IP)的Sockets(套接字)。
數(shù)據(jù)報(bào)包是一個(gè)字節(jié)數(shù)組從一個(gè)程序(發(fā)送程序)傳送到另一個(gè)(接受程序)。由于數(shù)據(jù)報(bào)遵守UDP,不保證發(fā)出的數(shù)據(jù)包必須到達(dá)目的地。數(shù)據(jù)報(bào)并不是可信賴的。因此,僅當(dāng)傳送少量數(shù)據(jù)時(shí)才使用,而且發(fā)送者和接受者之間的距離間隔不大,假如是網(wǎng)絡(luò)交通高峰,或接受程序正處理來(lái)自其他程序的多個(gè)請(qǐng)求,就有機(jī)會(huì)出現(xiàn)數(shù)據(jù)報(bào)包的丟失。
Sockets套接字用TCP來(lái)進(jìn)行通訊。套接字模型同其他模型相比,優(yōu)越性在于其不受客戶請(qǐng)求來(lái)自何處的影響。只要客戶機(jī)遵循TCP/IP協(xié)議,服務(wù)器就會(huì)對(duì)它的請(qǐng)求提供服務(wù)。這意味著客戶機(jī)可以是任何類型的計(jì)算機(jī)。客戶機(jī)不再局限為UNIX、Windows、DOS或 Macintosh平臺(tái),因此,網(wǎng)上所有遵循TCP/IP協(xié)議的計(jì)算機(jī)可以通過(guò)套接字互相通訊。
3 Sockets套接字
3.1 Sockets概況
在客戶機(jī)/服務(wù)器應(yīng)用程序中,服務(wù)器提供象處理數(shù)據(jù)庫(kù)查詢或修改數(shù)據(jù)庫(kù)中的數(shù)據(jù)之類的服務(wù)。發(fā)生在客戶機(jī)和服務(wù)器之間的通訊必須是可靠的,同時(shí)數(shù)據(jù)在客戶機(jī)上的次序應(yīng)該和服務(wù)器發(fā)送出來(lái)的次序相同。
什么是套接字?
既然我們已經(jīng)知道套接字扮演的角色,那么剩下的問(wèn)題是:什么是套接字?Bruce Eckel 在他的《Java 編程思想》一書(shū)中這樣描述套接字:套接字是一種軟件抽象,用于表達(dá)兩臺(tái)機(jī)器之間的連接“終端”。對(duì)于一個(gè)給定的連接,每臺(tái)機(jī)器上都有一個(gè)套接字,您也可以想象它們之間有一條虛擬的“電纜”,“電纜”的每一端都插入到套接字中。當(dāng)然,機(jī)器之間的物理硬件和電纜連接都是完全未知的。抽象的全部目的是使我們無(wú)須知道不必知道的細(xì)節(jié)。
簡(jiǎn)言之,一臺(tái)機(jī)器上的套接字與另一臺(tái)機(jī)器上的套接字交談就創(chuàng)建一條通信通道。程序員可以用該通道來(lái)在兩臺(tái)機(jī)器之間發(fā)送數(shù)據(jù)。當(dāng)您發(fā)送數(shù)據(jù)時(shí),TCP/IP 協(xié)議棧的每一層都會(huì)添加適當(dāng)?shù)膱?bào)頭信息來(lái)包裝數(shù)據(jù)。這些報(bào)頭幫助協(xié)議棧把您的數(shù)據(jù)送到目的地。好消息是 Java 語(yǔ)言通過(guò)"流"為您的代碼提供數(shù)據(jù),從而隱藏了所有這些細(xì)節(jié),這也是為什么它們有時(shí)候被叫做流套接字(streaming socket)的原因。
把套接字想成兩端電話上的聽(tīng)筒,我和您通過(guò)專用通道在我們的電話聽(tīng)筒上講話和聆聽(tīng)。直到我們決定掛斷電話,對(duì)話才會(huì)結(jié)束(除非我們?cè)谑褂梅涓C電話)。而且我們各自的電話線路都占線,直到我們掛斷電話。
如果想在沒(méi)有更高級(jí)機(jī)制如 ORB(以及 CORBA、RMI、IIOP 等等)開(kāi)銷的情況下進(jìn)行兩臺(tái)計(jì)算機(jī)之間的通信,那么套接字就適合您。套接字的低級(jí)細(xì)節(jié)相當(dāng)棘手。幸運(yùn)的是,Java 平臺(tái)給了您一些雖然簡(jiǎn)單但卻強(qiáng)大的更高級(jí)抽象,使您可以容易地創(chuàng)建和使用套接字。
傳輸控制協(xié)議(TCP)提供了一條可靠的、點(diǎn)對(duì)點(diǎn)的通訊通道,客戶機(jī)/服務(wù)器應(yīng)用程序可以用該通道互相通訊。要通過(guò)TCP進(jìn)行通訊,客戶機(jī)和服務(wù)器程序建立連接并綁定套接字。套接字用于處理通過(guò)網(wǎng)絡(luò)連接的應(yīng)用程序之間的通訊。客戶機(jī)和服務(wù)器之間更深入的通訊通過(guò)套接字完成。
Java被設(shè)計(jì)成一種連網(wǎng)語(yǔ)言。它通過(guò)將連接功能封裝到套接字類里而使得網(wǎng)絡(luò)編程更加容易。套接字類即Socket類(它創(chuàng)建一個(gè)客戶套接字)和ServerSocket類(它創(chuàng)建一個(gè)服務(wù)器套接字)。套接字類大致介紹如下:
l Socket是基類,它支持TCP協(xié)議。TCP是一個(gè)可靠的流網(wǎng)絡(luò)連接協(xié)議。Socket類提供了流輸入/輸出的方法,使得從套接字中讀出數(shù)據(jù)和往套接字中寫(xiě)數(shù)據(jù)都很容易。該類對(duì)于編寫(xiě)因特網(wǎng)上的通訊程序而言是必不可少的。
l ServerSocket是一個(gè)因特網(wǎng)服務(wù)程序用來(lái)監(jiān)聽(tīng)客戶請(qǐng)求的類。ServerSocket實(shí)際上并不執(zhí)行服務(wù);而是創(chuàng)建了一個(gè)Socket對(duì)象來(lái)代表客戶機(jī)。通訊由創(chuàng)建的對(duì)象來(lái)完成。
3.2 IP地址和端口
因特網(wǎng)服務(wù)器可以被認(rèn)為是一組套接字類,它們提供了一般稱為服務(wù)的附加功能。服務(wù)的例子有:電子郵件、遠(yuǎn)程登錄的Telnet、和通過(guò)網(wǎng)絡(luò)傳輸文件的文件傳輸協(xié)議(FTP)。每種服務(wù)都與一個(gè)端口相聯(lián)系。端口是一個(gè)數(shù)值地址,通過(guò)它來(lái)處理服務(wù)請(qǐng)求(就象請(qǐng)求Web頁(yè)一樣)。
TCP協(xié)議需要兩個(gè)數(shù)據(jù)項(xiàng):IP地址和端口號(hào)。因此,當(dāng)鍵入
http://www.jinnuo.com時(shí),你是如何進(jìn)入金諾的主頁(yè)呢?
因特網(wǎng)協(xié)議(IP)提供每一項(xiàng)網(wǎng)絡(luò)設(shè)備。這些設(shè)備都帶有一個(gè)稱為IP地址的邏輯地址。由因特網(wǎng)協(xié)議提供的IP地址具有特定的形式。每個(gè)IP地址都是32位的數(shù)值,表示4個(gè)范圍在0到255之間的8位數(shù)值金諾已經(jīng)注冊(cè)了它的名字,分配給
http://www.jinnuo.com的IP地址為192.168.0.110。
注意:域名服務(wù)或DNS服務(wù)是將
http://www.jinnuo.com翻譯成192.168.0.110的服務(wù)。這使你可以鍵入
http://www.jinnuo.com而不必記住IP地址。想象一下,怎么可能記住所有需要訪問(wèn)的站點(diǎn)的IP地址!有趣的是一個(gè)網(wǎng)絡(luò)名可以映射到許多IP地址。對(duì)于經(jīng)常訪問(wèn)的站點(diǎn)可能需要這一功能,因?yàn)檫@些站點(diǎn)容納大量的信息,并需要多個(gè)IP地址來(lái)提供業(yè)務(wù)服務(wù)。例如:192.168.0.110的實(shí)際的內(nèi)部名稱為
http://www.jinnuo.com。DNS可以將分配給jinnuo Ltd.的一系列IP地址翻譯成
http://www.jinnuo.com。
如果沒(méi)有指明端口號(hào),則使用服務(wù)文件中服務(wù)器的端口。每種協(xié)議有一個(gè)缺省的端口號(hào),在端口號(hào)未指明時(shí)使用該缺省端口號(hào)。
端口號(hào) 應(yīng)用
21 FTP.傳輸文件
23 Telnet.提供遠(yuǎn)程登錄
25 SMTP.傳遞郵件信息
67 BOOTP.在啟動(dòng)時(shí)提供配置情況
80 HTTP.傳輸Web頁(yè)
109 POP.使用戶能訪問(wèn)遠(yuǎn)程系統(tǒng)中的郵箱
讓我們?cè)賮?lái)看一下URL:
http://www.jinnuo.com
URL的第一部分(http)意味著你正在使用超文本傳輸協(xié)議(HTTP),該協(xié)議處理Web文檔。如果沒(méi)有指明文件,大多數(shù)的Web服務(wù)器會(huì)取一個(gè)叫index.html文件。因此,IP地址和端口既可以通過(guò)明確指出URL各部分來(lái)決定,也可以由缺省值決定。
4 創(chuàng)建Socket客戶
我們將在本部分討論的示例將闡明在 Java 代碼中如何使用 Socket 和 ServerSocket。客戶機(jī)用 Socket 連接到服務(wù)器。服務(wù)器用 ServerSocket 在端口 1001 偵聽(tīng)。客戶機(jī)請(qǐng)求服務(wù)器 C: 驅(qū)動(dòng)器上的文件內(nèi)容。
創(chuàng)建 RemoteFileClient 類
- import java.io.*;
- import java.net.*;
- public class RemoteFileClient {
- protected BufferedReader socketReader;
- protected PrintWriter socketWriter;
- protected String hostIp;
- protected int hostPort;
-
- public RemoteFileClient(String hostIp, int hostPort) {
- this.hostIp = hostIp;
- this.hostPort=hostPort;
- }
-
- public String getFile(String fileNameToGet) {
- StringBuffer fileLines = new StringBuffer();
- try {
- socketWriter.println(fileNameToGet);
- socketWriter.flush();
- String line = null;
- while((line=socketReader.readLine())!=null)
- fileLines.append(line+"\n");
- }
- catch(IOException e) {
- System.out.println("Error reading from file: "+fileNameToGet);
- }
- return fileLines.toString();
- }
-
- public void setUpConnection() {
- try {
- Socket client = new Socket(hostIp,hostPort);
- socketReader = new BufferedReader(new InputStreamReader(client.getInputStream()));
- socketWriter = new PrintWriter(client.getOutputStream());
- }
- catch(UnknownHostException e) {
- System.out.println("Error1 setting up socket connection: unknown host at "+hostIp+":"+hostPort);
- }
- catch(IOException e) {
- System.out.println("Error2 setting up socket connection: "+e);
- }
- }
-
- public void tearDownConnection() {
- try {
- socketWriter.close();
- socketReader.close();
- }catch(IOException e) {
- System.out.println("Error tearing down socket connection: "+e);
- }
- }
- public static void main(String args[]) {
- RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1",1001);
- remoteFileClient.setUpConnection();
- StringBuffer fileContents = new StringBuffer();
- fileContents.append(remoteFileClient.getFile("RemoteFileServer.java"));
-
- System.out.println(fileContents);
- }
- }
首先我們導(dǎo)入 java.net 和 java.io。java.net 包為您提供您需要的套接字工具。java.io 包為您提供對(duì)流進(jìn)行讀寫(xiě)的工具,這是您與 TCP 套接字通信的唯一途徑。
我們給我們的類實(shí)例變量以支持對(duì)套接字流的讀寫(xiě)和存儲(chǔ)我們將連接到的遠(yuǎn)程主機(jī)的詳細(xì)信息。
我們類的構(gòu)造器有兩個(gè)參數(shù):遠(yuǎn)程主機(jī)的IP地址和端口號(hào)各一個(gè),而且構(gòu)造器將它們賦給實(shí)例變量。
我們的類有一個(gè) main() 方法和三個(gè)其它方法。稍后我們將探究這些方法的細(xì)節(jié)。現(xiàn)在您只需知道 setUpConnection() 將連接到遠(yuǎn)程服務(wù)器,getFile() 將向遠(yuǎn)程服務(wù)器請(qǐng)求 fileNameToGet 的內(nèi)容以及 tearDownConnection() 將從遠(yuǎn)程服務(wù)器上斷開(kāi)。
實(shí)現(xiàn) main()
這里我們實(shí)現(xiàn) main() 方法,它將創(chuàng)建 RemoteFileClient 并用它來(lái)獲取遠(yuǎn)程文件的內(nèi)容,然后打印結(jié)果。main() 方法用主機(jī)的 IP 地址和端口號(hào)實(shí)例化一個(gè)新 RemoteFileClient(客戶機(jī))。然后,我們告訴客戶機(jī)建立一個(gè)到主機(jī)的連接。接著,我們告訴客戶機(jī)獲取主機(jī)上一個(gè)指定文件的內(nèi)容。最后,我們告訴客戶機(jī)斷開(kāi)它到主機(jī)的連接。我們把文件內(nèi)容打印到控制臺(tái),只是為了證明一切都是按計(jì)劃進(jìn)行的。
建立連接
這里我們實(shí)現(xiàn) setUpConnection() 方法,它將創(chuàng)建我們的 Socket 并讓我們?cè)L問(wèn)該套接字的流:
- public void setUpConnection() {
- try {
- Socket client = new Socket(hostIp,hostPort);
- socketReader = new BufferedReader(new InputStreamReader(client.getInputStream()));
- socketWriter = new PrintWriter(client.getOutputStream());
- }
- catch(UnknownHostException e) {
- System.out.println("Error1 setting up socket connection: unknown host at "+hostIp+":"+hostPort);
- }
- catch(IOException e) {
- System.out.println("Error2 setting up socket connection: "+e);
- }
- }
setUpConnection() 方法用主機(jī)的 IP 地址和端口號(hào)創(chuàng)建一個(gè) Socket:
Socket client = new Socket(hostIp, hostPort);
我們把 Socket 的 InputStream 包裝進(jìn) BufferedReader 以使我們能夠讀取流的行。然后,我們把 Socket 的 OutputStream 包裝進(jìn) PrintWriter 以使我們能夠發(fā)送文件請(qǐng)求到服務(wù)器:
socketReader = new BufferedReader(new InputStreamReader(client.getInputStream()));socketWriter = new PrintWriter(client.getOutputStream());
請(qǐng)記住我們的客戶機(jī)和服務(wù)器只是來(lái)回傳送字節(jié)。客戶機(jī)和服務(wù)器都必須知道另一方即將發(fā)送的是什么以使它們能夠作出適當(dāng)?shù)捻憫?yīng)。在這個(gè)案例中,服務(wù)器知道我們將發(fā)送一條有效的文件路徑。
當(dāng)您實(shí)例化一個(gè) Socket 時(shí),將拋出 UnknownHostException。這里我們不特別處理它,但我們打印一些信息到控制臺(tái)以告訴我們發(fā)生了什么錯(cuò)誤。同樣地,當(dāng)我們?cè)噲D獲取 Socket 的 InputStream 或 OutputStream 時(shí),如果拋出了一個(gè)一般 IOException,我們也打印一些信息到控制臺(tái)。
與主機(jī)交談
這里我們實(shí)現(xiàn) getFile() 方法,它將告訴服務(wù)器我們想要什么文件并在服務(wù)器傳回其內(nèi)容時(shí)接收該內(nèi)容。
- public String getFile(String fileNameToGet) {
- StringBuffer fileLines = new StringBuffer();
- try {
- socketWriter.println(fileNameToGet);
- socketWriter.flush();
- String line = null;
- while((line=socketReader.readLine())!=null)
- fileLines.append(line+"\n");
- }
- catch(IOException e) {
- System.out.println("Error reading from file: "+fileNameToGet);
- }
- return fileLines.toString();
- }
對(duì)getFile()方法的調(diào)用要求一個(gè)有效的文件路徑String。它首先創(chuàng)建名為fileLines的 StringBuffer,fileLines 用于存儲(chǔ)我們讀自服務(wù)器上的文件的每一行。
StringBuffer fileLines = new StringBuffer();
在 try{}catch{} 塊中,我們用 PrintWriter 把請(qǐng)求發(fā)送到主機(jī),PrintWriter 是我們?cè)趧?chuàng)建連接期間建立的。
socketWriter.println(fileNameToGet); socketWriter.flush();
請(qǐng)注意這里我們是 flush() 該 PrintWriter,而不是關(guān)閉它。這迫使數(shù)據(jù)被發(fā)送到服務(wù)器而不關(guān)閉 Socket。
一旦我們已經(jīng)寫(xiě)到 Socket,我們就希望有一些響應(yīng)。我們不得不在 Socket 的 InputStream 上等待它,我們通過(guò)在 while 循環(huán)中調(diào)用 BufferedReader 上的 readLine() 來(lái)達(dá)到這個(gè)目的。我們把每一個(gè)返回行附加到 fileLines StringBuffer(帶有一個(gè)換行符以保護(hù)行):
String line = null; while((line=socketReader.readLine())!=null) fileLines.append(line+"\n");
斷開(kāi)連接
這里我們實(shí)現(xiàn) tearDownConnection() 方法,它將在我們使用完畢連接后負(fù)責(zé)“清除”。tearDownConnection()方法只是分別關(guān)閉我們?cè)赟ocket的InputStream和OutputStream上創(chuàng)建的 BufferedReader和PrintWriter。這樣做會(huì)關(guān)閉我們從Socket獲取的底層流,所以我們必須捕捉可能的 IOException。
總結(jié)一下客戶機(jī)
我們的類研究完了。在我們繼續(xù)往前討論服務(wù)器端的情況之前,讓我們回顧一下創(chuàng)建和使用 Socket 的步驟:
1. 用您想連接的機(jī)器的 IP 地址和端口實(shí)例化 Socket(如有問(wèn)題則拋出 Exception)。
2. 獲取 Socket 上的流以進(jìn)行讀寫(xiě)。
3. 把流包裝進(jìn) BufferedReader/PrintWriter 的實(shí)例,如果這樣做能使事情更簡(jiǎn)單的話。
4. 對(duì) Socket 進(jìn)行讀寫(xiě)。
5. 關(guān)閉打開(kāi)的流。
5 創(chuàng)建服務(wù)器Socket
創(chuàng)建 RemoteFileServer 類
- import java.io.*;
- import java.net.*;
- public class RemoteFileServer {
- int listenPort;
- public RemoteFileServer(int listenPort) {
- this.listenPort=listenPort;
- }
-
- public void acceptConnections() {
- try {
- ServerSocket server = new ServerSocket(listenPort);
- Socket incomingConnection = null;
- while(true) {
- incomingConnection = server.accept();
- handleConnection(incomingConnection);
- }
- }
- catch(BindException e) {
- System.out.println("Unable to bind to port "+listenPort);
- }
- catch(IOException e) {
- System.out.println("Unable to instantiate a ServerSocket on port: "+listenPort);
-
- }
- }
-
- public void handleConnection(Socket incomingConnection) {
- try {
- OutputStream outputToSocket = incomingConnection.getOutputStream();
- InputStream inputFromSocket = incomingConnection.getInputStream();
- BufferedReader streamReader = new BufferedReader(new InputStreamReader(inputFromSocket));
- FileReader fileReader = new FileReader(new File(streamReader.readLine()));
- BufferedReader bufferedFileReader = new BufferedReader(fileReader);
- PrintWriter streamWriter = new PrintWriter(incomingConnection.getOutputStream());
- String line = null;
- while((line=bufferedFileReader.readLine())!=null){
- streamWriter.println(line);
- }
- fileReader.close();
- streamWriter.close();
- streamReader.close();
- }
- catch(Exception e) {
- System.out.println("Error handling a client: "+e);
- e.printStackTrace();
- }
- }
- public static void main(String args[]) {
- RemoteFileServer server = new RemoteFileServer(1001);
- server.acceptConnections();
- }
- }
跟客戶機(jī)中一樣,我們首先導(dǎo)入java.net的java.io。接著,我們給我們的類一個(gè)實(shí)例變量以保存端口,我們從該端口偵聽(tīng)進(jìn)入的連接。缺省情況下,端口是1001。
我們的類有一個(gè)main()方法和兩個(gè)其它方法。稍后我們將探究這些方法的細(xì)節(jié)。現(xiàn)在您只需知道acceptConnections()將允許客戶機(jī)連接到服務(wù)器以及handleConnection()與客戶機(jī)Socket交互以將您所請(qǐng)求的文件的內(nèi)容發(fā)送到客戶機(jī)。
實(shí)現(xiàn) main()
這里我們實(shí)現(xiàn)main()方法,它將創(chuàng)建RemoteFileServer并告訴它接受連接:服務(wù)器端的main()方法中,我們實(shí)例化一個(gè)新 RemoteFileServer,它將在偵聽(tīng)端口(1001)上偵聽(tīng)進(jìn)入的連接請(qǐng)求。然后我們調(diào)用acceptConnections()來(lái)告訴該 server進(jìn)行偵聽(tīng)。
接受連接
這里我們實(shí)現(xiàn) acceptConnections() 方法,它將創(chuàng)建一個(gè) ServerSocket 并等待連接請(qǐng)求:
- public void acceptConnections() {
- try {
- ServerSocket server = new ServerSocket(listenPort);
- Socket incomingConnection = null;
- while(true) {
- incomingConnection = server.accept();
- handleConnection(incomingConnection);
- }
- }
- catch(BindException e) {
- System.out.println("Unable to bind to port "+listenPort);
- }
- catch(IOException e) {
- System.out.println("Unable to instantiate a ServerSocket on port: "+listenPort);
-
- }
- }
acceptConnections()用欲偵聽(tīng)的端口號(hào)來(lái)創(chuàng)建ServerSocket。然后我們通過(guò)調(diào)用該ServerSocket的 accept()來(lái)告訴它開(kāi)始偵聽(tīng)。accept()方法將造成阻塞直到來(lái)了一個(gè)連接請(qǐng)求。此時(shí),accept()返回一個(gè)新的Socket,這個(gè) Socket綁定到服務(wù)器上一個(gè)隨機(jī)指定的端口,返回的Socket被傳遞給handleConnection()。請(qǐng)注意我們?cè)谝粋€(gè)無(wú)限循環(huán)中處理對(duì)連接的接受。這里不支持任何關(guān)機(jī)。
無(wú)論何時(shí)如果您創(chuàng)建了一個(gè)無(wú)法綁定到指定端口(可能是因?yàn)閯e的什么控制了該端口)的 ServerSocket,Java代碼都將拋出一個(gè)錯(cuò)誤。所以這里我們必須捕捉可能的BindException。就跟在客戶機(jī)端上時(shí)一樣,我們必須捕捉IOException,當(dāng)我們?cè)噲D在ServerSocket上接受連接時(shí),它就會(huì)被拋出。請(qǐng)注意,您可以通過(guò)用毫秒數(shù)調(diào)用 setSoTimeout()來(lái)為accept()調(diào)用設(shè)置超時(shí),以避免實(shí)際長(zhǎng)時(shí)間的等待。調(diào)用setSoTimeout()將使accept()經(jīng)過(guò)指定占用時(shí)間后拋出IOException。
處理連接
這里我們實(shí)現(xiàn)handleConnection()方法,它將用連接的流來(lái)接收輸入和寫(xiě)輸出:
- public void handleConnection(Socket incomingConnection) {
- try {
- OutputStream outputToSocket = incomingConnection.getOutputStream();
- InputStream inputFromSocket = incomingConnection.getInputStream();
- BufferedReader streamReader = new BufferedReader(new InputStreamReader(inputFromSocket));
- FileReader fileReader = new FileReader(new File(streamReader.readLine()));
- BufferedReader bufferedFileReader = new BufferedReader(fileReader);
- PrintWriter streamWriter = new PrintWriter(incomingConnection.getOutputStream());
- String line = null;
- while((line=bufferedFileReader.readLine())!=null){
- streamWriter.println(line);
- }
- fileReader.close();
- streamWriter.close();
- streamReader.close();
- }
- catch(Exception e) {
- System.out.println("Error handling a client: "+e);
- e.printStackTrace();
- }
- }
跟在客戶機(jī)中一樣,我們用getOutputStream()和getInputStream()來(lái)獲取與我們剛創(chuàng)建的Socket相關(guān)聯(lián)的流。跟在客戶機(jī)端一樣,我們把InputStream包裝進(jìn)BufferedReader,把OutputStream包裝進(jìn)PrintWriter。在服務(wù)器端上,我們需要添加一些代碼,用來(lái)讀取目標(biāo)文件和把內(nèi)容逐行發(fā)送到客戶機(jī)。這里是重要的代碼:
FileReader fileReader = new FileReader(new File(streamReader.readLine())); BufferedReader bufferedFileReader = new BufferedReader(fileReader); String line = null; while((line=bufferedFileReader.readLine())!=null) { streamWriter.println(line); }
這些代碼值得詳細(xì)解釋。讓我們一點(diǎn)一點(diǎn)來(lái)看:
FileReader fileReader = new FileReader(new File(streamReader.readLine()));
首先,我們使用Socket 的InputStream的BufferedReader。我們應(yīng)該獲取一條有效的文件路徑,所以我們用該路徑名構(gòu)造一個(gè)新File。我們創(chuàng)建一個(gè)新FileReader來(lái)處理讀文件的操作。
BufferedReader bufferedFileReader = new BufferedReader(fileReader);
這里我們把FileReader包裝進(jìn)BufferedReader以使我們能夠逐行地讀該文件。
接著,我們調(diào)用BufferedReader的readLine()。這個(gè)調(diào)用將造成阻塞直到有字節(jié)到來(lái)。我們獲取一些字節(jié)之后就把它們放到本地的line變量中,然后再寫(xiě)出到客戶機(jī)上。完成讀寫(xiě)操作之后,我們就關(guān)閉打開(kāi)的流。
請(qǐng)注意我們?cè)谕瓿蓮腟ocket的讀操作之后關(guān)閉streamWriter和streamReader。您或許會(huì)問(wèn)我們?yōu)槭裁床辉谧x取文件名之后立刻關(guān)閉 streamReader。原因是當(dāng)您這樣做時(shí),您的客戶機(jī)將不會(huì)獲取任何數(shù)據(jù)。如果您在關(guān)閉streamWriter之前關(guān)閉 streamReader,則您可以往Socket寫(xiě)任何東西,但卻沒(méi)有任何數(shù)據(jù)能通過(guò)通道(通道被關(guān)閉了)。
總結(jié)一下服務(wù)器
在我們接著討論另一個(gè)更實(shí)際的示例之前,讓我們回顧一下創(chuàng)建和使用ServerSocket的步驟:
1. 用一個(gè)您想讓它偵聽(tīng)傳入客戶機(jī)連接的端口來(lái)實(shí)例化一個(gè)ServerSocket(如有問(wèn)題則拋出 Exception)。
2. 調(diào)用ServerSocket的accept()以在等待連接期間造成阻塞。
3. 獲取位于該底層Socket的流以進(jìn)行讀寫(xiě)操作。
4. 按使事情簡(jiǎn)單化的原則包裝流。
5. 對(duì)Socket進(jìn)行讀寫(xiě)。
6. 關(guān)閉打開(kāi)的流(并請(qǐng)記住,永遠(yuǎn)不要在關(guān)閉Writer之前關(guān)閉Reader)。
6 創(chuàng)建多線程Socket服務(wù)器
前面的示例教給您基礎(chǔ)知識(shí),但并不能令您更深入。如果您到此就停止了,那么您一次只能處理一臺(tái)客戶機(jī)。原因是handleConnection()是一個(gè)阻塞方法。只有當(dāng)它完成了對(duì)當(dāng)前連接的處理時(shí),服務(wù)器才能接受另一個(gè)客戶機(jī)。在多數(shù)時(shí)候,您將需要(也有必要)一個(gè)多線程服務(wù)器。
創(chuàng)建 MultithreadedRemoteFileServer 類
- import java.io.*;
- import java.net.*;
- public class MultithreadedRemoteFileServer {
- int listenPort;
- public MultithreadedRemoteFileServer(int listenPort) {
- this.listenPort=listenPort;
- }
-
- public void acceptConnections() {
- try {
- ServerSocket server = new ServerSocket(listenPort, 5);
- Socket incomingConnection = null;
- while(true) {
- incomingConnection = server.accept();
- handleConnection(incomingConnection);
- }
- }
- catch(BindException e) {
- System.out.println("Unable to bind to port "+listenPort);
- }
- catch(IOException e) {
- System.out.println("Unable to instantiate a ServerSocket on port: "+listenPort);
- }
- }
-
- public void handleConnection(Socket connectionToHandle) {
- new Thread(new ConnectionHandler(connectionToHandle)).start();
- }
- public static void main(String args[]) {
- MultithreadedRemoteFileServer server = new MultithreadedRemoteFileServer(1001);
- server.acceptConnections();
- }
- }
這里我們實(shí)現(xiàn)改動(dòng)過(guò)acceptConnections()方法,它將創(chuàng)建一個(gè)能夠處理待發(fā)請(qǐng)求的ServerSocket,并告訴ServerSocket接受連接。
新的 server 仍然需要acceptConnections(),所以這些代碼實(shí)際上是一樣的。突出顯示的行表示一個(gè)重大的不同。對(duì)這個(gè)多線程版,我們現(xiàn)在可以指定客戶機(jī)請(qǐng)求的最大數(shù)目,這些請(qǐng)求都能在實(shí)例化ServerSocket期間處于待發(fā)狀態(tài)。如果我們沒(méi)有指定客戶機(jī)請(qǐng)求的最大數(shù)目,則我們假設(shè)使用缺省值50。
這里是它的工作機(jī)制。假設(shè)我們指定待發(fā)數(shù)(backlog 值)是5并且有五臺(tái)客戶機(jī)請(qǐng)求連接到我們的服務(wù)器。我們的服務(wù)器將著手處理第一個(gè)連接,但處理該連接需要很長(zhǎng)時(shí)間。由于我們的待發(fā)值是5,所以我們一次可以放五個(gè)請(qǐng)求到隊(duì)列中。我們正在處理一個(gè),所以這意味著還有其它五個(gè)正在等待。等待的和正在處理的一共有六個(gè)。當(dāng)我們的服務(wù)器仍忙于接受一號(hào)連接(記住隊(duì)列中還有 2?6 號(hào))時(shí),如果有第七個(gè)客戶機(jī)提出連接申請(qǐng),那么,該第七個(gè)客戶機(jī)將遭到拒絕。我們將在帶有連接池服務(wù)器示例中說(shuō)明如何限定能同時(shí)連接的客戶機(jī)數(shù)目。
處理連接:
public void handleConnection(Socket connectionToHandle) {
new Thread(new ConnectionHandler(connectionToHandle)).start();
}
我們對(duì)RemoteFileServer所做的大改動(dòng)就體現(xiàn)在這個(gè)方法上。我們?nèi)匀辉诜?wù)器接受一個(gè)連接之后調(diào)用handleConnection(),但現(xiàn)在我們把該Socket傳遞給ConnectionHandler的一個(gè)實(shí)例,它是 Runnable的。我們用ConnectionHandler創(chuàng)建一個(gè)新 Thread 并啟動(dòng)它。ConnectionHandler的run()方法包Socket讀/寫(xiě)和讀File的代碼,這些代碼原來(lái)在 RemoteFileServer的handleConnection()中。
創(chuàng)建 ConnectionHandler 類
- import java.io.*;
- import java.net.*;
- public class ConnectionHandler implements Runnable {
- protected Socket socketToHandle;
- public ConnectionHandler(Socket socketToHandle) {
- this.socketToHandle=socketToHandle;
- }
- public void run() {
- try {
- PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream());
- BufferedReader streamReader = new BufferedReader(new InputStreamReader(socketToHandle.getInputStream()));
- String fileToRead = streamReader.readLine();
- BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));
- String line =null;
- while((line=fileReader.readLine())!=null) {
- streamWriter.println(line);
- }
- fileReader.close();
- streamWriter.close();
- streamReader.close();
- }
- catch(Exception e) {
- System.out.println("Error handling a client: "+e);
- e.printStackTrace();
- }
- }
- }
這個(gè)助手類相當(dāng)簡(jiǎn)單。跟我們到目前為止的其它類一樣,我們導(dǎo)入java.net和java.io。該類只有一個(gè)實(shí)例變量socketToHandle,它保存由該實(shí)例處理的Socket。
類的構(gòu)造器用一個(gè)Socket實(shí)例作參數(shù)并將它賦給socketToHandle。
請(qǐng)注意該類實(shí)現(xiàn)了Runnable接口。實(shí)現(xiàn)這個(gè)接口的類都必須實(shí)現(xiàn)run()方法。這里我們實(shí)現(xiàn)run()方法,它將攫取我們的連接的流,用它來(lái)讀寫(xiě)該連接,并在任務(wù)完成之后關(guān)閉它。ConnectionHandler的run()方法所做的事情就是RemoteFileServer上的 handleConnection()所做的事情。首先,我們把InputStream和OutputStream分別包裝(用Socket的 getOutputStream()和 getInputStream())進(jìn)BufferedReader和PrintWriter。然后我們用這些代碼逐行地讀目標(biāo)文件:
- PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream());
- BufferedReader streamReader = new BufferedReader(new InputStreamReader(socketToHandle.getInputStream()));
- String fileToRead = streamReader.readLine();
- BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));
- String line =null;
- while((line=fileReader.readLine())!=null) {
- streamWriter.println(line);
- }
請(qǐng)記住我們應(yīng)該從客戶機(jī)獲取一條有效的文件路徑,這樣用該路徑名構(gòu)造一個(gè)新File,把它包裝進(jìn)FileReader以處理讀文件的操作,然后把它包裝進(jìn) BufferedReader以讓我們逐行地讀該文件。我們while循環(huán)中調(diào)用BufferedReader上的readLine()直到不再有要讀的行。請(qǐng)記注,對(duì)readLine()的調(diào)用將造成阻塞,直到有字節(jié)來(lái)到為止。我們獲取一些字節(jié)之后就把它們放到本地的line變量中,然后寫(xiě)出到客戶機(jī)上。完成讀寫(xiě)操作之后,我們關(guān)閉打開(kāi)的流。
總結(jié)一下多線程服務(wù)器
讓我們回顧一下創(chuàng)建和使用“多線程版”的服務(wù)器的步驟:
1. 修改 acceptConnections() 以用缺省為 50(或任何您想要的大于 1 的指定數(shù)字)實(shí)例化 ServerSocket。
2. 修改 ServerSocket 的 handleConnection() 以用 ConnectionHandler 的一個(gè)實(shí)例生成一個(gè)新的 Thread。
3. 借用 RemoteFileServer 的 handleConnection() 方法的代碼實(shí)現(xiàn) ConnectionHandler 類。
7 創(chuàng)建帶有連接池的Socket服務(wù)器
我們現(xiàn)在已經(jīng)擁有的 MultithreadedServer 每當(dāng)有客戶機(jī)申請(qǐng)一個(gè)連接時(shí)都在一個(gè)新Thread中創(chuàng)建一個(gè)新 ConnectionHandler。這意味著可能有一捆Thread“躺”在我們周圍。而且創(chuàng)建Thread的系統(tǒng)開(kāi)銷并不是微不足道的。如果性能成為了問(wèn)題(也請(qǐng)不要事到臨頭才意識(shí)到它),更高效地處理我們的服務(wù)器是件好事。那么,我們?nèi)绾胃咝У毓芾矸?wù)器端呢?我們可以維護(hù)一個(gè)進(jìn)入的連接池,一定數(shù)量的ConnectionHandler將為它提供服務(wù)。這種設(shè)計(jì)能帶來(lái)以下好處:
• 它限定了允許同時(shí)連接的數(shù)目。
• 我們只需啟動(dòng)ConnectionHandler Thread一次。
幸運(yùn)的是,跟在我們的多線程示例中一樣,往代碼中添加“池”不需要來(lái)一個(gè)大改動(dòng)。事實(shí)上,應(yīng)用程序的客戶機(jī)端根本就不受影響。在服務(wù)器端,我們?cè)诜?wù)器啟動(dòng)時(shí)創(chuàng)建一定數(shù)量的 ConnectionHandler,我們把進(jìn)入的連接放入“池”中并讓ConnectionHandler打理剩下的事情。這種設(shè)計(jì)中有很多我們不打算討論的可能存在的技巧。例如,我們可以通過(guò)限定允許在“池”中建立的連接的數(shù)目來(lái)拒絕客戶機(jī)。
請(qǐng)注意:我們將不會(huì)再次討論acceptConnections()。這個(gè)方法跟前面示例中的完全一樣。它無(wú)限循環(huán)地調(diào)用ServerSocket上的 accept() 并把連接傳遞到handleConnection()。
創(chuàng)建 PooledRemoteFileServer 類
- import java.io.*;
- import java.net.*;
- import java.util.*;
- public class PooledRemoteFileServer {
- protected int maxConnections;
- protected int listenPort;
- protected ServerSocket serverSocket;
- public PooledRemoteFileServer(int aListenPort, int maxConnections) {
- listenPort= aListenPort;
- this.maxConnections = maxConnections;
- }
- public void acceptConnections() {
- try {
- ServerSocket server = new ServerSocket(listenPort, 5);
- Socket incomingConnection = null;
- while(true) {
- incomingConnection = server.accept();
- handleConnection(incomingConnection);
- }
- }
- catch(BindException e) {
- System.out.println("");
- }
- catch(IOException e) {
- System.out.println(""+listenPort);
- }
- }
- protected void handleConnection(Socket connectionToHandle) {
- PooledConnectionHandler.processRequest(connectionToHandle);
- }
- public void setUpHandlers() {
- for(int i=0; i<maxConnections; i++) {
- PooledConnectionHandler currentHandler = new PooledConnectionHandler();
- new Thread(currentHandler, "Handler " + i).start();
- }
- }
- public static void main(String args[]) {
- PooledRemoteFileServer server = new PooledRemoteFileServer(1001, 3);
- server.setUpHandlers();
- server.acceptConnections();
- }
- }
請(qǐng)注意一下您現(xiàn)在應(yīng)該熟悉了的 import 語(yǔ)句。我們給類以下實(shí)例變量以保存:
• 我們的服務(wù)器能同時(shí)處理的活動(dòng)客戶機(jī)連接的最大數(shù)目
• 進(jìn)入的連接的偵聽(tīng)端口(我們沒(méi)有指定缺省值,但如果您想這樣做,并不會(huì)受到限制)
• 將接受客戶機(jī)連接請(qǐng)求的 ServerSocket
類的構(gòu)造器用的參數(shù)是偵聽(tīng)端口和連接的最大數(shù)目
我們的類有一個(gè) main() 方法和三個(gè)其它方法。稍后我們將探究這些方法的細(xì)節(jié)。現(xiàn)在只須知道setUpHandlers()創(chuàng)建數(shù)目為 maxConnections的大量PooledConnectionHandler,而其它兩個(gè)方法則與我們前面已經(jīng)看到的相似:acceptConnections()在ServerSocket上偵聽(tīng)傳入的客戶機(jī)連接,而handleConnection則在客戶機(jī)連接一旦被建立后就實(shí)際處理它。
實(shí)現(xiàn) main()
這里我們實(shí)現(xiàn)需作改動(dòng)的main()方法,該方法將創(chuàng)建能夠處理給定數(shù)目的客戶機(jī)連接的PooledRemoteFileServer,并告訴它接受連接:
public static void main(String args[]) {
PooledRemoteFileServer server = new PooledRemoteFileServer(1001, 3);
server.setUpHandlers();
server.acceptConnections();
}
我們的main()方法很簡(jiǎn)單。我們實(shí)例化一個(gè)新的PooledRemoteFileServer,它將通過(guò)調(diào)用setUpHandlers()來(lái)建立三個(gè) PooledConnectionHandler。一旦服務(wù)器就緒,我們就告訴它acceptConnections()。
建立連接處理程序
public void setUpHandlers() {
for(int i=0; i<maxConnections; i++) {
PooledConnectionHandler currentHandler = new PooledConnectionHandler();
new Thread(currentHandler, "Handler " + i).start();
}
}
setUpHandlers() 方法創(chuàng)建maxConnections(例如 3)個(gè)PooledConnectionHandler并在新Thread中激活它們。用實(shí)現(xiàn)了 Runnable的對(duì)象來(lái)創(chuàng)建Thread使我們可以在Thread調(diào)用start()并且可以期望在Runnable上調(diào)用了run()。換句話說(shuō),我們的PooledConnectionHandler將等著處理進(jìn)入的連接,每個(gè)都在它自己的Thread中進(jìn)行。我們?cè)谑纠兄粍?chuàng)建三個(gè)Thread,而且一旦服務(wù)器運(yùn)行,這就不能被改變。
處理連接
這里我們實(shí)現(xiàn)需作改動(dòng)的handleConnections()方法,它將委派PooledConnectionHandler處理連接:
protected void handleConnection(Socket connectionToHandle) {
PooledConnectionHandler.processRequest(connectionToHandle);
}
我們現(xiàn)在叫 PooledConnectionHandler 處理所有進(jìn)入的連接(processRequest() 是一個(gè)靜態(tài)方法)。
創(chuàng)建 PooledRemoteFileServer 類
- import java.io.*;
- import java.net.*;
- import java.util.*;
- public class PooledConnectionHandler implements Runnable {
- protected Socket connection;
- protected static List pool = new LinkedList();
- public PooledConnectionHandler() {}
- public void handleConnection() {
- try {
- PrintWriter streamWriter = new PrintWriter(connection.getOutputStream());
- BufferedReader streamReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
- String fileToRead = streamReader.readLine();
- BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));
- String line = null;
- while((line=fileReader.readLine())!=null)
- streamWriter.println(line);
- fileReader.close();
- streamWriter.close();
- streamReader.close();
- }
- catch(FileNotFoundException e) {
- System.out.println("");
- }
- catch(IOException e) {
- System.out.println(""+e);
- }
- }
- public static void processRequest(Socket requestToHandle) {
- synchronized(pool) {
- pool.add(pool.size(), requestToHandle);
- pool.notifyAll();
- }
- }
- public void run() {
- while(true) {
- synchronized(pool) {
- while(pool.isEmpty()) {
- try {
- pool.wait();
- }
- catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- connection= (Socket)pool.remove(0);
- }
- handleConnection();
- }
- }
- }
這個(gè)助手類與 ConnectionHandler 非常相似,但它帶有處理連接池的手段。該類有兩個(gè)實(shí)例變量:
• connection 是當(dāng)前正在處理的 Socket
• 名為 pool 的靜態(tài) LinkedList 保存需被處理的連接
填充連接池
這里我們實(shí)現(xiàn)PooledConnectionHandler上的processRequest()方法,它將把傳入請(qǐng)求添加到池中,并告訴其它正在等待的對(duì)象該池已經(jīng)有一些內(nèi)容:
public static void processRequest(Socket requestToHandle) {
synchronized(pool) {
pool.add(pool.size(), requestToHandle);
pool.notifyAll();
}
}
synchronized 塊是個(gè)稍微有些不同的東西。您可以同步任何對(duì)象上的一個(gè)塊,而不只是在本身的某個(gè)方法中含有該塊的對(duì)象。在我們的示例中,processRequest() 方法包含有一個(gè) pool(請(qǐng)記住它是一個(gè) LinkedList,保存等待處理的連接池)的 synchronized塊。我們這樣做的原因是確保沒(méi)有別人能跟我們同時(shí)修改連接池。
既然我們已經(jīng)保證了我們是唯一“涉水”池中的人,我們就可以把傳入的Socket添加到LinkedList的尾端。一旦我們添加了新的連接,我們就用以下代碼通知其它正在等待該池的Thread,池現(xiàn)在已經(jīng)可用:
pool.notifyAll();
Object的所有子類都繼承這個(gè)notifyAll()方法。這個(gè)方法,連同我們下一屏將要討論的wait()方法一起,就使一個(gè)Thread能夠讓另一個(gè)Thread知道一些條件已經(jīng)具備。這意味著該第二個(gè)Thread一定正在等待那些條件的滿足。
從池中獲取連接
這里我們實(shí)現(xiàn)PooledConnectionHandler上需作改動(dòng)的run()方法,它將在連接池上等待,并且池中一有連接就處理它:
public void run() {
while(true) {
synchronized(pool) {
while(pool.isEmpty()) {
try {
pool.wait();
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
connection= (Socket)pool.remove(0);
}
handleConnection();
}
}
回想一下在前面講過(guò)的:一個(gè)Thread正在等待有人通知它連接池方面的條件已經(jīng)滿足了。在我們的示例中,請(qǐng)記住我們有三個(gè) PooledConnectionHandler在等待使用池中的連接。每個(gè)PooledConnectionHandler都在它自已的Thread中運(yùn)行,并通過(guò)調(diào)用pool.wait()產(chǎn)生阻塞。當(dāng)我們的processRequest()在連接池上調(diào)用notifyAll()時(shí),所有正在等待的 PooledConnectionHandler都將得到“池已經(jīng)可用”的通知。然后各自繼續(xù)前行調(diào)用pool.wait(),并重新檢查 while(pool.isEmpty())循環(huán)條件。除了一個(gè)處理程序,其它池對(duì)所有處理程序都將是空的,因此,在調(diào)用pool.wait()時(shí),除了一個(gè)處理程序,其它所有處理程序都將再次產(chǎn)生阻塞。恰巧碰上非空池的處理程序?qū)⑻鰓hile(pool.isEmpty())循環(huán)并攫取池中的第一個(gè)連接:
connection= (Socket)pool.remove(0);
處理程序一旦有一個(gè)連接可以使用,就調(diào)用 handleConnection() 處理它。
在我們的示例中,池中可能永遠(yuǎn)不會(huì)有多個(gè)連接,只是因?yàn)槭虑楹芸炀捅惶幚淼袅恕H绻刂杏幸粋€(gè)以上連接,那么其它處理程序?qū)⒉槐氐却碌倪B接被添加到池。當(dāng)它們檢查pool.isEmpty()條件時(shí),將發(fā)現(xiàn)其值為假,然后就從池中攫取一個(gè)連接并處理它。
還有另一件事需注意。當(dāng)run()擁有池的互斥鎖時(shí),processRequest()如何能夠把連接放到池中呢?答案是對(duì)池上的wait()的調(diào)用釋放鎖,而wait()接著就在自己返回之前再次攫取該鎖。這就使得池對(duì)象的其它同步代碼可以獲取該鎖。
處理連接:再一次
這里我們實(shí)現(xiàn)需做改動(dòng)的handleConnection()方法,該方法將攫取連接的流,使用它們,并在任務(wù)完成之后清除它們:
- public void handleConnection() {
- try {
- PrintWriter streamWriter = new PrintWriter(connection.getOutputStream());
- BufferedReader streamReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
- String fileToRead = streamReader.readLine();
- BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));
- String line = null;
- while((line=fileReader.readLine())!=null)
- streamWriter.println(line);
- fileReader.close();
- streamWriter.close();
- streamReader.close();
- }
- catch(FileNotFoundException e) {
- System.out.println("");
- }
- catch(IOException e) {
- System.out.println(""+e);
- }
- }
跟在多線程服務(wù)器中不同,我們的PooledConnectionHandler有一個(gè)handleConnection()方法。這個(gè)方法的代碼跟非池式的ConnectionHandler上的run()方法的代碼完全一樣。首先,我們把OutputStream和InputStream分別包裝進(jìn)(用Socket上的getOutputStream()和getInputStream())BufferedReader和PrintWriter。然后我們逐行讀目標(biāo)文件,就象我們?cè)诙嗑€程示例中做的那樣。再一次,我們獲取一些字節(jié)之后就把它們放到本地的line變量中,然后寫(xiě)出到客戶機(jī)。完成讀寫(xiě)操作之后,我們關(guān)閉FileReader和打開(kāi)的流。
總結(jié)一下帶有連接池的服務(wù)器
讓我們回顧一下創(chuàng)建和使用“池版”服務(wù)器的步驟:
1. 創(chuàng)建一個(gè)新種類的連接處理程序(我們稱之為 PooledConnectionHandler)來(lái)處理池中的連接。
2. 修改服務(wù)器以創(chuàng)建和使用一組 PooledConnectionHandler。
Java 語(yǔ)言簡(jiǎn)化了套接字在應(yīng)用程序中的使用。它的基礎(chǔ)實(shí)際上是 java.net 包中的 Socket 和 ServerSocket 類。一旦您理解了表象背后發(fā)生的情況,就能容易地使用這些類。在現(xiàn)實(shí)生活中使用套接字只是這樣一件事,即通過(guò)貫徹優(yōu)秀的 OO 設(shè)計(jì)原則來(lái)保護(hù)應(yīng)用程序中各層間的封裝。我們?yōu)槟故玖艘恍┯袔椭念悺_@些類的結(jié)構(gòu)對(duì)我們的應(yīng)用程序隱藏了 Socket 交互作用的低級(jí)細(xì)節(jié) ? 使應(yīng)用程序能只使用可插入的 ClientSocketFacade 和 ServerSocketFacade。在有些地方(在 Facade 內(nèi)),您仍然必須管理稍顯雜亂的字節(jié)細(xì)節(jié),但您只須做一次就可以了。更好的是,您可以在將來(lái)的項(xiàng)目中重用這些低級(jí)別的助手類
posted on 2008-12-11 23:13
一葉笑天 閱讀(206)
評(píng)論(0) 編輯 收藏 所屬分類:
JAVA技術(shù)