• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • 從NIO編程到Netty的使用

    標簽: Netty

    我們在網絡編程——NIO編程中,就曾介紹過直接使用NIO進行編程,這里我們介紹下如何使用Netty框架,來完成我們之前直接使用NIO完成的功能,就是一個簡單的客戶端和服務端的通信。


    在這之前,我們先來簡單了解一下Netty框架的核心組件:

    Channel

    Channel 是Java NIO 的一個基本構造。它代表一個到實體(如一個硬件設備、一個文件、一個網絡套接字或者一個能夠執行一個或者多個不同的I/O操作的程序組件)的開放連接,如讀操作和寫操作。

    目前,可以把Channel 看作是傳入(入站)或者傳出(出站)數據的載體。因此,它可以被打開或者被關閉,連接或者斷開連接。


    回調和Future

    一個回調其實就是一個方法,一個指向已經被提供給另外一個方法的方法的引用。這使得后者可以在適當的時候調用前者。回調在廣泛的編程場景中都有應用,而且也是在操作完成后通知相關方最常見的方式之一。

    Netty 在內部使用了回調來處理事件;當一個回調被觸發時,相關的事件可以被一個interface-ChannelHandler 的實現處理。

    Future 提供了另一種在操作完成時通知應用程序的方式。這個對象可以看作是一個異步操
    作的結果的占位符;它將在未來的某個時刻完成,并提供對其結果的訪問。

    JDK 預置了interface java.util.concurrent.Future,但是其所提供的實現,只允許手動檢查對應的操作是否已經完成,或者一直阻塞直到它完成。這是非常繁瑣的,所以Netty提供了它自己的實現——ChannelFuture,用于在執行異步操作的時候使用。在并發編程中已有相關介紹。

    ChannelFuture提供了幾種額外的方法,這些方法使得我們能夠注冊一個或者多個ChannelFutureListener實例。監聽器的回調方法operationComplete(),將會在對應的操作完成時被調用。然后監聽器可以判斷該操作是成功地完成了還是出錯了。如果是后者,我們可以檢索產生的Throwable。簡而言之,由ChannelFutureListener提供的通知機制消除了手動檢查對應的操作是否完成的必要。每個Netty 的出站I/O 操作都將返回一個ChannelFuture。


    事件和ChannelHandler

    Netty 使用不同的事件來通知我們狀態的改變或者是操作的狀態。這使得我們能夠基于已經發生的事件來觸發適當的動作。

    Netty事件是按照它們與入站或出站數據流的相關性進行分類的。可能由入站數據或者相關的狀態更改而觸發的事件包括:

    • 連接已被**或者連接失活
    • 數據讀取
    • 用戶事件
    • 錯誤事件

    出站事件是未來將會觸發的某個動作的操作結果,這些動作包括:

    • 打開或者關閉到遠程節點的連接
    • 將數據寫到或者沖刷到套接字。

    每個事件都可以被分發給ChannelHandler 類中的某個用戶實現的方法。可以認為每個Channel-Handler 的實例都類似于一種為了響應特定事件而被執行的回調。

    Netty 提供了大量預定義的可以開箱即用的ChannelHandler 實現,包括用于各種協議(如HTTP 和SSL/TLS)的ChannelHandler。



    了解過我們在網絡編程使用NIO完成的小例子,以及上述Netty核心組件之后,我們來看一看如何使用Netty來完成網絡通信,首先來看一看客戶端的實現:

    public class NettyClient {
    
        private final String host;
        private final int port;
    
        public NettyClient(String host, int port) {
            this.host = host;
            this.port = port;
        }
    
        public void start() throws InterruptedException {
            EventLoopGroup eventLoopGroup = new NioEventLoopGroup();    //線程組
            Bootstrap bootstrap = new Bootstrap();      //客戶端啟動所需
    
            try {
                bootstrap.group(eventLoopGroup)
                        .channel(NioSocketChannel.class)    //指定使用NIO進行網絡通訊
                        .remoteAddress(new InetSocketAddress(host, port))   //配置遠程服務器地址
                        .handler(new NettyClientHandler());
                ChannelFuture channelFuture = bootstrap.connect().sync();   //連接遠程服務器,阻塞,直到連接完成
                channelFuture.channel().closeFuture().sync();   //阻塞關閉,直到channel關閉
            } finally {
                eventLoopGroup.shutdownGracefully().sync();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            new NettyClient("127.0.0.1", 8888).start();
        }
    }
    
    public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    
        /**
         * 客戶端得知Channel活躍后的處理
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            //向服務器發送數據
            ctx.writeAndFlush(Unpooled.copiedBuffer("Hello, Netty", Charset.forName("UTF-8")));
        }
    
        /**
         * 客戶端接收數據后的處理
         */
        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
            System.out.println("客戶端接收到數據,內容為:" + byteBuf.toString(CharsetUtil.UTF_8));
        }
    
        /**
         * 異常處理
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }
    

    然后我們在看一看Netty服務端的實現,在服務端接受到客戶端發送的消息之后,打印出來,并原封不動的再次發送給客戶端,如下:

    public class NettyServer {
    
        private final int port;
    
        public NettyServer(int port) {
            this.port = port;
        }
    
        public void start() throws InterruptedException {
            final NettyServerHandler serverHandler = new NettyServerHandler();
            EventLoopGroup eventLoopGroup = new NioEventLoopGroup();    //線程組
            ServerBootstrap serverBootstrap = new ServerBootstrap();    //服務端啟動所需
    
            try {
                serverBootstrap.group(eventLoopGroup)
                        .channel(NioServerSocketChannel.class)    //指定使用NIO進行網絡通訊
                        .localAddress(new InetSocketAddress(port))   //指定服務器監聽端口
                        //接收到連接請求,新啟一個socket通信,即channel,每個channel都有處理自己事件的handler
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel socketChannel) throws Exception {
                                socketChannel.pipeline().addLast(serverHandler);
                            }
                        });
                ChannelFuture channelFuture = serverBootstrap.bind().sync();   //綁定端口,阻塞,直到連接完成
                channelFuture.channel().closeFuture().sync();   //阻塞關閉,直到channel關閉
            } finally {
                eventLoopGroup.shutdownGracefully().sync();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            new NettyServer(8888).start();
        }
    }
    
    @ChannelHandler.Sharable
    public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    
        /**
         * 服務端讀取到網絡數據后的處理
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf byteBuf = (ByteBuf) msg;
            System.out.println("服務器接收到數據,內容為:" + byteBuf.toString(Charset.forName("UTF-8")));
            ctx.write(byteBuf);
        }
    
        /**
         * 服務器讀取完成網絡數據后的處理
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)    //flush所有需發送的數據
                    .addListener(ChannelFutureListener.CLOSE);  //當flush完成,關閉連接
        }
    
        /**
         * 發生異常后的處理
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }
    

    其中若接收到的客戶端的數據很大,需要多次調用上述的channelRead方法來進行發送,完成后會調用channelReadComplete方法,來flush所有的數據,并關閉與客戶端的連接,客戶端中監測到與服務端連接的channel被關閉后,隨之客戶端就會關閉了,如下
    在這里插入圖片描述


    測試結果截圖如下:
    在這里插入圖片描述
    在這里插入圖片描述


    另外我們在服務端上添加了 @ChannelHandler.Sharable 注解,這里其實可以理解為該類是一個共享的,即我們在Spring中提到的單例的意思,所以我們在使用時,只需參加一個實例即可,如下
    在這里插入圖片描述
    在這里插入圖片描述

    而我們在客戶端的實現上就沒有添加該注解,所以我們每次使用都會參加一個新的實例出來
    在這里插入圖片描述

    在上述的實現中,我們可以將服務端不添加該注解,每次使用和客戶端一樣,創建出一個新的實例即可,或者我們也可以在客戶端使用添加該注解,但是需要注意的是,我們在使用共享一個實例的時候,我們需要保證該類的線程安全性,至于如何保證一個類的線程安全,我們在并發編程中也以及說過了,這里就不重復介紹了。

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

    智能推薦

    網站HTTP升級HTTPS完全配置手冊

    本文由葡萄城技術團隊于博客園原創并首發 轉載請注明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。 今天,所有使用Google Chrome穩定版的用戶迎來了v68正式版首個版本的發布,詳細版本號為v68.0.3440.75,上一個正式版v67.0.3396.99發布于6月13日,自Chrome 68起,當在加載非HTTPS站點時,都會在地址欄上明確標記為&ldqu...

    echarts 自定義儀表盤設置背景圖片

    echarts儀表盤 使用插件 vue-echarts 代碼示例 HTML部分 js部分 效果圖...

    RT-Thread Studio部分定時器時鐘不正確的解決方案

    在昨天的RT-Thread Studio硬件定時器hwtimer在stm32f411上的使用筆記中,遇到了部分定時器速度想象中和實際不一致的情況,具體表現在定時器2、3、4、5、9、10、11都正常,但定時器1要快一倍。 仔細查看代碼,找到了原因。 因為代碼使用的是工程是直接生成的時鐘代碼,實際的時鐘頻率是這樣的: 而實際的定時器時鐘配置代碼如下: 針對F411,去掉其中的宏定義是這樣的: 這里說...

    symfony學習筆記之模板渲染-----twig總結

    參考:https://blog.csdn.net/liebert/article/details/77414217 目錄 一、模板引擎工作原理 二、Twig模板引擎 1.運行環境要求 2.基本API用法 3.設計模板 (1)變量輸出         a.全局變量         b.設置變量 (2)...

    小甲魚Python3學習筆記之第六講(僅記錄學習)

    第六講:python之常用操作符 一、知識點: 0.算術運算符:+,-,*,/,%(取模,即求余數),**(冪運算),//(地板除法,取整除,返回商的整數部分) 備注:①雙斜杠 // 除法總是向下取整。             ②從符點數到整數的轉換可能會舍入也可能截斷,建議使用math....

    猜你喜歡

    Java 習題 (21)

    題目: 寫一個程序,打印從1 到 100 的值。 解答: 答案如下: (只是截取結果一部分) 如果覺得不錯,就用點贊或者關注來取代五星好評吧!...

    Pytorch 安裝

    Pytorch 安裝 已有Cuda 9.0,anaconda3,用conda命令安裝pytorch 驗證是否安裝成功 然后依次輸入 得到如下之類的輸出 驗證pytorch在當前GPU和cuda上是否能用...

    使用Python實現QQ郵箱/163郵箱的郵件發送

    QQ郵箱/163郵箱的郵件發送:py文件發送郵件內容相當于一個第三方的客戶端,借助于QQ/163郵箱服務器來發送的郵件。 主要配置:                 導入模塊——import  &...

    微信字體放大影響布局的處理

    下圖中是安卓微信中調整字體大小后的效果,行內的font-size:12px !important設置沒起作用,個人覺得是webview做了另外的處理,使用getComputedStyle獲取到的字體也確實是變大了(而且這是全局的,對所以元素都生效,包括頁面加載后append的元素),對設定了絕對值的元素則不影響。 很多webview提供了調整頁面字體大小的功能,例如手機QQ、微信、部分Androi...

    mysql查看創建的表的字段類型

    mysql所承建的所有表都保存在information_schema這個庫里,而所有的字段都在columns 這張表里面,所以想查啥就去看對應的字段就行。 比如我想查詢t_agent這張表里面所有的字段名、長度加類型、是否空,就可如下: 具體課查詢的字段如下: That’s all....

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