[Netty學習筆記]八、Netty核心組件介紹

Netty核心組件介紹

在瞭解新知識之前,先來對前面的知識回顧下:

  1. Netty抽象出兩組線程池,BossGroup專門負責接收客戶端連接,WorkerGroup專門負責網絡讀寫
  2. NioEventLoop表示一個不斷循環執行處理任務的線程,每個NioEventLoop都有一個selector,用於監聽綁定在其上的socket網絡通道
  3. NioEventLoop內部採用串行化設計,從消息的讀取->解碼->編碼->發送,始終由IO線程NioEventLoop負責
  4. NioEventLoopGroup下包含多個NioEventLoop
  5. 每個NioEventLoop中包含一個Selector,一個taskQueue
  6. 每個NioEventLoop的Selector上可以註冊監聽多個NioChannel
  7. 每個NioChannel只會綁定在唯一的NioEventLoop上
  8. 每個NioChannel都綁定有一個自己的ChannelPipeline
任務隊列中Task的使用

關於示例代碼以NettyServerHandler舉例

1. 用戶程序自定義的普通任務

public class NettyServerCustomTaskHandler extends NettyServerHandler {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //比如有個很耗時的業務->異步執行->提交到該channel對應的NioEventLoop下的taskQueue中
        ctx.channel().eventLoop().execute(() -> {
            try {
                Thread.sleep(5 * 1000);
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello,這是一個耗時操作", CharsetUtil.UTF_8));
                System.out.println("channel code=" + ctx.channel().hashCode());
            } catch (Exception ex) {
                System.out.println("發生異常" + ex.getMessage());
            }
        });
    }

}

2.用戶自定義定時任務

public class NettyServerCustomTaskHandler extends NettyServerHandler {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //用戶自定義定時任務 ->該任務是提交到 scheduledTaskQueue 中
        ctx.channel().eventLoop().schedule(() -> {
            try {
                Thread.sleep(5 * 1000);
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello,這是一個耗時操作", CharsetUtil.UTF_8));
                System.out.println("channel code=" + ctx.channel().hashCode());
            } catch (Exception ex) {
                System.out.println("發生異常" + ex.getMessage());
            }
        }, 5, TimeUnit.SECONDS);

    }

}

3.非當前Reactor線程調用channel的各種方法

例如在推送系統的業務線程裏,根據用戶標誌找到對應的Channel引用,然後調用write類方法向該用戶推送消息,就會進入到這種場景。最終的write會提交到任務隊列中後被異步消費

異步模型

基本介紹

  1. 異步的概念和同步相對。當一個異步過程調用發出後,調用者不能立刻得到結果。實際處理這個調用的組件在完成後,通過狀態通知和回調來通知調用者。
  2. Netty中的IO操作時異步的。包括Bind/Write/Connect等操作會簡單的返回一個ChannelFuture。
  3. 調用者並不能立刻獲得結果,而是通過Future-Listener機制,用戶可以方便主動地獲取或者通過通知機制獲取IO操作結果
  4. Netty的異步模型是建立在future和callback(回調)之上的。Future的核心思想是:假設一個方法fun,計算過程可能非常耗時,等待fun返回顯然不太合適。那可以在調用fun的時候立馬返回一個future,後續可以通過Future去監控方法fun的處理過程(Future-Listener機制)
Future說明
  1. 表示異步的執行結果,可以通過它提供的方法來檢測執行是否完成,比如檢索計算等
  2. ChannelFuture是一個接口,我們可以添加監聽器,當監聽的事件發生時就會通過監聽器
Future-Listener機制
  1. 當Future對象剛剛創建時,處於非完成狀態,調用者可以通過返回的ChannelFuture來獲取操作執行的狀態,註冊監聽函數來執行完成後的操作
  2. 常見有如下操作
    • 通過isDone方法來判斷當前操作是否完成
    • 通過isSuccess方法來判斷已完成的當前操作是成功
    • 通過getCause方法來獲取已完成的當前操作失敗的原因
    • 通過isCancelled方法來判斷已完成的當前操作是否被取消
    • 通過addListener方法來註冊監聽器,當操作已完成(isDone方法返回完成),將會通知指定的監聽器,如果Future對象已完成,則通知指定的監聽器

