Java 對客戶程序的通信過程進(jìn)行了抽象,提供了通用的協(xié)議處理框架,該框架封裝了 Socket,主要包括以下類:
(相關(guān)資料圖)
URL 類:統(tǒng)一資源定位符,表示客戶程序要訪問的遠(yuǎn)程資源URLConnection 類:表示客戶程序與遠(yuǎn)程服務(wù)器的連接,客戶程序可以從 URLConnection 獲得數(shù)據(jù)輸入流和輸出流URLStreamHandler 類:協(xié)議處理器,主要負(fù)責(zé)創(chuàng)建與協(xié)議相關(guān)的 URLConnection 對象ContentHandler 類:內(nèi)容處理器,負(fù)責(zé)解析服務(wù)器發(fā)送的數(shù)據(jù),把它轉(zhuǎn)換為相應(yīng)的 Java 對象以上類都位于 java.net 包,除 URL 類為具體類,其余的都是抽象類,對于一種具體的協(xié)議,需要創(chuàng)建相應(yīng)的具體子類。Oracle 公司為協(xié)議處理框架提供了基于 HTTP 的實現(xiàn),它們都位于 JDK 類庫的 sun.net.www 包或者其子包
URL 類的用法下例的 HtpClient 類利用 URL 類創(chuàng)建了一個簡單的 HTTP 客戶程序,先創(chuàng)建了一個 URL 對象,然后通過它的 openStream()
方法獲得一個輸入流,接下來就從這個輸入流中讀取服務(wù)器發(fā)送的響應(yīng)結(jié)果
public class HttpClient { public static void main(String args[]) throws IOException { //http是協(xié)議符號 URI url = new URL("http://www.javathinker.net/hello.htm"); //接收響應(yīng)結(jié)果 InputStream in = url.openStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); bytel] buff = new byte[1024]; int len = -l; while((len = in.read(buff)) != -1) { buffer.write(buff, 0, len); } //把字節(jié)數(shù)組轉(zhuǎn)換為字符串 System.out.println(new String(buffer.toByteArray())); }}
URL 類的構(gòu)造方法創(chuàng)建 URLStreamHandler 實例的流程如下:
如果在 URL 緩存已經(jīng)存在這樣的 URLStreamHandler
實例,則無須再創(chuàng)建,否則繼續(xù)執(zhí)行下一步
如果程序通過 URL 類的靜態(tài) setURLStreamHandlerFactory()
方法設(shè)置了 URLStreamHandlerFactory
接口的具體實現(xiàn)類,那么就通過這個工廠類的 createURLStreamHandler()
方法來構(gòu)造 URLStreamHandler
實例,否則繼續(xù)執(zhí)行下一步
根據(jù)系統(tǒng)屬性 java.prolocol.handler.pkgs
來決定 URLStreamHandler
具體子類的名字,然后對其實例化,假定運(yùn)行 HttpClient 的命令為:
java -Djava.protocol.handler.pkgs=com.abc.net.www | net.javathinker.protocols HttpClient
以上命令中的 -D 選項設(shè)定系統(tǒng)屬性,會先查找并試圖實例化 com.abc.net.www.http.Handler
類,如果失敗,再試圖實例化 net.javathinkerprotocols.http.Handler
類,如果以上操作都失敗,那么繼續(xù)執(zhí)行下一步
試圖實例化位于 sun.net.www.prolocol
包的 sun.netwww.protocol.協(xié)議名.Handler
類,如果失敗,URL 構(gòu)造方法就會拋出 MalforedURLException。在本例協(xié)議名是 http,會試圖實例化 sun.net.www.protocol.http.Handler
類
URL 類具有以下方法:
openConnection()
:創(chuàng)建并返回一個 URLConnection
對象,這個 openConnection()
方法實際上是通過調(diào)用 URLStreamHandler
類的 openConnection()
方法,來創(chuàng)建 URLConnection
對象openStream()
:返回用于讀取服務(wù)器發(fā)送數(shù)據(jù)的輸入流,該方法實際上通過調(diào)用 URLConnection
類的 getInputStream()
方法來獲得輸入流getContent()
:返回包裝了服務(wù)器發(fā)送數(shù)據(jù)的 Java 對象,該方法實際上調(diào)用 URLConnection
類的 getContent)
方法,而 URLConnection
類的 getContent()
方法又調(diào)用了 ContentHandler
類的 getContent()
方法URLConnection 類的用法URLConnection 類表示客戶程序與遠(yuǎn)程服務(wù)器的連接,URLConnection 有兩個 boolean 類型的屬性以及相應(yīng)的 get 和 set 方法:
dolnput:如果取值為 true,表示允許獲得輸入流,讀取遠(yuǎn)程服務(wù)器發(fā)送的數(shù)據(jù)該屬性的默認(rèn)值為 true。程序可通過 getDolnput() 和 setDolnput() 方法來讀取和設(shè)置該屬性doOutput:如果取值為 true,表示允許獲得輸出流,向遠(yuǎn)程服務(wù)器發(fā)送數(shù)據(jù)該屬性的默認(rèn)值為 false。程序可通過 getDoOutput() 和 setDoOutput() 方法來讀取和設(shè)置該屬性URLConnection 類提供了讀取遠(yuǎn)程服務(wù)器的響應(yīng)數(shù)據(jù)的一系列方法:
getHeaderField(String name)
:返回響應(yīng)頭中參數(shù) name 指定的屬性的值getContentType()
:返回響應(yīng)正文的類型,如果無法獲取響應(yīng)正文的類型就返回 nullgetContentLength()
:返回響應(yīng)正文的長度,如果無法獲取響應(yīng)正文的長度,就返回 -1getContentEncoding()
:返回響應(yīng)正文的編碼類型,如果無法獲取響應(yīng)正文的編碼類型,就返回 null下例的 HtpClient 類利用 URLConnection 類來讀取服務(wù)器的響應(yīng)結(jié)果
public class HttpClient { public static void main(String args[]) throws IOException { URL url = new URL("http://www,javathinkernet/hello.htm"); URLConnection connection = url.openConnection(); //接收響應(yīng)結(jié)果 System.out.printIn("正文類型:" + connection.getContentType()); System.out.printIn("正文長度:" + connection.getContentLength()); //讀取響應(yīng)正文 InputStream in = connection.getInputStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); byte[] buff = new byte[1024]; int len = -l; while((len = in.read(buff)) != -1) { buffer.write(buff, 0, len); } //把字節(jié)數(shù)組轉(zhuǎn)換為字符串 System.out.println(new String(buffer.toByteArray())); }}
實現(xiàn)協(xié)議處理框架本節(jié)將為用戶自定義的 ECHO 協(xié)議實現(xiàn)處理框架,共創(chuàng)建了以下類:
EchoURLConnection 類:繼承自 URLConnection 類EchoURLStreamHandler 類:繼承自 URLStreamHandler 類EchoURLStreamHandlerFactory 類:實現(xiàn) URLStreamHandlerFactory 接口EchoContentHandler 類:繼承自 ContentHandler 類EchoContentHandlerFactory 類:實現(xiàn) ContentHandlerFactory 接口1. 創(chuàng)建 EchoURLConnection 類EchoURLConnection 類封裝了一個 Socket,在 connect() 方法中創(chuàng)建與遠(yuǎn)程服務(wù)器連接的 Socket 對象
public class EchoURLConnection extends URLConnection { private Socket connection = null; public final static int DEFAULT PORT = 8000; public EchoURLConnection(URL url) { super(url); } public synchronized InputStream getInputStream() throws IOException { if(!connected) connect(); return connection.getInputStream(); } public synchronized OutputStream getOutputStream() throws IOException { if(!connected) connect(); return connection.getOutputStream(); } public String getContentType() { return "text/plain"; } public synchronized void connect() throws IOException { if(!connected) { int port = url.getPort(); if(port < 0 || port > 65535) port = DEFAULT_PORT; this.connection = new Socket(url.getHost(), port); this.connected = true; } } public synchronized void disconnect() throws IOException { if(connected) { //斷開連接 this.connection.close(); this.connected = false; } }}
2. 創(chuàng)建 EchoURLStreamHandler 及工廠類EchoURLStreamHandler 類的 openConnection()
方法負(fù)責(zé)創(chuàng)建一個 EchoURLConnection 對象
public class EchoURLStreamHandler extends URLStreamHandler { public int getDefaultPort() { return 8000; } protected URLConnection openConnection(URL url) throws IOException { return new EchoURLConnection(url); }}
EchoURLStreamHandlerFactory 類的 createURLStreamHandle()
方法負(fù)責(zé)構(gòu)造 EchoURLStreamHandler 實例
public class EchoURLStreamHandlerFactory implements URLStreamhandlerFactory { public URLStreamHandler createURLStreamHandler(String protocol) { if(protocol.equals("echo")) return new EchoURLStreamHandler(); else return null; }}
在客戶程序中,可以通過以下方式設(shè)置 EchoURLStreamHandlerFactory
URL.setURLStreamHandlerFactory(new EchoURLStreamHandlerFactory());URL url=new URL("echo://localhost:8000");
3. 創(chuàng)建 EchoContentHandler 類及工廠類URLConnection 類還提供了 getContent()
方法,它有兩種重載形式:
public Object getContent();public Object getContent(Class[] classes);
第二個 getContent() 方法把服務(wù)器發(fā)送的數(shù)據(jù)優(yōu)先轉(zhuǎn)換為 classes 數(shù)組第一個元素指定的類型,如果轉(zhuǎn)換失敗,再嘗試轉(zhuǎn)換第二個元素指定的類型,以此類推
下例 HttpClient 演示處理服務(wù)器發(fā)送的數(shù)據(jù)
public class HttpClient { public static void main(String args[]) throws IOException { URL url = new URL("http://www,javathinker.net/hello.htm"); URlConnection connection = url.openConnection(); //接收響應(yīng)結(jié)果 InputStream in = connection.getInputStream(); Class[] types = {String.class, InputStream.class}; Object obj = connection.getContent(types); if(obj instanceof String) { System.out.println(obj); } else if(obj instanceof InputStream) { in = (InputStream) obj; FileOutputStream file = new FileOutputStream("data"); byte[] buff = new byte[1024]; int len = -l; while((len = in.read(buff)) != -1) { file.write(buff, 0 ,len); } System.out.println("正文保存完畢"); } else { System.out.println("未知的響應(yīng)正文類型"); } }}
EchoContentHandler 類負(fù)責(zé)處理 EchoServer 服務(wù)器發(fā)送的數(shù)據(jù)
public class EchoContentHandler extends ContentHandler { /** 讀取服務(wù)器發(fā)送的一行數(shù)據(jù),把它轉(zhuǎn)換為字符串對象 */ public Object getContent(URLConnection connection) throws IOException { InputStream in = connection.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(in)); return br.readLine(); } public Object getContent(URLConnection connection, Class[] classes) throws IOException { InputStream in = connection.getInputStream(); for(int i = 0; i < classes.length; i++) { if(classes[i] == InputStream.class) { return in; } else if(classes[i] == String.class) { return getContent(connection); } } return null; }}
第二個 getContent() 方法依次遍歷 classes 參數(shù)中的元素,判斷元素是否為 InputSuream 類型或 String 類型,如果是,就返回相應(yīng)類型的對象,它包含了服務(wù)器發(fā)送的數(shù)據(jù)。如果 classes 參數(shù)中的元素都不是 InputStream 類型或 String 類型,就返回 null
EchoContentHandlerFactory 類的 createContentHandler() 方法負(fù)責(zé)創(chuàng)建一個EchoContentHandler 對象
public class EchoContentHandlerFactory implements ContentHandlerFactory { public ContentHandler createContentHandler(String mimetype) { if(mimetype.equals("text/plain")) { return new EchoContentHandler(); } else { return null; } }}
在客戶程序中,可以通過以下方式設(shè)置 EchoContentHandlerFactory
URLConnection.setContentHandlerFactory(new EchoContentHandlerFactory());URL url = new URL("echo://localhost:8000");EchoURLConnection connection = (EchoURLConnection)url.openConnection();...//讀取服務(wù)器返回的數(shù)據(jù),它被包裝為一個字符串對象String echoMsg = (String)connection.getContent();
4. 在 EchoClient 類運(yùn)用 ECHO 協(xié)議處理框架public class EchoClient { public static void main(String args[]) throws IOException { //設(shè)置URLStreamHandlerFactory URL.setURLStreamHandlerFactory(new EchoURLStreamHandlerFactory()); //設(shè)置ContentHandlerFactory URLConnection.setContentHandlerFactory(new EchoContentHandlerFactory()); URL url = new URL("echo://localhost:8000"); EchoURLConnection connection = (EchoURlConnection) url.openConnection(); //允許獲得輸出流 connection.setDoOutput(true); //獲得輸出流 PrintWriter pw = new PrintWriter(connection.getOutputStream(), true); while(true) { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String msg = br.readLine(); //向服務(wù)器發(fā)送消息 pw.println(msg); //讀取服務(wù)器返回的消息 String echoMsg = (String) connection.getContent(); System.out.println(echoMsg); if(echoMsg.equals("echo:bye")) { //斷開連接 connection.disconnect(); break; } } }}