Java 網路編程 —— 客戶端協議處理框架

来源:https://www.cnblogs.com/Yee-Q/archive/2023/06/03/17453172.html
-Advertisement-
Play Games

## 概述 Java 對客戶程式的通信過程進行了抽象,提供了通用的協議處理框架,該框架封裝了 Socket,主要包括以下類: - URL 類:統一資源定位符,表示客戶程式要訪問的遠程資源 - URLConnection 類:表示客戶程式與遠程伺服器的連接,客戶程式可以從 URLConnection ...


概述

Java 對客戶程式的通信過程進行了抽象,提供了通用的協議處理框架,該框架封裝了 Socket,主要包括以下類:

  • URL 類:統一資源定位符,表示客戶程式要訪問的遠程資源
  • URLConnection 類:表示客戶程式與遠程伺服器的連接,客戶程式可以從 URLConnection 獲得數據輸入流和輸出流
  • URLStreamHandler 類:協議處理器,主要負責創建與協議相關的 URLConnection 對象
  • ContentHandler 類:內容處理器,負責解析伺服器發送的數據,把它轉換為相應的 Java 對象

以上類都位於 java.net 包,除 URL 類為具體類,其餘的都是抽象類,對於一種具體的協議,需要創建相應的具體子類。Oracle 公司為協議處理框架提供了基於 HTTP 的實現,它們都位於 JDK 類庫的 sun.net.www 包或者其子包


URL 類的用法

下例的 HtpClient 類利用 URL 類創建了一個簡單的 HTTP 客戶程式,先創建了一個 URL 對象,然後通過它的 openStream() 方法獲得一個輸入流,接下來就從這個輸入流中讀取伺服器發送的響應結果

public class HttpClient {
    
    public static void main(String args[]) throws IOException {
        //http是協議符號
        URI url = new URL("http://www.javathinker.net/hello.htm");
        //接收響應結果
        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);
        }
        //把位元組數組轉換為字元串
        System.out.println(new String(buffer.toByteArray()));
    }
}

URL 類的構造方法創建 URLStreamHandler 實例的流程如下:

  1. 如果在 URL 緩存已經存在這樣的 URLStreamHandler 實例,則無須再創建,否則繼續執行下一步

  2. 如果程式通過 URL 類的靜態 setURLStreamHandlerFactory() 方法設置了 URLStreamHandlerFactory 介面的具體實現類,那麼就通過這個工廠類的 createURLStreamHandler() 方法來構造 URLStreamHandler 實例,否則繼續執行下一步

  3. 根據系統屬性 java.prolocol.handler.pkgs 來決定 URLStreamHandler 具體子類的名字,然後對其實例化,假定運行 HttpClient 的命令為:

    java -Djava.protocol.handler.pkgs=com.abc.net.www | net.javathinker.protocols HttpClient
    

    以上命令中的 -D 選項設定系統屬性,會先查找並試圖實例化 com.abc.net.www.http.Handler 類,如果失敗,再試圖實例化 net.javathinkerprotocols.http.Handler 類,如果以上操作都失敗,那麼繼續執行下一步

  4. 試圖實例化位於 sun.net.www.prolocol 包的 sun.netwww.protocol.協議名.Handler 類,如果失敗,URL 構造方法就會拋出 MalforedURLException。在本例協議名是 http,會試圖實例化 sun.net.www.protocol.http.Handler

URL 類具有以下方法:

  • openConnection():創建並返回一個 URLConnection 對象,這個 openConnection() 方法實際上是通過調用 URLStreamHandler 類的 openConnection() 方法,來創建 URLConnection 對象
  • openStream():返回用於讀取伺服器發送數據的輸入流,該方法實際上通過調用 URLConnection 類的 getInputStream() 方法來獲得輸入流
  • getContent():返回包裝了伺服器發送數據的 Java 對象,該方法實際上調用 URLConnection 類的 getContent) 方法,而 URLConnection 類的 getContent() 方法又調用了 ContentHandler 類的 getContent() 方法

URLConnection 類的用法

