Netty(二)—從Java NIO到Netty

從一段代碼開始說起

  • Java NIO實現
public class demo{
    public void socket() {
        //  使用 socket 提供的  ServerSocketChannel建立一個 通信通道 channel
        ServerSocketChannel server = ServerSocketChannel.open();
        //  進行 IP 端口綁定,默認本地IP
        server.bind(new InetSocketAddress("port"));
        //  手動設置非阻塞
        server.configurationBlocking(false);
        //  打開選擇器  可以開始接收請求了
        Selector selector = Selector.open();
        // ON_ACCEPT :設置爲可接收請求狀態
        server.register(this.selector, SelectionKey.OP_ACCEPT);
        //  使用輪詢的方法 處理請求
        while (true) {
            /*   多路複用體現
             *       多路:select();方法可以接收多個TCP連接請求
             *       複用:複用一個主線程去處理多個請求
             */  
            //  接收所有請求  select()方法會阻塞,直到拿到一個新的key
            selector.select();
            //  拿到請求
            Set<SelectionKey> selectionKeys = this.selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            //  處理請求.....
            while (iterator.hasNext()) {
                //  每一個key代表一種狀態
                SelectionKey key = iterator.next();
                //  每一個key都有一個通道去處理
                key.channel();
                //  to  do
            }
        }
    }
}

Netty的做法

  • 通過複習java NIO的實現,對NIO有了更深一層的記憶
  • Netty是基於Java的一個高性能的RPC框架
  • Netty提供瞭如 零拷貝、內存池、無鎖串行化設計理念、Reactor線程模型 等高性能操作
  • 迴歸正題:Netty既然是基於Java實現的,那麼利用Netty編程和使用Java實現NIO必然有異曲同工之處

Netty代碼示例

在這裏插入圖片描述

  • 說實話,屬實不知道怎麼去搞懂它
  • 不過記住一件事,這裏做的事情和java NIO做的事情是一樣的,只不過性能更優
  • 其實從 serverBootstrap.bind() 方法也可以看出,是綁定端口的操作

從Channel()開始學起

  • Java NIO中有 ServerSocketChannel.open() 建立一個channel對象
  • 那麼Netty中必然也有Channel對象,從代碼中也可以窺見一二
  • Netty的 Channel.java

在這裏插入圖片描述

  • 部分 API
    • 連接到socket網絡或者組件進行IO操作、讀、寫、綁定或連接
    • A channel providers a user : 用來提供Channel對象的

在這裏插入圖片描述

  • 通過其很多的實現類可以瞭解到,Netty.Channel針對不同的連接協議提供了支持

Channel的創建

  • 從代碼入手,本例 完成了 對 NioServerSocketChannel的初始化
ServerBootStrap 提供了 channel()  方法  其本身是由 Netty 提供用來完成Netty客戶端/服務端初始化操作的

在這裏插入圖片描述

channel方法  返回了  一個工廠對象

在這裏插入圖片描述

  • 通過跟蹤代碼,channel初始化操作是由 serverBootstrap.bind(port) 完成
    在這裏插入圖片描述
  • 交給 channelFactory 工廠來創建
    在這裏插入圖片描述
  • 最終通過反射進行 Channel() 對象的創建
    在這裏插入圖片描述
  • 隨後調用 NioServerSocketChannel 的構造方法 NioServerSocketChannel 在 調用Channel()方法階段設置
    在這裏插入圖片描述
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
  • 接下來就是通過父類的層層調用—直到 AbstractChannel 的構造器

在這裏插入圖片描述在這裏插入圖片描述

  • 至此,以 NioServerSocketChannel 爲例的 Channel初始化就完成了
總結
  • NioServerSocketChannel 構造方法中,使用 newSocket 方法新建 一個 ServerSocketChannel
  • 需要初始化的屬性在 AbstractChannel 中完成,並 爲每一個channel設置了一個唯一的id
    • 在執行AcstractChannel構造器的時候同時新建了一個unsafe對象並完成了pipeline對象的實例化
  • 在代碼編程方面
    • 我們可以理解到,ServerBootstrap 完成了初始化預設參數
    • 在調用 bind 方法的時候完成註冊和端口綁定

Unsafe的初始化

  • 在 Channel 的創建過程中,在父類 AbstractChannel 的構造方法中調用 newUnsafe() 方法來獲取一個unsafe實例
  • 瞭解一下 Unsafe 接口所提供的方法
