天天看點

【Netty】從0到1(六):入門-Hello World

概述

Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.

Netty 是一個異步的、基于事件驅動的網絡應用架構,用于快速開發可維護、高性能的網絡伺服器和用戶端。

需要注意的是,Netty 中的異步操作是通過多路複用來實作的。在 Java NIO 中,可以使用一個線程同時處理多個通道的讀寫操作,這就是所謂的多路複用。Netty 正是基于 Java NIO 實作的,是以它也采用了多路複用技術來實作異步操作。

雖然 Netty 的異步操作并沒有實作真正意義上的異步 I/O,但是它的性能表現非常出色,能夠很好地滿足大部分應用的需求。同時,Netty 的程式設計模型比純 NIO 更加簡潔易用,可以幫助開發者快速建構高性能、可靠的網絡應用程式。

接下來,通過使用 Netty 建構服務端與用戶端,實作第一個 Netty demo。

服務端

1、首先,通過建立一個 ServerBootstrap 執行個體來啟動伺服器,它會組裝和配置 Netty 元件,并且啟動伺服器:

new ServerBootstrap()
           

2、使用 NioEventLoopGroup 類型的事件循環組作為 BossEventLoop 和 WorkerEventLoop,BossEventLoop 管理連接配接請求,WorkerEventLoop 管理連接配接的 I/O 資料處理:

group(new NioEventLoopGroup())
           

不熟悉的讀者可以看到部落客的上一篇博文 NIO-多線程優化,博文裡詳細介紹了 Boss 與 Worker 的用法;

3、選擇了 NioServerSocketChannel 來實作伺服器端監聽 Socket 的 Channel,表示該伺服器将使用 NIO 方式進行網絡通信:

channel(NioServerSocketChannel.class)
           

4、childHandler() 方法設定了一個初始化器,它将在每個新連接配接被接受時調用。該方法中的匿名内部類 ChannelInitializer 将為每個新連接配接添加一個新的管道 pipeline,并将 initChannel() 方法回調給這個新的管道:

childHandler(  
    // channel 初始化,負責添加别的 handler  
    new ChannelInitializer<NioSocketChannel>() {  
        @Override  
        protected void initChannel(NioSocketChannel nsc) throws Exception {  
            ...
        }  
    }  
)
           

5、向 initChannel() 方法中添加兩個 handler,分别是 StringDecoder 和 ChannelInboundHandlerAdapter,其中 StringDecoder 是 Netty 提供的一個具體的消息解碼器,将位元組流轉換成字元串;ChannelInboundHandlerAdapter 則是自定義的消息處理器,當有消息到達時,将其列印出來:

@Override  
protected void initChannel(NioSocketChannel nsc) throws Exception {  
    nsc.pipeline().addLast(new StringDecoder());  
    nsc.pipeline().addLast(new ChannelInboundHandlerAdapter() {  
        @Override  
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
            System.out.println(msg);  
        }  
    });  
}
           

6、調用 bind() 方法,監聽特定的端口号 7999,以開始接受來自用戶端的連接配接請求:

bind(7999);
           

7、完整代碼:

public class HelloServer {
    public static void main(String[] args) {
        // 1. 啟動器,負責組裝 netty 元件并啟動伺服器
        new ServerBootstrap()
                // 2. BossEventLoop, WorkerEventLoop(selector, thread)
                .group(new NioEventLoopGroup())
                // 3. 選擇伺服器的 ServerSocketChannel 實作
                .channel(NioServerSocketChannel.class)
                // 4. 配置 worker(child) 能執行的操作 handler
                .childHandler(
                        // 5. channel 初始化,負責添加别的 handler
                        new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel nsc) throws Exception {
                                // 6. 添加具體的 handler
                                nsc.pipeline().addLast(new StringDecoder());
                                nsc.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                        System.out.println(msg);
                                    }
                                });
                            }
                        }
                )
                // 7. 綁定監聽端口
                .bind(7999);
    }
}
           

用戶端

1、建立 Bootstrap 執行個體,它是 Netty 庫中用于建立用戶端的啟動類:

