1. 程式人生 > >漫談NIO(3)之Netty實現

漫談NIO(3)之Netty實現

ext tty 數據 ftp nios 區別 med event sync

1.前言

上一章結合Java的NIO例子,講解了多路IO復用的一個基本使用方法,通過實際編碼加深對其理解。本章開始進入Netty的環節,前面兩章都是為了Netty進行鋪墊說明。此節將對比Java的NIO例子,說明Netty的一個基本設計,如果前面理解透徹,對Netty的學習將非常有幫助。

國際慣例,將Netty官網的基本描述放上:Netty是一個為了快速開發可維護的高性能協議服務器和客戶端的異步事件驅動的網絡應用程序框架。快速簡單並不意味著應用會受到可維護和性能問題。其設計非常謹慎,使用了多種協議,如FTP、SMTP、HTTP和各種二進制和基於文本的遺留協議。Netty成功的找到了一種方法,可以在不妥協的情況下實現開發、性能、穩定、靈活。

以上的描述主要關註的就兩點:1.異步事件驅動;2.多種協議解析。另外,Netty有較高的吞吐量,低延遲,更少的資源浪費,最小不必要的內存拷貝。

2.例子

2.1 服務端

Java的NIO中我提到服務端基礎的4個內容:1.線程池;2.端口;3.Selector;4.Channel。Netty實際上也就是這些內容,但是Netty作為一個封裝好了的框架,其不會讓我們自己獲取Selector,畢竟不是所有的IO都是這種方式,連接過程進行的封裝(連接、讀取、寫入、斷開連接),另一個就是在之前沒有講到的協議解析。我們都知道TCP實際上是有粘包的問題,一般需要一個協議避免這個問題,前面也說到了Netty支持很多協議,demo會體現這一點。

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

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup);
            bootstrap.channel(NioServerSocketChannel.class);
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                    pipeline.addLast("decoder", new StringDecoder());
                    pipeline.addLast("encoder", new StringEncoder());
                    pipeline.addLast("handler", new SimpleChannelInboundHandler<String>() {
                        @Override
                        protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
                            System.out.println("===>receive msg:" + msg);
                            ctx.channel().writeAndFlush("server get msg:" + msg +"\r\n");
                        }
                    });
                }
            });
            ChannelFuture future = bootstrap.bind(7777).sync();
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

EventLoopGroup就是Netty根據JDK提供的線程池框架進行二次封裝的一個線程池類。ServerBootstrap是啟動類,首先要對其進行一系列設置:1.設置線程池;2.設置IO方式(由channel確定);3.設置handler(handler就是Netty的一個非常重要的封裝了,實際上我們對於IO關心的就只有連接的生命周期而已,handler就是對於連接的各個生命周期提供了一個處理相應業務的入口);4.綁定端口。這個和Java的例子前面部分是不是很像,只是對於事件的處理不需要開發者關註了,取而代之的是開發者只需要關註handler對連接各個階段的處理即可,根據channel類型的不同,Netty對於IO事件的處理全部轉換成了Handler。

這裏我們主要關註的就是handler了,這對於之前是一個新概念。上面例子就設計了一個最簡單的handler了,handler采取的是職責鏈設計模式,並且其是有先後順序的,這個不能亂。想一下,讀取數據,讀出來的都是二進制,第一步當然就是對二進制進行解析,解析出一個完整的協議,多的部分不要,少了繼續等待數據到來。DelimiterBasedFrameDecoder就是進行了這個操作,其以換行符作為約定的協議。獲取協議之後第二步自然是解析協議了,StringDecoder就是這個用處,即我們希望客戶端發送的是帶換行符的字符串。那麽StringEncoder是幹啥用的?這個不是對協議進行編碼嗎?實際上hander管理了讀取的處理,也管理寫的操作,我們也需要一個協議返回給客戶端。所以handler分為兩類,in和out,in會處理read事件,out會處理write事件。最後一個handler就是自定義的一個in類型的handler了,解析完協議後我們要做什麽操作,就在這裏完成了,這個就是我們的業務層。

2.2 客戶端

客戶端之前沒有使用線程池,但是Netty依舊使用了線程池,因為又不是只有socket需要線程處理,耗時不需要同步執行的業務操作也可以使用多線程技術。其它的地方區別就不是很大了,具體看代碼。

    public static void main(String[] args) {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group);
            b.channel(NioSocketChannel.class);
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                    pipeline.addLast("decoder", new StringDecoder());
                    pipeline.addLast("encoder", new StringEncoder());
                    pipeline.addLast("handler", new SimpleChannelInboundHandler<String>() {
                        @Override
                        protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
                            System.out.println(msg);
                        }
                    });
                }
            });

            Channel ch = b.connect("127.0.0.1", 7777).sync().channel();
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            String line = null;
            while(true) {
                line = in.readLine();
                if("close".equals(line)) {
                    break;
                }
                ch.writeAndFlush(line + ‘\n‘);
            }
            ch.close().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }

上面代碼有個特殊的地方在於其只需要一個線程池,服務端設置的bossGroup看上章的內容應該也容易理解,服務端的主線程實際上是被select阻塞了的,所以服務端Netty對於主線程也交由線程池處理了,客戶端不存在這個問題。

3.Netty核心概念

上面是一個非常基礎的demo,但是麻雀雖小五臟俱全(也許不算全),其給出了Netty中幾個重要的概念。

Bootstrap或者是ServerBootstrap:Netty的啟動類,基本的參數,選擇都要在這裏設置完成。

EventLoopGroup或者是EventLoop:Netty封裝的線程池,需要針對channel選擇合適的線程池

  Channel:Netty對於不同的IO處理是由Channel決定的,Channel中有其它核心的概念,這裏不進行介紹

  Handler:處理IO各個生命周期節點的對應類,handler也產生了一系列概念,這個之後介紹。

  Future或者是Promise:這個是異步的核心,主要獲取異步操作的結果,很重要。

這5個是由demo得出來的核心內容,實際上Netty很復雜,由這些核心會引出其它的核心內容。這裏不進行介紹,相關章節會進行說明。

4.後記

本節主要是貫穿前面的章節,以Netty和Java的例子,對Netty進行一個入門的了解,有了Java例子的基礎,就不會對Netty的簡單配置最終為什麽能達到所要的效果一無所知。仔細思考就能大致明白Netty的Nio是如何處理的了。後續章節將以Netty核心概念為主題,介紹Netty整體的一個結構。

漫談NIO(3)之Netty實現