interface Unsafe {
    RecvByteBufAllocator.Handle recvBufAllocHandle();
    SocketAddress localAddress();
    SocketAddress remoteAddress();
    void register(EventLoop eventLoop, ChannelPromise promise);
    void bind(SocketAddress localAddress, ChannelPromise promise);
    void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
    void disconnect(ChannelPromise promise);
    void close(ChannelPromise promise);
    void closeForcibly();
    void deregister(ChannelPromise promise);
    void beginRead();
    void write(Object msg, ChannelPromise promise);
    void flush();
    ChannelPromise voidPromise();
    ChannelOutboundBuffer outboundBuffer();
}
  • Netty提供的Unsafe類對Java底層Socket的操作進行了封裝,是Netty和Java底層**”溝通“**的重要橋樑
  • Unsafe類以內部類的方式實現,同時避免了上層業務的調用

Pipeline初始化

  • 在ChannelPipline的註釋中有這樣一句話

在這裏插入圖片描述

  • 在上文Channel初始化過程中,AbstractChannel構造器中完成了對Pipline的初始化
    • 傳入 Channel 對象作爲初始化參數,進行綁定

在這裏插入圖片描述

  • 對Pipline暫時先了解到這裏

關於EventLoop

  • 最開始的時候我們創建了兩個NioEventLoopGroup對象

    • 通過其構造方法可以發現其構造器中可傳入線程數量、線程工廠對象、線程池對象進行初始化
      在這裏插入圖片描述
  • 通過跟蹤代碼瞭解其構造器參數的意義

    • 首先Neyyt通過io.netty.eventLoopThreads從系統屬性中獲取值
    • 默認值爲處理器核心數*2
      在這裏插入圖片描述
  • 最終會調用到 MultithreadEventExecutorGroup 的構造方法

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                        EventExecutorChooserFactory chooserFactory, Object... args) {
    ......
    children = new EventExecutor[nThreads];
    ......
    chooser = chooserFactory.newChooser(children);
    ......
}
  • 這裏的判斷意思是:如果nThreads是2的冪,則使用PowerOfTwoEventExecutorChooser否則GenericEventExecutorChooser
    在這裏插入圖片描述

  • 兩種 Chooser 都重寫了 next() 方法
    在這裏插入圖片描述

  • next()方法中的運算邏輯是數組索引循環位移

  • 當索引移動到最後要給位置,再次調用next()方法將會使索引位置重新指向0

    • 運算邏輯:每次索引自增後和數組長度取模 idx.getAndIncrement() % executors.length
    • Netty優化算法:idx.getAndIncrement() & executors.length-1
    • 注:在計算機底層&比%運算效率更高

總結

  • EventLoop其實就是創建了一個大小爲nThreads類型爲EventExecutor的 childern 數組,構成了一個線程池
  • 當我們實例化NioEventLoopGroup的時候,如果指定線程池大小,則 nThreads 就是指定的值,反之爲處理器核心數*2

Channel註冊到Seclector

  • 當Channel在 initAndRegister() 方法中註冊的時候還會將初始化完成的Channel註冊到NioEventloop的Selector中
//  initAndRegister()方法
ChannelFuture regFuture = config().group().register(channel);
  • 其調用關係如下
//  MultithreadEventLoopGroup.java
public ChannelFuture register(Channel channel) {
    return next().register(channel);
}
//  SingleThreadEventLoop.java
public ChannelFuture register(Channel channel) {
    return register(new DefaultChannelPromise(channel, this));
}
//   AbstractChannel.java
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    AbstractChannel.this.eventLoop = eventLoop;      
    register0(promise);
}
private void register0(ChannelPromise promise) {
    doRegister();
}
  • 到這裏,實現了SocketChannel註冊到EventLoop對象的selector上

在這裏插入圖片描述

  • 註冊流程總結

在這裏插入圖片描述

  • 本質上所做的事情就是建channel與對應的EventLoop關聯。關聯完成,把Channel註冊到EcentLoop對象中的selector中

如何添加Handler

  • 在開始時有這樣的代碼,這裏體現了Netty的Handler機制
.group().channel().childHandler
  • 從官方偷來一段註釋瞧瞧
    • 處理IO事件或攔截IO操作,然後交給其ChannelPipline去處理
Handles an I/O event or intercepts an I/O operation, and forwards it to its next handler in its 
{@link ChannelPipeline}.
  • 簡單理解就是很強大還支持擴展。針對不同場景使用不同的Handler,先了解到這裏

通過這次代碼分析流程,有助於後續學習Netty

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章