Java网络通信程序的设计课件_第1页
Java网络通信程序的设计课件_第2页
Java网络通信程序的设计课件_第3页
Java网络通信程序的设计课件_第4页
Java网络通信程序的设计课件_第5页
已阅读5页,还剩114页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Java網路通信程式的設計

11.1處理URL內容URL(UniformResourceLocator)是Internet的關鍵部分,它提供了人和機器的導航,其功能是指向電腦裏的資源,即定位。URL可以分成三個部分:通信協議、電腦地址和文件。URL常見的通信協議有三種:http,ftp和file。所謂通信協議,就是客戶端電腦與伺服器端電腦在網路上通信的方法。

有時候在地址後面還要指定使用哪一個端口(Port),例如:80/index.html。如果URL沒有指定使用哪一個端口,則會根據通信協議使用默認的端口。一般地,http協議默認端口為80,ftp協議默認端口為21。

包中包含兩個專門用於URL的關鍵類,即URL和URLConnection。URL和URLConnection類封裝了檢索遠程站點資訊的操作,因而大大地降低了這些操作的複雜性。下麵幾節將介紹這兩個類。11.1.1URL類的基本方法

URL類提供的最基本的網路功能是以流的形式讀取URL所指的的數據。URL類的實例可以用表示URL的文本串來建立,以表示URL所指的數據。構造一個URL類實例的最簡單方法是為URL構造方法賦予一個字串:

URLurl=newURL(/index.html);

這被稱為“絕對”URL,因為賦予的字串指定了從協議到資源名的全部內容。另一種URL類的構造方法是構造一個“相對”URL: URLdata=newURL(url,"data/data.html");

這種構造方法指定了位於url的data子目錄中的data.html檔,它的絕對地址應該是/data/data.html。這兩種構造方法都可以指定一個URL,如果指定的URL是錯誤的,構造方法會拋出一個運行時錯誤:MalformedURLException,這個Excetion通知用戶構造了一個形式錯誤的URL。

注:URL類既支持http協議,也支持ftp和file協議。如果URL文本有錯或者Java平臺不支持其協議部分,則這個構造函數拋出一個MalformedURLException,該Exception是java.io.IOException的子類,指出給定的是不合法的URL。通常應通過try-catch塊處理或聲明讓調用方法傳遞這個異常。URL類常用的構造函數有下列三種:●URL(Stringspec)throwsMalformedURLException

創建一個由spec指定的URL類的實例。●URL(Stringspec,Stringhost,intport,Stringfile)throwsMalformedURLException

創建一個URL類的實例,分別指定其通信協議(protocal)、電腦地址(host)、連接端口(port)和文件(file)。如果port值是-1,則表示使用默認端口。●URL(Stringprotocal,Stringhost,Stringfile)throwsMalformedURLException功能同上,但沒有指定端口,即使用默認的端口。URL類的一些主要方法如下:●publicStringgetFile():返回URL中的檔部分。●publicStringgetHost():返回URL中的電腦地址部分。●publicintgetPort():返回URL中所使用的端口。●publicStringgetProtocal():返回URL中通信協議的部分。下麵是一個使用URL類及其方法的例子。例11.1URLDemo.javaimportgenesis.*;import.*;

publicclassURLDemo{ publicstaticvoidmain(Stringargs[]) { try {//創建一個指向首頁的URL類的實例

URLurl=newURL("/index.html"); Transcript.println("Protocol:"+url.getProtocol()); Transcript.println("Host:"+url.getHost()); Transcript.println("Port:"+url.getPort()); Transcript.println("File:"+url.getFile()); } catch(MalformedURLExceptione) { Transcript.println("錯誤的URL!");} }}

這個例子很簡單,因為沒有指定URL的連接端口,所以顯示值為-1。實際連接時,會根據通信協議而決定使用哪一個端口。它的運行結果如圖11.1所示。

