什麼是 Netty

LZC發表於2020-06-17

Netty是由JBoss開發,基於Java NIO的一個高效能通訊框架。之前幾篇文章介紹了Java NIO的一些基本的概念和API。但在實際的網路開發中,其實很少使用Java NIO原生的API。主要有以下原因:

  • 原生API使用單執行緒模型,不能很好利用多核優勢,如果自己去寫多執行緒結合起來比較麻煩;
  • 原生API是直接使用的IO資料,沒有做任何封裝處理,對資料的編解碼、TCP的粘包和拆包、客戶端斷連、網路的可靠性和安全性方面沒有做處理;

在《Netty權威指南》這本書裡提到一個真實的故事,兩個專案團隊都要做基於NIO非阻塞特性的一個高效能、非同步和高可靠性的底層通訊框架,但一個團隊選擇了基於Java NIO API從頭開發,另一個團隊選擇了基於Netty開發。最終,從頭開發的團隊遇到了各種各樣的問題,導致專案延遲,而基於Netty開發的團隊則進展順利。

其實網路開發是一個比較複雜的事情,因為網路的不穩定性,通常會遇到各種各樣的問題,比如前面提到的客戶端突然斷連、TCP的拆包和沾包等等。

幸運的是,網路上已經有了這麼一個成熟的框架幫我們處理了這些事情,這個框架就是Netty。Netty主要應用於以下領域:

  • 高效能的RPC框架:常用於微服務之間的高效能遠端呼叫(如Dubbo)
  • 遊戲行業:Netty可以很輕鬆地定製和開發一個私有協議棧,
  • 即時通訊:Netty基於Java NIO,並且做了一些最佳化,支援高效能的即時通訊

這裡有一個Netty的功能特性的圖:

功能特性

Netty Core提供了基本功能,包括檔案零複製、基本的API、可擴充套件的基於事件的模型(下文詳細介紹)。

Netty支援非常多的協議,比如HTTP、WebSocket等。當然,Netty也可以自定義協議。常見協議的示例程式碼可以參考netty原始碼裡面的example包。

Netty同時支援Java的BIO和NIO兩種方式。且很容易與Spring等主流框架進行整合。

首先介紹處理事件的兩種方式:

  • 輪詢方式:執行緒不斷輪詢訪問相關事件發生源有沒有發生事件,有發生事件就呼叫事件處理邏輯。Java 原生的NIO就是使用的輪詢方式。
  • 事件驅動方式,發生事件,主執行緒把事件放入事件佇列,在另外執行緒不斷迴圈消費事件列表中的事件,呼叫事件對應的處理邏輯處理事件。事件驅動方式也被稱為訊息通知方式,其實是設計模式中觀察者模式的思路。

Reactor是反應堆的意思。Reactor執行緒模型是指透過一個或多個輸入,同時傳遞給服務處理器的服務請求的事件驅動處理模式。

Reactor模式主要工作原理如下圖:

reactor執行緒模型

Reactor 有一個專門負責監聽和分發事件的執行緒,如圖中的Service Handler,所有請求進來後,被它分發到具體的處理執行緒,如圖中的Event Handler去處理。

Reactor可能有多個,而Netty正是使用了多Reactor的執行緒模型。

netty工作原理架構圖

Netty裡面有兩個Group,分別是Boss Group和Worker Group。其中,Boss Group用於處於連線,Worker Group用於處理實際的讀寫IO。

Boss Group裡面的NioEventLoop會輪詢accept事件,遇到有新的連線,就生成NioSocketChannel,並把這個Channel註冊到Worker Group的Selector上。

Worker Group輪詢read和write事件,在可讀或者可寫條件滿足時,就進行處理。

Worker Group和Boss Group都是透過裡面的NioEventLoop來操作的。NioEventLoop中維護了一個執行緒和任務佇列,支援非同步提交執行任務,執行緒啟動時會呼叫NioEventLoop的run方法。

最後都會執行一個runAllTasks方法,它用於處理任務佇列中的任務。任務佇列中的任務包括使用者呼叫 eventloop.execute或schedule執行的任務,或者其他執行緒提交到該eventloop的任務。

以下是使用Netty建立一個Server的示例程式碼。Client也可以使用Netty來建立,也可以使用之前文章裡面的NIO示例程式碼。這裡就不展示基於Netty的Client端的程式碼,只展示Server端的程式碼了。

public class Server {

    private final static int PORT = 8080;

    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChildChannelHandler());

            ChannelFuture f = b.bind(PORT).sync();
            System.out.println(Thread.currentThread().getName() +
                    ",伺服器開始監聽埠,等待客戶端連線.........");

            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }


    private static class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            socketChannel.pipeline().addLast(new ServerHandler());
        }
    }

    static class ServerHandler extends ChannelInboundHandlerAdapter {

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf buf = (ByteBuf) msg;
            byte[] reg = new byte[buf.readableBytes()];
            buf.readBytes(reg);
            String body = new String(reg, StandardCharsets.UTF_8);
            System.out.println(Thread.currentThread().getName() +
                    ",The server receive  order : " + body);

            String respMsg = "I am Server,訊息接收 success!";
            ByteBuf respByteBuf = Unpooled.copiedBuffer(respMsg.getBytes());
            ctx.write(respByteBuf);
        }
    }
}
預覽

作者:公眾號_xy的技術圈

連結:www.imooc.com/article/289251

來源:慕課網

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章