示例

public class NettyServer {

    public static void main(String[] args) {

        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workGroup)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {

                            socketChannel.pipeline().addLast(new NettyServerCustomTaskHandler());

                        }
                    });

            ChannelFuture channelFuture = bootstrap.bind(6668).sync();
//給 channelFuture 註冊監聽器,監控我們關心的事件
            channelFuture.addListener(new GenericFutureListener<Future<? super Void>>() {
                @Override
                public void operationComplete(Future<? super Void> future) throws Exception {
                    if(channelFuture.isDone()){
                        System.out.println("已完成");
                    }
                    if(channelFuture.isSuccess()){
                        System.out.println("已成功");
                    }
                }
            });

            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

}

示例:基於Netty實現一個簡單的HTTP服務

  1. Netty服務器在6668端口監聽,瀏覽器發出請求"http://localhost:16668/"
  2. 服務器回覆消息給瀏覽器或客戶端消息,並對指定請求資源進行過濾
//服務器端
public class NettyBrowserServer {

    public static void main(String[] args) {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);

        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();

            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast("serverCodec", new HttpServerCodec());
                            ch.pipeline().addLast(new NettyBrowserHandler());
                        }
                    });

            ChannelFuture channelFuture = serverBootstrap.bind(16668).sync();
            channelFuture.addListener(future -> {
                if (channelFuture.isSuccess()) {
                    System.out.println("運行成功");
                }
            });
            channelFuture.channel().closeFuture().sync();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }


    }

}


//handler
/**
 * Created by myk
 * 2020/1/21 下午2:28
 * 1、SimpleChannelInboundHandler是ChannelInboundHandlerAdapter的子類
 * 2、客戶端和服務器端通訊的數據放在HttpObject中
 */
public class NettyBrowserHandler extends SimpleChannelInboundHandler<HttpObject> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {


        //判斷是否爲Http請求
        if (msg instanceof HttpRequest) {
            System.out.println("pipeline hashcode" + ctx.pipeline().hashCode() + ",NettyBrowserHandler.hash=" + this.hashCode());
            System.out.println("消息類型" + msg.getClass());
            System.out.println("客戶端地址:" + ctx.channel().remoteAddress());
            HttpRequest httpRequest = (HttpRequest) msg;
            //瀏覽器請求時會請求圖標favicon.ico 這裏沒有這個圖標因此將這個給過濾掉
            if (Objects.equals(httpRequest.uri(), "/favicon.ico")) {
                return;
            }

            //回覆信息給瀏覽器
            ByteBuf byteBuf = Unpooled.copiedBuffer("hello,這是瀏覽器", CharsetUtil.UTF_16);
            //給瀏覽器回覆信息 要符合http協議規範'
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);
            //設置content-type=text-plain
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            //設置content-length
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
            //將構建好 response 返回
            ctx.writeAndFlush(response);

        }

    }
}

瀏覽器的輸出結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-rSJJz2k4-1580353534681)(/Users/wojiushiwo/Library/Application Support/typora-user-images/image-20200122190312737.png)]

Bootstrap、ServerBootstrap介紹
  1. Bootstrap意思是引導,一個Netty應用通常由一個Bootstrap開始,主要作用是配置整個Netty程序,串聯各個組件,Netty中Bootstrap類是客戶端程序的啓動引導類,ServerBootstrap是服務端啓動引導類,

  2. 常見的方法有

    public ServerBootstrap group(EventLoopGroup parentGroup,EventLoopGroup childGroup)//該方法用於服務器端,用來設置兩個EventLoop
    
    public B group(EventLoopGroup group);//該方法用於客戶端,用來設置一個EventLoop
    public B channel(Class<? extends C> channelClass);//該方法用來設置一個服務器端的通道實現
    public <T> B option(ChannelOption<T> option,T value);//用來給ServerChannel添加配置
    public <T> ServerBootstrap childOption(ChannelOption<T> childOption,T value);//用來給接收到的通道添加配置
    public ServerBootstrap childHandler(ChannelHandler childHandler);//該方法用來設置業務處理類(自定義的handler)
    public ChannelFuture bind(int inetPort);//該方法用於服務器端,用來設置佔用的端口號
    public ChannelFuture connect(String inetHost,int inetPort);//該方法用於客戶端,用來連接服務器端
    