圖11.111.1.2用URL類實現頁面的訪問一旦構造了URL,就可以用URL類中的openStream()方法讀取URL描述的數據。openStream()打開一個到URL類指定資源的連接,並返回一個InputStream對象。利用這個對象,可以方便的讀取資源的內容,也可以鏈接到其他類型的輸入流和讀取器上。我們來看一個讀取頁面內容的例子。例11.2GetPage.javaimportgenesis.*;importjava.io.*;import.*;publicclassGetPage{ publicstaticvoidmain(Stringargs[]) { try { URLurl=newURL("/index.html"); InputStreamin=url.openStream(); BufferedReaderreader= newBufferedReader(newInputStreamReader(in));//打開index.hmtl檔為寫做準備

FileWriterfw=newFileWriter("index.html"); PrintWriterpw=newPrintWriter(fw); Stringline; //逐行讀入頁面內容

while((line=reader.readLine())!=null) { //將讀入的行保存到index.html檔

pw.println(line); //將讀入的行顯示在窗口中

Transcript.println(line);}

reader.close(); pw.close(); fw.close(); } catch(IOExceptione) { Transcript.println(e.getMessage()); } }}程式的運行結果如圖11.2所示。

圖11.2

上面的例子通過讀取的首頁index.html,展示了怎樣通過URL訪問Web頁面的內容。圖11.2窗口中顯示的就是index.html頁面所包含的內容(即它的源碼)。該例中將讀入的內容保存在當前目錄下,名為index.html的檔中,用流覽器打開這個檔,可以看到如同訪問的頁面。但是,無法看到頁面中的圖片,因為打開的輸入流只讀入了頁面的內容,並沒有將圖片鏈接也讀入進來。打開index.html檔將看到如圖11.3所示的頁面。圖11.311.1.3用URLConnection類實現頁面的訪問我們已經知道了如何通過URL類訪問URL資源,但如果想瞭解關於這個資源的更多資訊,就需要使用URLConnection類。URLConnection類提供了訪問網路資源時更多、更好的控制方法。使用URLConnection類來訪問Web頁面的步驟如下:

(1)調用URL類的openConnection()方法得到一個URLConnection類的實例:

URLConnectionconn=url.openConnection();(2)調用以下方法,設置所有相關屬性:①setAllowUserInteraction()②setDoInput()③setDoOutput()④setIfModifiedSince()⑤setUseCaches()⑥setRequestProperty()(3)調用connect()方法連接遠程資源:conn.connect();connect()方法除了創建一個連接指定伺服器的套接字連接外,還可以查詢伺服器以獲取相應頭資訊(headerinformation)。(4)連接伺服器以後,使用getHeaderFieldKey()和getHeaderField()方法來枚舉出頭資訊的所有域。此外,也可以使用如下的方法來查詢標準域的內容:

①getContentEncoding()②getContentLength()③getContentType()④getData()⑤getExpiration()⑥getLastModified()(5)使用getInputStream()方法訪問資源數據。用getInputStream()方法將返回一個輸入流,此輸入流和URL類的openStream()方法返回的輸入流是相同的。下麵我們將詳細的介紹其中的一些方法。在用於連接伺服器前設置連接屬性(第(2)步)的幾個方法中,setDoInput()和setDoOutput()這兩個方法最為重要。缺省時,連接伺服器後將產生一個用於讀取伺服器數據的輸入流,但不會產生用於向伺服器寫出數據的輸出流。如果需要用到一個輸出流(例如,用於向CGIForm發送數據),就必須調用

conn.setDoOutput(true);

setIfModifiedSince()方法用於告訴連接:只需要在給出的日期之後被修改過的數據。

setUseCaches()和setAllowUserInteraction()方法只用於Applet。setUseCaches()方法指示流覽器先檢查它的高速緩存區,這樣可以優化訪問,如果設置其值為false,將不使用流覽器的緩存,其默認值為true。setAllowUserInteraction()允許Applet彈出一個查詢用戶名和密碼的對話框。這些設置在Applet外部不起作用。

setRequestProperty()方法設置一個名字/值對,用於說明某一特定的協議。例如,想訪問一個口令保護的網頁,必須進行如下設置:Stringinput=username+":"+password;

conn.setRequestProperty("Authorization","Basic"+input);

注:用ftp協議訪問一個有口令保護的檔時,將使用和上面完全不同的方法,只需要創建一個如下形式的URL:

ftp:://username:password@ftp.ftpserver.com/pub/file.txt

當調用了connect()方法後,就可以查尋回應頭資訊。我們通過下麵的例子來介紹如何獲得頭資訊的所有域和值以及頁面內容。例11.3GetHeaderField.javaimportgenesis.*;import.*;importjava.io.*;