new ServerBootstrap()
           

2、添加一個 NioEventLoopGroup 執行個體作為 EventLoop,用于處理 I/O 操作:

group(new NioEventLoopGroup())
           

3、設定 channel 類型為 NioSocketChannel,表示使用 NIO 進行網絡通信:

channel(NioServerSocketChannel.class)
           

4、添加一個 ChannelInitializer 執行個體,在連接配接建立後對 channel 進行初始化。這裡添加了一個 StringEncoder,用于将字元串編碼成位元組流以進行傳輸:

handler(new ChannelInitializer<NioSocketChannel>() {
    @Override
    protected void initChannel(NioSocketChannel nsc) throws Exception {
        nsc.pipeline().addLast(new StringEncoder());
    }
})
           

5、調用 connect 方法,與伺服器建立連接配接,并傳回一個 ChannelFuture 執行個體,通過調用 sync 方法等待連接配接成功:

.connect(new InetSocketAddress(7999))  
.sync()
           

6、擷取連接配接成功後的 channel 執行個體,通過 writeAndFlush 方法向伺服器發送字元串消息:

.channel()  
.writeAndFlush("Hello World! --sidiot.");
           

7、完整代碼:

public class HelloClient {
    public static void main(String[] args) throws InterruptedException {
        // 1. 啟動用戶端
        new Bootstrap()
                // 2. 添加 EventLoop
                .group(new NioEventLoopGroup())
                // 3. 選擇 channel 實作
                .channel(NioSocketChannel.class)
                // 4. 添加 handler
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nsc) throws Exception {
                        nsc.pipeline().addLast(new StringEncoder());
                    }
                })
                // 5. 連接配接到伺服器
                .connect(new InetSocketAddress(7999))
                .sync()
                .channel()
                // 6. 向伺服器發送資料
                .writeAndFlush("Hello World! --sidiot.");
    }
}
           

運作結果:

Hello World! --sidiot.
           

流程分析

【Netty】從0到1(六):入門-Hello World

伺服器先行啟動,步驟1到步驟5按代碼順序進行執行,但是 initChannel 需要在建立連接配接後才會被執行,是以步驟6是綁定監聽端口 bind(7999);

再啟動用戶端,步驟7到步驟11按代碼順序進行執行,步驟12在連接配接建立後對 channel 進行初始化,步驟13中 sync() 方法等待連接配接成功。

然後進行步驟14,用戶端向服務端發送資料,在這個過程中,資料會先經過步驟15進行加密,再發送至服務端,由步驟16中相應的 eventLoop 進行處理,步驟17将接收到的資料進行解密,最後是步驟18,執行 read 方法,列印資料。

在這些步驟中,用到了 channel,handler 和 eventLoop 等元件,接下來稍作解釋:

  • channel:資料的傳輸通道;
  • handler:資料的處理工序: handler 分為 Inbound 和 Outbound 兩類:Inbound 表示入站,Outbound 表示出站; pipeline 代表了 Netty 中的一個處理鍊,負責釋出事件(讀、讀取完成等)傳播給每個 handler, handler 對自己感興趣的事件進行處理(重寫了相應事件處理方法),每個 handler 都會按順序依次處理傳入和傳出的資料流,直到最後一個完成其工作并将響應發送回用戶端。
  • eventLoop:處理資料的勞工: eventLoop 可以管理多個 channel 的 I/O 操作,并且一旦 eventLoop 負責了某個 channel,就會将其與這個 channel進行綁定,以後該 channel 中的 I/O 操作都由該 eventLoop 負責; eventLoop 既可以執行 I/O 操作,也可以進行任務處理,每個 eventLoop 有自己的任務隊列,隊列裡可以堆放多個 channel 的待處理任務,任務分為普通任務、定時任務; eventLoop 按照 pipeline 順序,依次按照 handler 的規劃(代碼)處理資料,可以為每個 handler 指定不同的 eventLoop;

後記

以上就是 從0到1(六):入門-Hello World 的所有内容了,希望本篇博文對大家有所幫助!

連結:https://juejin.cn/post/7235539402867949605

繼續閱讀