URLConnection 類表示客戶程式與遠程伺服器的連接,URLConnection 有兩個 boolean 類型的屬性以及相應的 get 和 set 方法:

  • dolnput:如果取值為 true,表示允許獲得輸入流,讀取遠程伺服器發送的數據該屬性的預設值為 true。程式可通過 getDolnput() 和 setDolnput() 方法來讀取和設置該屬性
  • doOutput:如果取值為 true,表示允許獲得輸出流,向遠程伺服器發送數據該屬性的預設值為 false。程式可通過 getDoOutput() 和 setDoOutput() 方法來讀取和設置該屬性

URLConnection 類提供了讀取遠程伺服器的響應數據的一系列方法:

  • getHeaderField(String name):返迴響應頭中參數 name 指定的屬性的值
  • getContentType():返迴響應正文的類型,如果無法獲取響應正文的類型就返回 null
  • getContentLength():返迴響應正文的長度,如果無法獲取響應正文的長度,就返回 -1
  • getContentEncoding():返迴響應正文的編碼類型,如果無法獲取響應正文的編碼類型,就返回 null

下例的 HtpClient 類利用 URLConnection 類來讀取伺服器的響應結果

public class HttpClient {
    
    public static void main(String args[]) throws IOException {
        URL url = new URL("http://www,javathinkernet/hello.htm");
        URLConnection connection = url.openConnection();
        //接收響應結果
        System.out.printIn("正文類型:" + connection.getContentType());
        System.out.printIn("正文長度:" + connection.getContentLength());
        //讀取響應正文
        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);
        }
        
        //把位元組數組轉換為字元串
        System.out.println(new String(buffer.toByteArray()));
    }
}

實現協議處理框架

本節將為用戶自定義的 ECHO 協議實現處理框架,共創建了以下類:

  • EchoURLConnection 類:繼承自 URLConnection 類
  • EchoURLStreamHandler 類:繼承自 URLStreamHandler 類
  • EchoURLStreamHandlerFactory 類:實現 URLStreamHandlerFactory 介面
  • EchoContentHandler 類:繼承自 ContentHandler 類
  • EchoContentHandlerFactory 類:實現 ContentHandlerFactory 介面

1. 創建 EchoURLConnection 類

EchoURLConnection 類封裝了一個 Socket,在 connect() 方法中創建與遠程伺服器連接的 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. 創建 EchoURLStreamHandler 及工廠類

EchoURLStreamHandler 類的 openConnection() 方法負責創建一個 EchoURLConnection 對象

public class EchoURLStreamHandler extends URLStreamHandler {
    
    public int getDefaultPort() {
        return 8000;
    }
    
    protected URLConnection openConnection(URL url) throws IOException {
        return new EchoURLConnection(url);
    }
}

EchoURLStreamHandlerFactory 類的 createURLStreamHandle() 方法負責構造 EchoURLStreamHandler 實例

public class EchoURLStreamHandlerFactory implements URLStreamhandlerFactory {
    
    public URLStreamHandler createURLStreamHandler(String protocol) {
        if(protocol.equals("echo"))
            return new EchoURLStreamHandler();
        else
            return null;
    }
}

在客戶程式中,可以通過以下方式設置 EchoURLStreamHandlerFactory

URL.setURLStreamHandlerFactory(new EchoURLStreamHandlerFactory());
URL url=new URL("echo://localhost:8000");

3. 創建 EchoContentHandler 類及工廠類

URLConnection 類還提供了 getContent() 方法,它有兩種重載形式:

public Object getContent();
public Object getContent(Class[] classes);

第二個 getContent() 方法把伺服器發送的數據優先轉換為 classes 數組第一個元素指定的類型,如果轉換失敗,再嘗試轉換第二個元素指定的類型,以此類推

下例 HttpClient 演示處理伺服器發送的數據

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();
        //接收響應結果
        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("未知的響應正文類型");
        }
    }
}

EchoContentHandler 類負責處理 EchoServer 伺服器發送的數據

public class EchoContentHandler extends ContentHandler {
    
    /** 讀取伺服器發送的一行數據,把它轉換為字元串對象 */
    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 參數中的元素,判斷元素是否為 InputSuream 類型或 String 類型,如果是,就返回相應類型的對象,它包含了伺服器發送的數據。如果 classes 參數中的元素都不是 InputStream 類型或 String 類型,就返回 null

EchoContentHandlerFactory 類的 createContentHandler() 方法負責創建一個EchoContentHandler 對象

public class EchoContentHandlerFactory implements ContentHandlerFactory {
    