publicclassGetHeaderField{ publicstaticvoidmain(Stringargs[]) { try{

URLurl=newURL("/index.html"); //得到一個URLConnection對象

URLConnectionconn=url.openConnection(); //連接伺服器

conn.connect(); inti=1; Stringkey=""; Stringvalue="";//逐一讀出指定URL中的所有域

while((key=conn.getHeaderFieldKey(i))

!=null) { //讀出對應域的值

value=conn.getHeaderField(i); Transcript.println(key+":"+value); i++; } //獲取一個輸入流

InputStreamin=conn.getInputStream();BufferedReaderreader= newBufferedReader(newInputStreamReader(in));

//打開index.hmtl檔為寫做準備

FileWriterfw=newFileWriter("index.html"); PrintWriterpw=newPrintWriter(fw);

Stringline=""; //逐行讀入頁面內容

while((line=reader.readLine())!=null){ //保存到index.html檔中

pw.println(line); }

reader.close(); pw.close(); fw.close(); } catch(IOExceptione){

Transcript.println(e.getMessage()); } }}程式的運行結果如圖11.4所示。圖11.4

以上輸出結果展示了一個典型的HTTP請求的回應頭所包含的一組域。getHeaderFieldKey()和getHeaderField()方法分別返回回應頭中指定位置的功能變數名稱和值。指定值基數為1,當指定值為零或大於頭中域總數時,將返回null串。由於URLConnection類中沒有提供獲得相應頭中域總數的方法,所以需重複調用getHeaderFieldKey()方法,直到返回null串來確定頭中域的總數。

程式中同時演示了如何通過URLConnection類獲取頁面內容。調用URLConnection類的方法getInputStream()將返回一個InputStream類的實例,這個對象和調用URL類中openStream()返回的InputStream對象是相同的。利用這個對象,我們可以建立一個獲取頁面內容的輸入流。運行例子後,當前目錄將會生成一個index.html檔,通過流覽器打開這個檔,可以看到,結果和前面通過URL訪問頁面內容所得到的結果相同。此外,為了便於查詢最常見的回應頭域的值,Java庫提供了六個更為方便的方法直接訪問這些域,如表11.1所示。表11.1直接獲取指定域值的方法域名方法返回類型DatagetDatalongExpiresgetExpirationlongLast-ModifiedgetLastModifiedlongContent-LengthgetContentLengthintContent-TypegetContenttypeStringContent-EncodinggetContentEncodingString11.1.4與CGI的溝通在Java技術出現之前,用CGI(CommonGatewayInterface)來提供相互交換網絡數據的方法就已廣泛使用。把資訊從網路流覽器發送給相應的網路伺服器,例如使用電子郵件服務,通常需要填寫用戶名和提交密碼。傳統的HTML語言提供了Form標記發送格式數據。一個典型的HTMLForm描述如例11.4。例11.4form.html<html><head></head><body><formmethod="GET"action="/cgi-bin/login.exe"><p>Name:<inputtype="text"name="name"value=""size="40"></p><p>EmailAddress:<inputtype="text"name="email"value=""size="40"></p><p><inputtype="submit"name="submit"></p></form></body></html>

通過在流覽器上打開輸入以上代碼的html檔,我們可以看到一個如圖11.5所示的頁面。

圖11.5

在Internet上訪問郵件服務時,用戶需要填寫一個類似上圖的表單。當用戶點擊提交按鍵後,流覽器將會發送這些填寫的數據到action屬性指定的伺服器中的CGI程式。CGI程式負責處理這些數據,然後生成一個HTML頁面,並送回流覽器。對於郵件服務,這個新的回應頁通常就是我們登陸後的頁面。(有關CGI程式和HTMLForm的詳細內容,本章將不再繼續討論,如需瞭解可參考有關書籍)

除了處理傳統的非互動式類型外,URLConnection類也提供了用於CGI或Servlet連接的功能。和HTML表單頁一樣,Java程式可以向伺服器發出一個CGI請求,這個請求既可以設為GET,也可以設為POST。此外,也可在程式中攔截CGI程式的輸出,所以通過Java程式可以實現和CGI程式交互資訊的全部過程。

通過GET方法向CGI程式發送資訊時,只需將參數置於URL的末尾,並用“?”分隔。URL的基本格式為

http://host/script?parameters

