• <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系列3-BIO、AIO、NIO

    客戶端和服務端通信本質上就是服務端監聽端口,客戶端發起連接請求,通過三次握手連接,如果連接成功建立,雙方就可以通過套接字socket進行通信。
    根據通信實現方式的不同又分為BIO、AIO、NIO三種。

    1.BIO

    BIO是同步阻塞模型。通常由一個Acceptor線程監聽客戶端的連接,接收到連接請求后為每個客戶端都創建一個新線程進行處理,最后將響應通過輸出流返回給客戶端,線程銷毀。
    BIO最大的缺點是并發訪問量增加后,服務端的線程個數和客戶端并發訪問數呈1:1的關系,隨著線程數量快速膨脹,系統性能將急劇下降,當線程達到一定數量,系統宕機。
    為了改進這個問題,提出了對線程使用線程池進行管理,這種通常被稱為偽異步I/O模型,并沒有解決問題。當大量高并發的時候,尤其是大量長連接或者讀取數據較慢的時候,線程數量還是急劇增加。使用固定數量線程池,可以解決線程增加的問題,但會導致大量用戶線程等待,系統有瓶頸,不友好。
    服務端核心代

     		  server = new ServerSocket(8080);
                while(true){
                    Socket socket= server.accept();
                    //當有新的客戶端接入時,投入線程池
                    executorService.execute(new BioServerHandler(socket));
                }
    
    public class BioServerHandler implements Runnable{
        private Socket socket;
        public BioServerHandler(Socket socket) {
            this.socket = socket;
        }
        public void run() {
            try(
                BufferedReader in = new BufferedReader(
                    new InputStreamReader(socket.getInputStream()));
                PrintWriter out = new PrintWriter(socket.getOutputStream(),
                        true)){
                String message;
                String result;
                while((message = in.readLine())!=null){
                    result = response(message);
                    out.println(result);
                }
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                if(socket != null){
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    socket = null;
                }
            }
        }
    
    }
    

    客戶端核心代碼

    	 Socket socket =  new Socket(DEFAULT_SERVER_IP,DEFAULT_PORT);
        pw = new PrintWriter(socket.getOutputStream());
      	 pw.println(“hello”);
          pw.flush();
    

    2.AIO

    AIO是異步非阻塞通信模型,是java在1.7后提供的。本質上就是通過回調函數,直接上代碼。
    服務端核心代碼

    public class AioServerHandler implements Runnable {
    
        /*異步通信通道*/
        public AsynchronousServerSocketChannel channel;
    
        public AioServerHandler(int port) {
            try {
                //創建服務端通道
                channel = AsynchronousServerSocketChannel.open();
                //綁定端口
                channel.bind(new InetSocketAddress(8080));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void run() {
        	//accept函數有兩個參數,第一個是傳遞給回調函數的附件,第二個就是回調函數,回調函數需要實現 				      	     //CompletionHandler<AsynchronousSocketChannel, ? super AioServerHandler>
            //CompletionHandler內部有兩個方法,completed函數在連接成功后調用,failed在失敗時候調用,兩個方法的參數			   //都有兩個,第一個代表IO操作完成后返回的結果,第二個參數就是我們的附件
            channel.accept(this,new AioAcceptHandler());
            try {
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    }
    
    public class AioAcceptHandler
            implements CompletionHandler<AsynchronousSocketChannel,
            AioServerHandler> {
        @Override
        //第一個參數是代碼連接成功后返回的結果,第二個就是我們傳遞的附件
        public void completed(AsynchronousSocketChannel channel,
                              AioServerHandler serverHandler) {
            //重新注冊監聽,讓別的客戶端也可以連接
            serverHandler.channel.accept(serverHandler,this);
            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
            //這個是讀事件發生的時候,又有另外一個回調,讀事件的回調函數不貼了
            channel.read(readBuffer,readBuffer,
                    new AioReadHandler(channel));
    
        }
    
        @Override
        //第一個參數是連接失敗返回的結果,這里是個異常
        //第二個參數是我們傳遞的附件
        public void failed(Throwable exc, AioServerHandler serverHandler) {
            exc.printStackTrace();
            serverHandler.latch.countDown();
        }
    

    客戶端核心代碼:

    public class AioClientHandler
            implements CompletionHandler<Void,AioClientHandler>,Runnable {
    
        private AsynchronousSocketChannel clientChannel;
        private String host;
        private int port;
    
        public AioClientHandler(String host, int port) {
            this.host = host;
            this.port = port;
            try {
                //創建客戶端通道
                clientChannel = AsynchronousSocketChannel.open();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    
    
        @Override
        public void run() {
          //這里的連接成功后回調函數就是自己
            clientChannel.connect(new InetSocketAddress(host,port),
                            null,this);
            try {
                clientChannel.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        //連接成功回調方法
        @Override
        public void completed(Void result, AioClientHandler attachment) {
        }
    
        //連接失敗回調方法
        @Override
        public void failed(Throwable exc, AioClientHandler attachment) {
       
        }
    
        }
    
    

    3.NIO

    3.1 Reactor模式

    說起NIO不得不先說下Reactor模式。
    (1)Reactor模式定義
    Reactor模式是事件驅動模型,有一個或多個并發輸入源,一個Service Handler和多個Request Handlers,Service Handler同步的將輸入的請求(Event)以多路復用的方式,并且根據Event類型分發給相應的Request Handler。
    (2)Reactor模式元素

    • EventHandler:事件處理器
    • Handle:操作系統中的句柄,是對資源在操作系統層面上的一種抽象,它可以是打開的文件、一個連接等。在網絡編程中一般指Socket Handle,即一個網絡連接(在Java NIO中的Channel)。這個Channel注冊到Synchronous Event Demultiplexer中,以監聽Handle中發生的事件,可以是CONNECT、READ、WRITE、CLOSE等事件
    • InitiationDispatcher:事件處理調度器,用來管理EventHandler,將接收到網絡請求分發給相應的處理器去異步處理
    • Demultiplexer:阻塞等待一系列的Handle中的事件到來,在Java NIO中用Selector來封裝。
      在這里插入圖片描述

    (3)Reactor模式請求處理流程

    • 初始化InitiationDispatcher,并初始化一個Map,用來放Handle和EventHandler的映射。
    • 注冊EventHandler到InitiationDispatcher中,把EventHandler和對應Handle放入到map中
    • 調用InitiationDispatcher的handle_events()方法以啟動Event Loop。在Event Loop中,調用Synchronous Event Demultiplexer的select()方法阻塞等待Event發生。
    • 當Event發生后,select()方法返回,InitiationDispatcher根據返回的Handle找到對應的EventHandler,并回調該EventHandler的handle_events()方法,在handle_events()方法中還可以向InitiationDispatcher中注冊新的Eventhandler

    3.2 Reactor模式的java實現

    (1)單線程Reactor模式
    這里的單線程指的是服務端的Reactor是單線程

    • 服務器端的Reactor使用Selector來實現多路復用,并且啟動事件循環。服務端會注冊一個Acceptor事件處理器到Reactor中,這樣Reactor會監聽客戶端向服務器端發起的連接請求事件(ACCEPT事件)。
    • 客戶端向服務器發起一個連接請求,Reactor監聽到了該ACCEPT事件并將該事件派發給對應的Acceptor處理器。Acceptor處理器通過accept()方法得到連接(SocketChannel),然后將該連接所關注的READ事件以及對應的事件處理器注冊到Reactor中,這樣Reactor就會監聽該連接的READ事件。
    • 當Reactor監聽到有讀或者寫事件發生時,將相關的事件派發給對應的處理器進行處理。
    • 處理完所有就緒的感興趣的I/O事件后,Reactor線程會再次執行select()阻塞等待新的事件就緒并將其分派給對應處理器進行處理。
      在這里插入圖片描述
      Reactor的單線程主要是針對于I/O操作而言,也就是accept()、read()、write()以及connect()操作都在一個線程上完成。
      但這里單線程Reactor模式中,不僅I/O操作在該Reactor線程上,連非I/O的業務操作也在該線程上,這會大大延遲I/O請求的響應。
      所以出現了第二種模式單線程Reactor,工作者線程池。

    (2)單線程Reactor,工作者線程池
    與單線程Reactor模式不同的是,添加了一個工作者線程池,并將非I/O操作從Reactor線程中移出轉交給工作者線程池。這樣可以提高Reactor線程的I/O響應,不至于因為一些耗時的業務邏輯而延遲對后面I/O請求的處理。
    線程池的優勢:

    • 重用現線程,減少線程創建和銷毀過程的開銷。
    • 當請求到達時,不會由于等待創建線程,提高了響應性。
    • 合理調整線程池的大小,可以創建足夠多的線程使處理器保持忙碌狀態,還可防止過多線程耗盡內存。
      缺點:
      I/O操作還是一個Reactor來完成,對于高負載、大并發或大數據量的應用場景有性能瓶頸:
    • 一個NIO線程同時處理成百上千的鏈路,性能上無法支撐
    • 當NIO線程負載過重時處理速度將變慢,這會導致大量客戶端連接超時,超時之后往往會進行重發,這更加重了NIO線程的負載,最終會導致大量消息積壓和處理超時
      在這里插入圖片描述
      這就出現了第三種模式:多Reactor線程模式

    (3)多Reactor線程模式
    多Reactor線程模式中有一個mainReactor,多個subReactor。Reactor線程池中的每一Reactor線程都會有自己的Selector、線程和分發的事件循環邏輯。mainReactor線程主要負責接收客戶端的連接請求,然后將接收到的SocketChannel傳遞給subReactor,由subReactor來完成和客戶端的通信。
    流程:

    • 注冊一個Acceptor事件處理器到mainReactor中,啟動mainReactor的事件循環。
    • 客戶端向服務器端發起一個連接請求,mainReactor監聽到了該ACCEPT事件并將該事件派發給Acceptor處理器
    • Acceptor處理器通過accept()方法得到與這個客戶端對應的連接(SocketChannel),然后將這個SocketChannel傳遞給subReactor線程池
    • subReactor線程池分配一個subReactor線程給這個SocketChannel,將SocketChannel關注的READ事件或者以及對應的事件處理器注冊到subReactor線程
    • 當有I/O事件就緒時,相關的subReactor就將事件派發給響應的處理器。這里subReactor線程只負責完成I/O的read()操作,在讀取到數據后將業務邏輯的處理放入到線程池中完成,若完成業務邏輯后需要返回數據給客戶端,則相關的I/O的write操作還是會被提交回subReactor線程
      多Reactor線程模式將接受客戶端的連接請求和與該客戶端的通信分在了兩個Reactor線程來完成。mainReactor完成接收客戶端連接請求的操作,將建立好的連接轉交給subReactor線程,subReactor線程完成與客戶端的通信。這里所有的I/O操作(accept()、read()、write()、connect())還是在Reactor線程(mainReactor線程 或 subReactor線程)中完成的。Thread Pool(線程池)僅用來處理非I/O操作的邏輯。
      優點:
      多Reactor線程模式在大量并發請求的情況下,將大量連接分發給多個subReactor線程,在多核的操作系統中這能大大提升應用的負載和吞吐量。
      同時不會因為read()數據耗時而導致后面的客戶端連接請求得不到即時處理。
      Netty服務端使用了多Reactor線程模式
      在這里插入圖片描述
      上述分析完后你會發現reactor模式與觀察者模式有點像。不過,觀察者模式與單個事件源關聯,而反應器模式則與多個事件源關聯 。當一個主體發生改變時,所有依屬體都得到通知。

    3.3 Selector、Channels、SelectionKey

    (1)Selector
    Selector也就是NIO中的選擇器,用做事件訂閱和Channel管理。
    應用程序向Selector注冊需要它關注的Channel,以及具每一個Channel感興趣的IO事件。
    (2)Channels
    通道,應用程序和操作系統通信的渠道。應用程序可以通過通道讀寫數據。所有在Selector注冊的通道必須繼承SelectableChannel類

    • ServerSocketChannel:服務器程序的監聽通道,只能通過這個通道向操作系統注冊支持多路復用IO的端口監聽。可以支持UDP和TCP
    • ScoketChannel:TCPSocket套接字的監聽通道,一個Socket套接字對應了一個客戶端
    • DatagramChannel:UDP數據報文的監聽通道。
      (3)SelectionKey
      SelectionKey是NIO中的操作類型。一共四種操作類型:OP_READ(讀)、OP_WRITE(寫)、OP_CONNECT(請求連接)、OP_ACCEPT(接受連接)。
    • ServerSocketChannel:可以注冊OP_ACCEPT
    • 服務器SocketChannel:OP_READ、OP_WRITE
    • 客戶端SocketChannel:OP_READ、OP_WRITE、OP_CONNECT

    每個操作類型就緒條件:

    • OP_READ: 當操作系統讀緩沖區有數據可讀時就緒。
    • OP_WRITE:當操作系統寫緩沖區有空閑空間時就緒。一般情況下寫緩沖區都有空閑空間,小塊數據無需注冊,直接寫入即可,否則該條件不斷就緒浪費CPU。但如果是寫密集型的任務,有可能寫滿緩存,這時需要注冊,并且寫完后取消注冊。
    • OP_CONNECT: 請求連接成功后就緒。
    • OP_ACCEPT 當收到客戶端連接請求時就緒。
    版權聲明:本文為huanshirenjian原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/huanshirenjian/article/details/90116331

    智能推薦

    BIO、NIO、AIO

    一:BIO 1、網絡編程的基本模型是C/S模型,即兩個進程間的通信。 2、服務端提供IP和監聽端口,客戶端通過連接操作想服務端監聽的地址發起連接請求,通過三次握手連接,如果連接成功建立,雙方就可以通過套接字進行通信。 3、傳統的同步阻塞模型開發中,ServerSocket負責綁定IP地址,啟動監聽端口;Socket負責發起連接操作。連接成功后,雙方通過輸入和輸出流進行同步阻塞式通信。  ...

    BIO,NIO,AIO總結

    BIO,NIO,AIO總結 通過對Linux系統的網絡IO模型映射到java的IO實現.從而解釋為什么BIO是同步阻塞,NIO是同步非阻塞,AIO是異步. Linux系統I/O基礎 Linux系統用戶態與內核態 系統內存分為: 用戶態內存和內核態內存 如上圖所示,從宏觀上來看,Linux操作系統的體系架構分為用戶態和內核態(或者用戶空間和內核)。內核從本質上看是一種軟件——...

    BIO、NIO、AIO 講解

    一、前沿 在通信框架中經常使用到的三種通信模式,即 BIO、NIO 和 AIO,它們也是面試中經常被問到的,如果學會了它們將會給你帶來薪資的變化哦。下面分別對三者介紹一下,通過示例理解其用法 下面先通過一張圖來簡單了解一下三者,如下所示: 同步阻塞IO : 用戶進程發起一個IO操作以后,必須等待IO操作的真正完成后,才能繼續運行 同步非阻塞IO: 用戶進程發起一個IO操作以后,可做其它事情,但用戶...

    BIO, NIO, AIO

    同步,異步,阻塞,非阻塞 同步:a 事件必須等到 b 事件完成才可以繼續執行/返回 異步:a 事件可以先執行/返回,不需要等待 b 事件的完成,而是通過回調處理 b 事件的返回結果 阻塞:當發起一次請求時,調用者一直等待結果的返回,只有當條件滿足時,才繼續處理后續的工作 非阻塞:當發起一次請求時,調用者不用一直等待結果的返回,可以先去做其他的事情 1、BIO(Blocking IO) 同步阻塞 i...

    BIO、NIO、AIO概述

    1.1 阻塞與非阻塞的概念 1.2 同步與異步的概念 1.3 BIO、NIO與AIO概述...

    猜你喜歡

    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壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

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