    public ContentHandler createContentHandler(String mimetype) {
        if(mimetype.equals("text/plain")) {
            return new EchoContentHandler();
        } else {
            return null;
        }
    }
}

在客戶程式中,可以通過以下方式設置 EchoContentHandlerFactory

URLConnection.setContentHandlerFactory(new EchoContentHandlerFactory());
URL url = new URL("echo://localhost:8000");
EchoURLConnection connection = (EchoURLConnection)url.openConnection();
...
//讀取伺服器返回的數據,它被包裝為一個字元串對象
String echoMsg = (String)connection.getContent();

4. 在 EchoClient 類運用 ECHO 協議處理框架

public class EchoClient {
    
    public static void main(String args[]) throws IOException {
        //設置URLStreamHandlerFactory
        URL.setURLStreamHandlerFactory(new EchoURLStreamHandlerFactory());
        //設置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();
            //向伺服器發送消息
            pw.println(msg);
            //讀取伺服器返回的消息
            String echoMsg = (String) connection.getContent();
            System.out.println(echoMsg);
            if(echoMsg.equals("echo:bye")) {
                //斷開連接
                connection.disconnect();
                break;
            }
        }
    }
}


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ## strings.xml匹配替換 將兩個Android項目中的多語言字元串文件(strings.xml)進行比較,如果其中一個項目中包含另一個項目沒有的字元,則合併到單一的輸出文件,並以 key 在原始 XML 文件中更新 value 值。如果key匹配不准確則忽略它。 具體來說: 1. 引入 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 發現一個很有創意的小工具網站,如封面圖所示功能很簡單,就是將一個URL地址轉換為都是 ooooooooo 的樣子,通過轉換後的地址訪問可以轉換回到原始地址,簡單流程如下圖所示。轉換的邏輯有點像短鏈平臺一樣,只不過這個是將你的URL地址變的 ...
  • **歷時一年多,我也體驗了不少的靜態博客托管服務了,這裡進行一下對比吧。主要列舉一下優缺點,所有的內容基於該平臺免費版,並且不添加任何增值服務的情況。 速度體驗基於浙江電信的個人測試結果。** # GitHub Pages > 速度:尚可,並不很慢,但有時候會抽風。 自定義功能變數名稱:一個。 限制: 單個 ...
  • # 如何在 CloudFlare Pages 上建站? > 幾分鐘、零基礎搭建個人網頁!- 高速直連,基於Cloudflare Page: `https://zhuanlan.zhihu.com/p/416269228` 使用 Cloudflare Worker 免費搭建網址導航網站 `https: ...
  • setTimeout 倒計時誤差的出現主要與 JavaScript 的事件迴圈機制和計時器的執行方式有關。 在 JavaScript 中,事件迴圈是用於管理和調度代碼執行的機制。setTimeout 函數用於設置一個定時器,在指定的延遲時間後執行回調函數。然而,由於事件迴圈的機制,setTimeou ...
  • # 高解析度大圖像可縮放 Web 查看器的實踐 ## 一、使用 vips 將高解析度大圖像轉換為 DZI 1. 安裝 vips 具體安裝步驟請參考[libvips Install](https://www.libvips.org/install.html)。 註意,在 windows 11 中安裝 ...
  • 前面博文有介紹JavaScript中數組的一些特性,通過對這些數組特性的深入梳理,能夠加深我們對數組相關知識的理解,詳見博文: [一文搞懂JavaScript數組的特性](https://www.cnblogs.com/jimojianghu/p/17292277.html) 其實,在前端開發中,除 ...
  • # 前言 本文主要講述設計模式中的**抽象工廠模式**,文中使用通俗易懂的案例,使你更好的學習本章知識點並理解原理,做到有道無術。 # 一.什麼是抽象工廠模式 抽象工廠是23種設計模式中**創建型模式**的一種,抽象工廠是由多個工廠組合而成。 上一章我們提到的工廠模式只存在一個抽象角色,而抽象工廠是 ...
一周排行
    -Advertisement-
    Play Games
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...