當包含兩個以上的參數時,用一個“&”分開每個參數,並對參數做以下的編碼處理:用“+”代替所有的空格,用%加兩位十六進制數代替所有非字母的字元(包括漢字)。例如,假設一個參數為“C++Language”,它將被編碼為“C%2b%2b+Language”,其中2b(十進位數43)就是ASCII碼的“+”字元。這種編碼方法避免了空格干擾,並可以解釋其他字元,這種編碼方式稱為URL編碼。GET方法非常方便,但是有一個大的限制,就是流覽器一般會限制GET請求的URL串的字元個數。而POST的方法則不同,使用POST請求的時候,不把參數放在URL的末端,而是採用建立一個到伺服器的輸出流的方式。通過得到一個來自URLConnection的輸出流,並把參數名/值對逐個寫入到輸出流,就可以實現POST方法的請求。使用POST方法時,仍需用URL編碼方式處理這些輸入的參數,並用“&”將參數分開。

下麵我們仍然通過一個例子來詳細的瞭解GET和POST方法與CGI通信的過程以及它們的不同之處。例11.5CGITest.javaimportgenesis.*;importjava.io.*;import.*;importjava.util.*;

publicclassCGITest{URLurl=null; URLConnectionconn=null; FileWriterfw=null; PrintWriterpw=null; publicstaticvoidmain(String[]args) { CGITesttest=newCGITest(); ArrayListparam=newArrayList(); if(args[0].equals("GET")||args[0].equals("get")){ //GET方法所需要的參數

param.add("p"); param.add(args[1]); param.add("u"); param.add("B"); //GET方法通過Yahoo提供的搜索引擎搜索給定的關鍵字

test.doGet("/search",param); }if(args[0].equals("POST")||args[0].equals("post")) { //POST方法所需要的參數

param.clear(); param.add("user"); param.add(args[1]); param.add("pass"); param.add(args[2]); param.add("type");

param.add("0"); param.add("verifycookie"); param.add("y"); //POST方法登陸163郵箱

test.doPost("http://bjweb.163.net/cgi/163/login_pro.cgi",param); } }publicStringdoGet(Stringlocation,java.util.Listparam) { if(param!=null) { StringBufferbufParam=newStringBuffer();

//通過一個枚舉器列出所有參數

Iteratoriterator=param.iterator(); Stringkey=null; Stringvalue=null;//枚舉出所有的參數

while(iterator.hasNext()) { key=(String)iterator.next(); value=(String)iterator.next(); //對參數進行編碼,然後轉化成key=value的格式

//並將它們串在一起,通過&分隔開

bufParam.append(URLEncoder.encode(key)); bufParam.append("="); bufParam.append(URLEncoder.encode(value));if(iterator.hasNext()) bufParam.append("&"); }

//把參數連接在URL後面,用?號分隔開

location+="?"+bufParam.toString(); }

StringBufferpage=newStringBuffer();try { url=newURL(location); conn=url.openConnection(); //連接伺服器

conn.connect(); //得到一個輸入流

InputStreamin=conn.getInputStream(); BufferedReaderreader= newBufferedReader(new

InputStreamReader(in));//打開Get.html檔準備寫

fw=newFileWriter("Get.html"); pw=newPrintWriter(fw);

Stringline=""; //逐行讀入

while((line=reader.readLine())!=null) { page.append(line+"\n"); pw.println(line);}

pw.close(); fw.close(); reader.close(); in.close(); } catch(IOExceptione) { Transcript.println(e.getMessage()); }returnpage.toString(); }

publicStringdoPost(Stringlocation,java.util.Listparam) { StringBufferpage=newStringBuffer();

try { url=newURL(location); conn=url.openConnection();//同時打開到伺服器的輸入和輸出流

conn.setDoOutput(true); conn.setDoInput(true);

if(param!=null) { //獲取一個到伺服器的輸入通道

PrintWriterout=newPrintWriter(conn.getOutputStream());

Iteratoriterator=param.iterator();Stringkey=null; Stringvalue=null; //枚舉列出所有參數

while(iterator.hasNext()) { //對參數進行編碼,並逐個將參數轉化成

//key=value的格式,直接輸出到伺服器

key=URLEncoder.encode((String)iterator.next()); value=URLEncoder.encode((String)iterator.next());out.print(key+"="+value); if(iterator.hasNext()) out.print("&"); }

out.close(); }

//連接伺服器

conn.connect();//獲取一個輸入流

InputStreamin=conn.getInputStream(); BufferedReaderreader=

newBufferedReader(newInputStreamReader(in)); //打開Post.html準備寫

fw=newFileWriter("Post.html"); pw=newPrintWriter(fw); Stringline=""; //逐行讀入while((line=reader.readLine())!=null) { page.append(line+"\n"); pw.println(line); }

pw.close(); fw.close(); reader.close(); in.close();}

catch(IOExceptione) { Transcript.println(e.getMessage()); }

returnpage.toString(); }}

