• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • netty(一) java NIO

    標簽: java  網絡編程  異步  nio

    關于java,其網絡通信方面性能可以說在慢慢挖掘,而且是現今被挖掘出來有效率最高的一塊了,不止2017年,最近這兩年開始,你會發現,java很多組件都采用了NIO的方式,因為其性能確實可以帶來很大提高,從阿里dubbo的廣泛應用netty,到tomcat8以后的NIO通訊,作為一個老程序員,我們發現一定要深入研究java的NIO了,它現在成了java各種應用級開發的核心功能。

    在jdk1.4之前,java是沒有NIO的,所有的socket都采用了同步阻塞模式,這以請求一應答的模式簡化了java開發,但在性能和可靠性上卻存在著巨大瓶頸。在jdk1.4中增加了個java.nio包,提供了很多進行異步I/O開發的API和類庫,主要的類和接口如下:

    • 進行異步I/O操作的緩沖區ByteBuffer等;
    • 進行異步I/O操作的管道Pipe;
    • 進行各種I/O操作(異步或者同步)的Channel,包括ServerSocketChannel和SocketChannel;
    • 多種字符集的編碼能力和解碼能力;
    • 實現非阻塞I/O操作的多路復用器selector;
    • 基于流行的Perl實現的正則表達式類庫
    • 文件通道FileChannel。
    新的NIO類庫的提供,極大地促進了基于java的異步非阻塞編程的發展和應用,但是,它依然有不完善的地方,特別是對文件系統的處理能力仍顯不足,主要問題如下。
    • 沒有統一的文件屬性(例如讀寫權限);
    • API能力比較弱,例如目錄的級聯創建和遞歸遍歷,往往需要自己實現;
    • 底層存儲系統的一些高級API無法使用;
    • 所有的文件操作都是同步阻塞調用,不支持異步文件讀寫操作。
    2011年7月28日,JDK1.7正式發布。它的一個比較大的亮點就是將原來的NIO類庫進行了升級,被成為NIO2.0。主要提供了以下三個方面的改進。
    • 提供能夠批量獲取文件屬性的API,這些API具有平臺無關性,不與特性的文件系統相耦合。另外它還提供了標準文件系統的SPI,供各個服務提供商擴展實現;
    • 提供AIO功能,支持基于文件的異步i/O操作和針對網絡套接字的異步操作;

    傳統的BIO編程是可以

    網絡編程的基本模型是Client/Server模型,也就是兩個進程之間進行相互通信,其中服務端提供位置信息(綁定的IP地址和端口),客戶端通過連接操作向服務端監聽的地址發起連接請求,通過三次握手建立連接,如果連接建立成功,雙方就可以通過網絡套接字(Socket)進行通信。

    在基于傳統同步阻塞模型開發中,ServerSocket負責綁定IP地址,啟動監聽端口;Socket負責發起連接操作。連接成功后,雙方通過輸入和輸出流進行同步阻塞式通信。
    下面我們就以經典的時間服務器(TimeServer)為例,通過代碼分析來回顧和熟悉BIO編程。

    BIO通信模型圖

    首先,我們通過圖2-1所示的通信模型圖來熟悉BIO的服務端通信模型:采用BIO通信模型的服務端,通常由一個獨立的Acceptor線程負責監聽客戶端的連接,它接收到客戶端的連接請求之后為每個客戶端創建一個新的線程進行鏈路處理,處理完成之后,通過輸出流返回應答給客戶端,線程銷毀,這就是典型的一請求一應答通信模型。

    該模型最大的問題就是缺乏彈性伸縮能力,當客戶端并發訪問量增加以后,服務端的線程個數和客戶端并發訪問數呈1:1關系,由于線程是Java虛擬機非常寶貴的系統資源,當線程數膨脹之后,系統的性能將急劇下降,隨著并發訪問量的繼續增大,系統會發生線程堆棧溢出、創建新線程失敗等問題,并最終導致進程宕機或僵死,不能對外提供服務。下面我們對服務端和客戶端進行源碼分析,尋找同步阻塞I/O的弊端。

    同步阻塞式I/O創建的TimerServer源碼分析

    public class TimerServer {
        public static void main(String[] args) throws IOException {
            int port = 8080;
            if (args != null && args.length > 0) {
                try {
                    port = Integer.valueOf(args[0]);
                } catch (NumberFormatException e) {
                    //采用默認值
                }
            }
            ServerSocket server = null;
            try{
                server = new ServerSocket(port);
                System.out.println("The time server is start in port:" + port);
                Socket socket = null;
                while (true) {
                    socket = server.accept();
                    new Thread(new TimeServerHandler(socket)).start();
                }
            }finally {
                if (server != null) {
                    System.out.println("The time server close");
                    server.close();
                    server = null;
                }
            }
        }
    }
    TimeServer根據傳入的參數設置監聽端口,如果沒有入參,使用默認值8080.通過構造函數創建ServerSocket,如果端口合法且沒有被占用,服務端監聽成功。之后通過一個無限循環來監聽客戶端的連接,如果沒有客戶端接入,則主線程阻塞在ServerSocket的accept操作上。
    當有新的線程接入的時候,執行代碼new Thread,以Socket為參數構造TimeServerHandler對象,TimeServerHandler是一個Runnable,使用它為構造函數的參數創建一個新的客戶端線程處理Socket鏈路。下面再看下TimeServerHandler的代碼:
    public class TimeServerHandler implements Runnable {
    
        private Socket socket;
    
        public TimeServerHandler() {
    
        }
    
        public TimeServerHandler(Socket socket) {
            this.socket = socket;
        }
    
        @Override
        public void run() {
            BufferedReader in = null;
            PrintWriter out = null;
            try {
                in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
                out = new PrintWriter(this.socket.getOutputStream(), true);
                String currentTime = null;
                String body = null;
                while (true) {
                    body = in.readLine();
                    if (body == null) {
                        break;
                    }
                    System.out.println("The time server receive order:" + body);
                    currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
                    out.println(currentTime);
                }
            } catch (Exception e) {
                
            }finally{
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                }
                if (out != null) {
                    out.close();
                    out = null;
                }
                if (this.socket != null) {
                    try {
                        this.socket.close();
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                    this.socket = null;
                }
            }
        }
    }


    第37行通過BufferedReader讀取一行,如果已經讀到了輸入流的尾部,則返回值為null,推出循環。如果讀到了非空值,則對內容進行判斷,如果請求消息為查詢時間的指令
    “QUERY TIME ORDER”,則獲取當前最新的系統時間,通過PrintWriter的println函數發送給客戶端,最后推出循環。
    最后釋放,輸入流,輸出流和socket套接字句柄資源,最后線程自動銷毀并被虛擬機回收。

    下面我們會介紹同步阻塞的客戶端代碼,然后分別運行服務端和客戶端,看下程序的運行結果。

    同步阻塞式IO創建的TimeClient源碼分析

    客戶端通過Socket創建,發送查詢時間服務器的“QUERY TIME ORDER”命令,然后讀取服務端的響應并將結果打印出來,隨后關閉連接,釋放資源,程序退出執行。
    public class TimeClient {
        public static void main(String[] args) {
            int port = 8080;
            if (args != null && args.length > 0) {
                try {
                    port = Integer.valueOf(args[0]);
                } catch (NumberFormatException e) {
                    //
                }
            }
    
            Socket socket = null;
            BufferedReader in = null;
            PrintWriter out = null;
    
            try {
                socket = new Socket("127.0.0.1", port);
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                out = new PrintWriter(socket.getOutputStream(), true);
                out.println("QUERY TIME ORDER");
                System.out.println("Send order 2 server succeed.");
                String resp = in.readLine();
                System.out.println("Now is:" + resp);
            } catch (Exception e) {
    
            } finally {
                if (out != null) {
                    out.close();
                    out = null;
                }
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    in = null;
                }
                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    socket = null;
                }
            }
        }
    }
    分別執行服務端和客戶端,執行結果如下。
    客戶端執行結果如下:

    到此為止,同步阻塞IO開發的時間服務器程序已經講解完畢。我們發現,BIO主要的問題在于每當有一個新的客戶端接入時,服務端必須創建一個新的線程處理新接入的客戶端鏈路,一個線程只能處理一個客戶端連接。在高性能服務器領域,往往需要面對成千上萬個客戶端的并發連接,這種模型顯然無法滿足高性能、高并發接入的場景。

    為了改進一線程一連接的模型,后來又演進出了一種通過線程池或者消息隊列實現一個或者多個線程處理N個客戶端的模型,由于它的底層通信機制依然使用同步阻塞IO,所以被成為“偽異步”。下篇博文我們就對偽異步代碼進行分析,看看偽異步是否能夠滿足我們對高性能、高并發接入的訴求。


    版權聲明:本文為lz710117239原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/lz710117239/article/details/76474263

    智能推薦

    Java NIO (一)

    在現在,Java NIO已經越來越多的獲得了應用包括Tomcat,netty等,這項技術也由之前面試時候的加分項變成了Java程序員必備的技能之一。而之前我一直沒有系統的對其進行學習梳理,因此現在閑下來,想對這一塊的知識做一個相對完整的整理。話不多說,開始吧。 引 NIO主要有三大核心部分:Channel(通道),Buffer(緩沖區), Selector。傳統IO基于字節流和字符流進行操作,而N...

    JAVA NIO入門一

    1.簡介 2.什么是NIO 2.1 BIO Server: 服務端起來了之后,可以通過telnet 127.0.0.1 8080和服務端進行通信: 當然也可以自己寫個Client: BIO連接和線程對應關系: 引入線程池對客戶端連接進行處理: 雖然引入線程池極大的優化了BIO,但是線程池中的線程不可能無限制的增加,在高并發的情況下如果連接數量遠遠大于線程池中線程數量,那大于線程數量的請求就只能被阻...

    Java NIO學習一

    一、NIO概述 NIO即New IO,這個庫是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但實現方式不同,NIO主要用到的是塊,所以NIO的效率要比IO高很多。 NIO主要有三大核心部分:Channel(通道),Buffer(緩沖區), Selector。傳統IO基于字節流和字符流進行操作,而NIO基于Channel和Buffer(緩沖區)進行操作,數據總是從通道讀取到緩沖區中,或者...

    Java NIO(一)

    2019獨角獸企業重金招聘Python工程師標準>>> 原文地址:http://tutorials.jenkov.com/java-nio/index.html Java NIO(New IO)是一個可以替代標準Java IO API的IO API(從Java 1.4開始),Java NIO提供了與標準IO不同的IO工作方式。 Java NIO: Channels and Buf...

    Netty4實戰第一章:Netty和Java NIO APIs

    一、此章內容 Netty架構 為什么我們需要非阻塞IO(NIO) 阻塞IO和非阻塞IO對比 了解JDK NIO的問題和Netty的解決方案   這一章內容是要介紹Netty,不過大部分內容是介紹Java NIO接口。如果你是JVM網絡編程的新手,那么本章將是你學習網絡編程優秀的開端,對于經驗豐富的Java開發者,也可以令你復習到很多知識。對于有經驗的開發者來說,學習本章內容也是很好的復習...

    猜你喜歡

    Java NIO(五)Netty框架簡單應用

    Netty是一款基于NIO開發的網絡通信框架,Netty能通過編程自定義各種協議,因為netty能夠通過自己來編碼/解碼字節流。本文只是對netty的簡單應用。。 關于Netty的一本書“Netty權威指南”,中提到了為什么要選擇Netty。 下面對上一篇文章中的Server/Client(客戶端向服務器端發送“query”字符串,服務器端響應給客戶...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    精品国产乱码久久久久久蜜桃不卡