漫谈NIO(3)之Netty实现

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整体的一个结构。

posted @ 2018-04-25 22:19  dark_saber  阅读(1816)  评论(0编辑  收藏  举报