這個例子比較長,但它並不複雜。程式中,通過兩個方法:doGet()和doPost()分別實現了GET和POST兩種方式與CGI的通信。在doGet()方法中,測試的是使用Yahoo提供的搜索引擎;在doPost()方法中,測試的是登陸163.net的免費郵件系統。編譯這個程式後,測試doGet()方法,可以用下麵的命令行運行它:

javaCGITestgetJava

產生的結果將保存在一個叫Get.html的檔中。用流覽器打開它,可以看到同在上的搜索引擎中輸入“Java”搜索產生的頁面相同的效果。doGet()方法需要提供兩個參數,第一個參數是創建交互的CGI程式的URL,第二個參數是一個List範本類,裏面存放了提供的參數。要注意的是,把這些參數串在一起並接在URL的末尾時,必須用URLEncoder類的encode()方法對參數進行URL編碼。

如果要測試doPost()方法,須通過下麵的命令行運行這個程式:

javaCGITestpostUsernamePassword

要測試登陸163.net的免費郵箱,就必須在163.net上註冊一個郵箱,並在命令行的第2、3個參數提供登陸郵箱的用戶名和密碼。產生的結果保存在一個叫Post.html的檔中。同樣,流覽這個檔看到的同在網站上登錄免費郵件系統所看到的頁面一樣。

doPost()方法同樣需要提供交互用的URL和保存參數的List,但是這裏我們沒有把參數串在一起,而是通過URLConnection類的getOutputStream()方法打開輸入流,逐個將參數寫入到輸出流中。這樣無論有多少參數也沒關係了,但要注意的是,仍然要對參數進行URL編碼並用“&”將它們分開。

注:由於各網站的CGI程式URL和參數可能會隨著網站的程式修改而改變,所以這個程式並不能保證一直得到正確的結果,但是只需查看網站的源代碼,並找到HTMLForm的action以及input的所有參數,對上面例子中的程式略作修改,就可以順利的得到正確結果了。11.2使用Socket通信URL和URLConnection類提供給我們一種簡便的方法編寫網路程式,實現一些較高級的協議訪問Internet。但是通常,這些協議是不夠的,我們常需要用一種更通用、更底層的方法編寫網路應用程式,這就是我們最常用的套接字(Socket)通信。11.2.1InetAddress類

Internet上的每一臺電腦都擁有一個惟一的地址,這樣才能和其他網路上的電腦互通資訊。我們常說的TCP/IP中的IP(InternetProtocal)。就是定義Internet上電腦的地址的。我們知道,一個IP地址必須由四個0~255之間的數字組成,數字之間用點隔開。由於很難記憶這個由四個數字組成的地址,所以有了主機名(hostname)的出現,例如,就是一個主機名。InetAddress就是用來實現IP的類。

InetAddress

類的聲名如下:

publicfinalclassInetAddressextendsObjectimplementsSerializableInetAddress類沒有提供任何構造函數,我們只能通過它本身提供的一些靜態方法來建立一個它的實例。通常,用於建立一個InetAddress類的方法有如下幾種:●publicstaticInetAddressgetByName(Stringhost)throwsUnknowHostException指定主機名建立一個InetAddress的實例。●publicstaticInetAddressgetByAddress(byte[]addr)throwsUnknowHostException指定IP地址建立一個InetAddress的實例。●publicstaticInetAddressgetLocalHost()throwsUnknowHostException建立本機的InetAddress的實例。下麵我們通過一個實例來看看如何使用InetAddress類。例11.6InetAddressDemo.javaimportgenesis.*;import.*;

publicclassInetAddressDemo{ publicstaticvoidmain(String[]args) { try {

InetAddressaddr1=InetAddress.getByName("www.szptt.net.cn"); Transcript.println(addr1.getHostAddress());

InetAddressaddr2=InetAddress.getByAddress(addr1.getAddress()); Transcript.println(addr2.getHostName());

Transcript.println();

InetAddressaddr3=InetAddress.getLocalHost(); Transcript.println(addr3.getHostName()); Transcript.println(addr3.getHostAddress()); } catch(UnknownHostExceptione) { Transcript.println(e.getMessage()); } }}程式的運行結果如圖11.6所示。圖11.6