Future、ChannelFuture介紹

Netty中所有的IO操作都是異步的,不能立刻得知消息是否被正確處理。但是可以過一會兒等它執行完成或者直接註冊一個監聽,具體的實現就是通過Future和ChannelFutures,它們可以註冊一個監聽,當操作執行成功或失敗時監聽會自動觸發註冊的監聽事件

常見的方法有:

Channel channel();//返回當前正在執行IO操作的通道
ChannelFuture sync();//等待異步操作執行完畢
Channel介紹
  1. Netty網絡通信的組件,能夠用於執行網絡IO操作
  2. 通過Channel可獲得當前網絡連接的通道的狀態
  3. 通過Channel可獲得網絡連接的配置參數(例如接收緩衝區的大小)
  4. Channe提供異步的網絡IO操作(如建立連接、讀寫、綁定端口),異步調用意味着任何IO調用都將立即返回,並且不保證在調用結束時所請求的IO操作已完成
  5. 調用立即返回一個ChannelFuture實例,通過註冊監聽器到ChannelFuture上,可以IO操作成功、失敗或取消時回調通知對方
  6. 支持關聯IO操作與對應的處理程序
  7. 不同協議、不同阻塞類型的鏈接都有不同的Channel類型與之對應,常用的Channel類型:
    • NioSocketchannel 異步客戶端TCP Scoket連接
    • NioServerSocketChannel,異步的服務器端TCP Socket連接
    • NioSctpChannel,異步的UDP連接
    • NioDatagramChannel,異步的UDP連接
    • NioSctpServerChannel,異步的Sctp服務器端連接,這些通道涵蓋了UDP和TCP網絡IO以及文件IO
Selector
  1. Netty基於Selector對象實現IO多路複用,通過Selector一個線程可以監聽多個連接的Channel事件。
  2. 當向一個Selector中註冊Channel後,Selector內部的機制就可以自動不斷地查詢(Select)這些註冊的Channel是否有已就緒的IO事件(例如可讀、可寫、網絡連接完成等),這些程序就可以很簡單地使用一個線程高效地管理多個Channel
ChannelHandler及其實現類
  1. ChannelHandler是一個接口,處理IO事件或攔截IO操作,並將其轉發到其ChannelPipeline(業務處理鏈)中的下一個處理程序
  2. ChannelHandler本身並沒有提供很多方法,因爲這個接口有很多的方法需要實現,使用期間可以繼承它的子類

在這裏插入圖片描述

  • ChannelInboundHandler用於處理入站IO事件

  • ChannelOutboundHandler用於處理出站IO操作

    //適配器

  • ChannelInboundHandlerAdapter用戶處理入站IO事件

  • ChannelOutboundHandlerAdapter用於處理出站IO操作

  • ChannelDuplexHandler用於處理入站和出站事件

對常用方法的介紹(部分未列出):

void channelRegistered(ChannelHandlerContext ctx) throws Exception;

  
    void channelUnregistered(ChannelHandlerContext ctx) throws Exception;

  	//通道就緒事件
    void channelActive(ChannelHandlerContext ctx) throws Exception;

    //通道不活躍事件
    void channelInactive(ChannelHandlerContext ctx) throws Exception;

    //通道讀取數據事件
    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;

    //通道讀取完畢事件
    void channelReadComplete(ChannelHandlerContext ctx) throws Exception;

    //異常捕獲事件
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;

執行順序:handlerAdded->channelRegistered->channelActive->channelReadComplete->channelInactive->channelUnregistered->handlerRemoved