從運行結果我們可以看到,調用getByName()方法通過主機名建立一個InetAddress類的實例後,我們就可以得到對應於這個主機名的地址。需要注意的是,在調用建立InetAddress實例的幾個靜態方法時,這些方法會連接指定的地址或功能變數名稱,如果無法連通,會拋出一個UnknowHostException,所以必須用try-catch塊包含這個方法,並處理無法連通的情況。11.2.2客戶端Socket類

TCP/IP最主要的功能是提供點對點通信機制。一個機器要在網路上與另一臺機器通信,就需要建立一個互聯的通道。這個通道傳輸兩個數據流,一個為發出的數據流,一個為接收的數據流。由於有了IP地址,我們可以方便的在Internet上找到另一臺機器。假如自己的機器要和另一臺機器通信,就必須向那臺主機發起一個連接請求。如果這個請求被接受,這次通信過程就開始了。通常情況下,我們把發起請求的機器叫客戶端,被請求的機器則稱為伺服器。Socket類是用來編寫客戶端程式的類。Socket類的聲明如下:

publicclassSocketextendsObjectsSocket類常用的方法有下列幾種:●Socket(InetAddressaddress,intport)throwsIOException建立一個Socket的實例,用來連接指定的地址和端口。●Socket(Stringhost,intport)throwsUnknowHostException,IOException和上面類似,但是通過主機名代替InetAddress。●publicvoidclose()throwsIOException中斷該Socket連接。●publicInetAddressgetInetAddress()返回該Socket實例所連接的地址。●publicInputStreamgetInputStream()throwsIOException返回一個輸入流,用來接收伺服器端發送的資訊。●publicOutputStreamgetOutputStream()throwsIOException返回一個輸出流,用來向伺服器端發送資訊。●publicintgetPort()返回該Socket實例連接遠程的端口。●publicintgetSoTimeout()throwsSocketException返回接收資訊的時間限制。如果返回值為0,則表示沒有限制。●publicvoidsetSoTimeout(inttimeout)throwsSocketException

設置接收資訊的時間限制,以千分之一秒為單位。利用InputStream接收資訊時,程式會一直掛起,直到接收到資訊,或者到達時間限制為止。如果出現超時,則會拋出一個java.io.InterruptedIOException。如果timeout設為0,則沒有時間限制,程式會一直掛起等待。下麵來看一個簡單的Socket編程的示例。例11.7SocketDemo.javaimportgenesis.*;importjava.io.*;import.*;publicclassSocketDemo{ publicstaticvoidmain(String[]args) { try { //構造一個連接到時間伺服器time-A.timefreq.bldrdoc.gov //端口13的Socket類實例。

Socketsock=newSocket("time-A.timefreq.bldrdoc.gov",13);//從Socket獲得一個接收伺服器資訊的輸入流

BufferedReaderin =newBufferedReader( newInputStreamReader(sock.getInputStream())); while(true) { Stringline=in.readLine(); if(null==line) break; elseTranscript.println(line); } } catch(IOExceptione) { Transcript.println(e.getMessage()); } }}程式的運行結果如圖11.7所示。圖11.7

這個例子非常簡單,它打開了一個Socket類,這個Socket類連接到時間伺服器time-A.timefreq.bldrdoc.gov的13端口上。當連接成功後,Socket類的getInputStream()方法返回一個InputStream對象,然後程式把流對象鏈接到一個BufferedReader上。接著就可以用readLine()方法讀取伺服器發送的所有字元,並把它們輸出到螢幕上。直到readLine()方法返回null,即讀完所有伺服器發送的資訊,程式才會結束。在Windows命令行下輸入telnettime-A.timefreq.bldrdoc.gov13,可以看到和上面類似的輸出結果,如圖11.8所示。圖11.811.2.3伺服器端ServerSocket類前面實現了一個簡單的網路客戶程式,它可以從伺服器上接收資訊,下麵我們學習實現伺服器端的編程方法。一個伺服器程式啟動後,通常會監聽某一個端口,等待某一臺客戶端電腦發出連接的請求,然後作出反應。ServerSocket類的聲明如下:

publicclassServerSocketextendsObjects

ServerSocket類的大部分方法都和Socket類的類似,它最常用的構造方法如下:

ServerSocket(intport)throwsIOException

創建一個ServerSocket示例是不需要指定IP地址的,SeverSocket總是處於監聽本機端口的狀態。下麵我們看一個伺服器程式的示例。例11.8ReversalServer.javaimportjava.io.*;import.*;

publicclassReversalServer{

publicstaticvoidmain(String[]agrs){

try { //創建一個監聽8189端口的ServerSocket ServerSockets=newServerSocket(8189); //啟動ServerSocket的監聽

Socketinsock=s.accept(); //建立輸入流通道

BufferedReaderin=newBufferedReader( newInputStreamReader(insock.getInputStream()));//建立輸出流通道

PrintWriterout= newPrintWriter(insock.getOutputStream(),true/*autoFlush*/); out.println("Enterquittoexit.");

while(true) { Stringline=in.readLine(); if(null==line) continue;if(line.trim().equals("quit")) break;else { //翻轉輸入的字串

StringBufferrline=newStringBuffer(); for(inti=line.length();i>0;i--) rline.append(line.charAt(i-1)); out.println("Reversed:"+rline.toString()); } }out.close(); in.close(); insock.close(); } catch(IOExceptione) { e.printStackTrace(); } }}

上面的例子是一個簡單的伺服器程式,它啟動了一個ServerSocket監聽本機的8189端口,這個端口一般不會被用到。當有其他客戶端請求與它聯接時,accept()方法將接受這個請求並創建一個獨立的Socket實例。通過這個實例,我們可以取得輸入和輸出的兩個流。程式首先通過輸出流向客戶端發送了一條問候資訊:

out.println("Hello!Pleaseinput.Enterquittoexit.");

然後,伺服器程式接收來自客戶端的輸入。每次從客戶端讀取一行資訊,就將這些輸入的字串翻轉以後再送回給客戶端,所以這個伺服器程式叫做ReversalSever。編譯並運行這個程式,然後在Windows命令行下輸入“telnet8189”。IP地址是一個特殊的地址,稱為本機回送地址(LocalLoopbackAddress),它代表著本機。

由於運行伺服器程式的機器和啟動telnet測試的是同一主機,所以這個地址也正是我們要連接的。當然,也可以從其他機器上啟動telnet來測試這個伺服器程式,這時候就必須telnet到運行伺服器程式的IP地址上。啟動telnet以後,輸入任何資訊,看看結果是不是你想像的,最後輸入quit結束連接。此處如輸入“123456”,則結果如圖11.9所示。

圖11.911.2.4多客戶通信機制我們注意到,前面例子中的伺服器程式只能服務於一個客戶端,也就是說,一個客戶連接到這個服務程式後,將一直獨佔它。而通常情況下,我們看到的伺服器總是要服務於許許多多的客戶端的,比如一個網頁伺服器,可以接受許多客戶的流覽。利用線程的特性,我們就可以很好地解決服務於多個客戶的問題。

我們只需要對上面的程式做少許修改,就可以使它服務於多個客戶端。首先,應該把主程序部分放入一個迴圈,每接收到一個來自客戶端的請求,就啟動一個線程來處理他。而主程序則可以繼續等待來自其他客戶端的請求。下麵看修改以後的程式,可試著啟動多個telnet連接它進行測試。例11.9ThreadedReversalServer.javaimportjava.io.*;import.*;

publicclassThreadedReversalServer{ publicstaticvoidmain(String[]agrs) { inti=0;try { //創建一個監聽8189端口的ServerSocket ServerSockets=newServerSocket(8189); for(;;) { //啟動ServerSocket的監聽

Socketinsock=s.accept(); System.out.println("Thread"+i+"run."); //啟動處理客戶端資訊的線程newThreadReversal(insock,i).start(); i++; } } catch(IOExceptione) { e.printStackTrace(); } }

}classThreadReversalextendsThread{ privateSocketsock; privateintcounter;

publicThreadReversal(Sockets,inti) { sock=s; counter=i; }publicvoidrun() { try { //建立輸入流通道

BufferedReaderin= newBufferedReader( newInputStreamReader(sock.getInputStream())); //建立輸出流通道

PrintWriterout=newPrintWriter(sock.getOutputStream(),true/*autoFlush*/); out.println("Hello!Pleaseinput.Enterquittoexit."); while(true) { Stringline=in.readLine(); if(null==line) continue; if(line.trim().equals("quit")) break;else { //翻轉輸入的字串

StringBufferrline=newStringBuffer(); for(inti=line.length();i>0;i--) rline.append(

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论