Pipeline和ChannelPipeline的介紹
  1. ChannelPipeline是Handler的集合,它負責處理和攔截inbound或者outbound的事件和操作,相當於一個貫穿Netty的鏈(ChannelPipeline是保存ChannelHandler的list,用於處理或攔截Channel的入站事件和出站操作)

  2. ChannelPipeline實現了一種高級形式的攔截過濾器模式,使用戶可以完全控制事件的處理方式,以及channel中各個ChannelHandler如何相互交互

  3. 在Netty中每個Channel都有且僅有一個ChannelPipeline與之對應,它們的組成關係如下:

在這裏插入圖片描述

說明:

一個channel包含了一個ChannelPipeline,而ChannelPipeline中又維護了一個由ChannelHandlerContext組成的雙向鏈表,並且每個ChannelHandlerContext中又關聯着一個ChannelHandler

入站事件和出站事件在一個雙向鏈表中,入站事件會從鏈表head往後傳遞到最後一個入站的handler,出站事件會從鏈表tail往前傳遞到最前一個出站的handler,兩種類型的handler互不干擾。

常用方法

ChannelPipeline addFirst(ChannelHandler... handlers);//把一個業務處理類(handler)添加到鏈中的每一個位置
ChannelPipeline addLast(ChannelHandler...handlers);//把一個業務處理類(handler)添加到鏈中的最後一個位置
ChannelHandlerContext介紹
  1. 保存Channel相關的所有上下文信息,同時管理一個ChannelHandler對象

  2. ChannelHandlerContext中包含一個具體的事件處理器ChannelHandler,同時ChannelHandlerContext也綁定了對應的pipeline和Channel的信息,方便對ChannelHandler進行調用

  3. 常用方法

    ChannelFuture close();//關閉通道
    ChannelOutboundInvoker flush();//刷新
    ChannelFuture writeAndFlush(Object msg);//將數據寫到ChannelPipeline中當前ChannelHandler的下一個ChannelHandler開始處理(出站)
    
ChannelOption介紹

Netty在創建Channel實例後,一般都需要設置ChannelOption參數:

ChannelOption.SO_BACKLOG

對應TCP/IP協議listen函數中的backlog參數,用來初始化服務器可連接隊列大小。服務器端處理客戶端連接請求是順序處理的,所以同一時間只能處理一個客戶端連接。多個客戶端的時候,服務端將不能處理的客戶端連接請求放在隊列中等待處理,backlog參數指定了隊列的大小

ChannelOption.SO_KEEPALIVE

一直保持連接活動狀態

EventLoopGroup和實現類NioEventLoopGroup的介紹
  1. EventLoopGroup是一組EventLoop的抽象,Netty爲了更好的利用多核CPU資源,一般會有多個EventLoop同時工作,每個EventLoop維護者一個Selector實例

  2. EventLoopGroup提供next接口,可以從組裏面按照一定規則獲取其中一個EventLoop來處理任務。在服務端編程中,我們一般需要提供兩個EventLoopGroup,BossEventLoopGroup和WorkerEventLoopGroup

  3. 通常一個服務端口即一個ServerSocketChannel對應一個Selector和一個EventLoop線程。BossEventLoop負責接收客戶端的連接並將SocketChannel交給WorkerEventLoopGroup來進行IO處理,

    在這裏插入圖片描述

    說明:

BossEventLoopGroup通常是一個單線程的EventLoop,EventLoop維護着一個註冊了ServerSocketChannel的Selector實例,BossEventLoop不斷輪詢Selector將連接事件分離出來。然後將接收到的SocketChannel交給WorkerEventLoopGroup。WorkerEventLoopGroup會由next選擇其中一個EventLoop來將SocketChannel註冊到其維護的Selector,並對其後續的IO事件進行處理。

常用方法:

public NioEventLoopGroup();//構造方法
public Future<?> shutdownGracefully();//斷開連接,關閉線程
發佈了101 篇原創文章 · 獲贊 22 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章