乐趣区

关于netty:netty系列之netty初探

简介

咱们罕用浏览器来拜访 web 页面失去相干的信息,通常来说应用的都是 HTTP 或者 HTTPS 协定,这些协定的实质上都是 IO,客户端的申请就是 In,服务器的返回就是 Out。然而在目前的协定框架中,并不能齐全满足咱们所有的需要。比方应用 HTTP 下载大文件,可能须要长连贯期待等。
咱们也晓得 IO 形式有多种多样的,包含同步 IO,异步 IO,阻塞 IO 和非阻塞 IO 等。不同的 IO 形式其性能也是不同的,而 netty 就是一个基于异步事件驱动的 NIO 框架。

本系列文章将会探讨 netty 的具体应用,通过原理 + 例子的具体联合,让大家理解和意识 netty 的魅力。

netty 介绍

netty 是一个优良的 NIO 框架,大家对 IO 的第一映像应该是比较复杂,尤其是跟各种 HTTP、TCP、UDP 协定打交道,应用起来非常复杂。然而 netty 提供了对这些协定的敌对封装,通过 netty 能够疾速而且简洁的进行 IO 编程。netty 易于开发、性能优良同时兼具稳定性和灵活性。如果你心愿开发高性能的服务,那么应用 netty 总是没错的。

netty 的最新版本是 4.1.66.Final, 事实上这个版本是官网举荐的最稳固的版本,netty 还有 5.x 的版本,然而官网并不举荐。

如果要在我的项目中应用,则能够引入上面的代码:

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.66.Final</version>
        </dependency>

上面咱们将会从一个最简略的例子,体验 netty 的魅力。

netty 的第一个服务器

什么叫做服务器?可能对外提供服务的程序就能够被称为是服务器。建设服务器是所有对外服务的第一步,怎么应用 netty 建设一个服务器呢?服务器次要负责解决各种服务端的申请,netty 提供了一个 ChannelInboundHandlerAdapter 的类来解决这类申请,咱们只须要继承这个类即可。

在 NIO 中每个 channel 都是客户端和服务器端沟通的通道。ChannelInboundHandlerAdapter 定义了在这个 channel 上可能呈现一些事件和状况,如下图所示:

如上图所示,channel 上能够呈现很多事件,比方建设连贯,敞开连贯,读取数据,读取实现,注册,勾销注册等。这些办法都是能够被重写的,咱们只须要新建一个类,继承 ChannelInboundHandlerAdapter 即可。

这里咱们新建一个 FirstServerHandler 类,并重写 channelRead 和 exceptionCaught 两个办法,第一个办法是从 channel 中读取音讯,第二个办法是对异样进行解决。

public class FirstServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 对音讯进行解决
        ByteBuf in = (ByteBuf) msg;
        try {log.info("收到音讯:{}",in.toString(io.netty.util.CharsetUtil.US_ASCII));
        }finally {ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 异样解决
        log.error("出现异常",cause);
        ctx.close();}
}

下面例子中,咱们收到音讯后调用 release() 办法将其开释,并不进行理论的解决。调用 release 办法是在音讯应用实现之后罕用的做法。下面代码将 msg 进行了 ByteBuf 的强制转换,如果并不想进行转换的话,能够间接这样应用:

        try {// 音讯解决} finally {ReferenceCountUtil.release(msg);
        }

在异样解决办法中,咱们打印出异样信息,并敞开异样的上下文。

有了 Handler, 咱们须要新建一个 Server 类用来应用 Handler 创立 channel 和接管音讯。接下来咱们看一下 netty 的音讯解决流程。

在 netty 中,对 IO 进行解决是应用多线程的 event loop 来实现的。netty 中的 EventLoopGroup 就是这些 event loop 的抽象类。

咱们来察看一下 EventLoopGroup 的类构造。

能够看出 EventLoopGroup 继承自 EventExecutorGroup,而 EventExecutorGroup 继承自 JDK 自带的 ScheduledExecutorService。

所以 EventLoopGroup 实质是是一个线程池服务,之所以叫做 Group,是因为它外面蕴含了很多个 EventLoop, 能够通过调用 next 办法对 EventLoop 进行遍历。

EventLoop 是用来解决注册到该 EventLoop 的 channel 中的 IO 信息,一个 EventLoop 就是一个 Executor, 通过一直的提交工作进行执行。当然,一个 EventLoop 能够注册多个 channel,不过个别状况下并不这样解决。

EventLoopGroup 将多个 EventLoop 组成了一个 Group,通过其中的 next 办法,能够对 Group 中的 EventLoop 进行遍历。另外 EventLoopGroup 提供了一些 register 办法,将 Channel 注册到以后的 EventLoop 中。

从上图能够看到,register 的返回后果是一个 ChannelFuture,Future 大家都很分明,能够用来取得异步工作的执行后果,同样的 ChannelFuture 也是一个异步的后果承载器,能够通过调用 sync 办法来阻塞 Future 直到取得执行后果。

能够看到,register 办法还能够传入一个 ChannelPromise 对象,ChannelPromise 它同时是 ChannelFuture 和 Promise 的子类,Promise 又是 Future 的子类,它是一个非凡的能够管制 Future 状态的 Future。

EventLoopGroup 有很多子类的实现,这里咱们应用 NioEventLoopGroup,Nio 应用 Selector 对 channel 进行抉择。还有一个个性是 NioEventLoopGroup 能够增加子 EventLoopGroup。

对于 NIO 服务器程序来说,咱们须要两个 Group,一个 group 叫做 bossGroup,次要用来监控连贯,一个 group 叫做 worker group,用来解决被 boss accept 的连贯,这些连贯须要被注册到 worker group 中能力进行解决。

将这两个 group 传给 ServerBootstrap,就能够从 ServerBootstrap 启动服务了,相应的代码如下:

// 建设两个 EventloopGroup 用来解决连贯和音讯
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new FirstServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // 绑定端口并开始接管连贯
            ChannelFuture f = b.bind(port).sync();

咱们最开始创立的 FirstServerHandler 最作为 childHandler 的处理器在初始化 Channel 的时候就被增加进去了。

这样,当有新建设的 channel 时,FirstServerHandler 就会被用来解决该 channel 的数据。

上例中,咱们还指定了一些 ChannelOption,用于对 channel 的一些属性进行设定。

最初,咱们绑定了对应的端口,并启动服务器。

netty 的第一个客户端

下面咱们曾经写好了服务器,并将其启动,当初还须要一个客户端和其进行交互。

如果不想写代码的话,能够间接 telnet localhost 8000 和 server 端进行交互即可,然而这里咱们心愿应用 netty 的 API 来构建一个 client 和 Server 进行交互。

构建 netty 客户端的流程和构建 netty server 端的流程基本一致。首先也须要创立一个 Handler 用来解决具体的音讯,同样,这里咱们也继承 ChannelInboundHandlerAdapter。

上一节讲到了 ChannelInboundHandlerAdapter 外面有很多办法,能够依据本人业务的须要进行重写,这里咱们心愿当 Channel active 的时候向 server 发送一个音讯。那么就须要重写 channelActive 办法,同时也心愿对异样进行一些解决,所以还须要重写 exceptionCaught 办法。如果你想在 channel 读取音讯的时候进行解决,那么能够重写 channelRead 办法。

创立的 FirstClientHandler 代码如下:

@Slf4j
public class FirstClientHandler extends ChannelInboundHandlerAdapter {

    private ByteBuf content;
    private ChannelHandlerContext ctx;

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        this.ctx = ctx;
        content = ctx.alloc().directBuffer(256).writeBytes("Hello flydean.com".getBytes(StandardCharsets.UTF_8));
        // 发送音讯
        sayHello();}

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 异样解决
        log.error("出现异常",cause);
        ctx.close();}
    
    private void sayHello() {
        // 向服务器输入音讯
        ctx.writeAndFlush(content.retain());
    }
}

下面的代码中,咱们首先从 ChannelHandlerContext 申请了一个 ByteBuff,而后调用它的 writeBytes 办法,写入要传输的数据。最初调用 ctx 的 writeAndFlush 办法,向服务器输入音讯。

接下来就是启动客户端服务了,在服务端咱们建了两个 NioEventLoopGroup,是兼顾了 channel 的抉择和 channel 中音讯的读取两局部。对于客户端来说,并不存在这个问题,这里只须要一个 NioEventLoopGroup 即可。

服务器端应用 ServerBootstrap 来启动服务,客户端应用的是 Bootstrap,其启动的业务逻辑根本和服务器启动统一:

        EventLoopGroup group = new NioEventLoopGroup();
        try {Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline p = ch.pipeline();
                     p.addLast(new FirstClientHandler());
                 }
             });

            // 连贯服务器
            ChannelFuture f = b.connect(HOST, PORT).sync();

运行服务器和客户端

有了上述的筹备工作,咱们就能够运行了。首先运行服务器,再运行客户端。

如果没有问题的话,应该会输入上面的内容:

[nioEventLoopGroup-3-1] INFO com.flydean01.FirstServerHandler - 收到音讯:Hello flydean.com

总结

一个残缺的服务器,客户端的例子就实现了。咱们总结一下 netty 的工作流程,对于服务器端,首先建设 handler 用于对音讯的理论解决,而后应用 ServerBootstrap 对 EventLoop 进行分组,并绑定端口启动。对于客户端来说,同样须要建设 handler 对音讯进行解决,而后调用 Bootstrap 对 EventLoop 进行分组,并绑定端口启动。

有了下面的探讨就能够开发属于本人的 NIO 服务了。是不是很简略?后续文章将会对 netty 的架构和背地的原理进行深刻探讨,敬请期待。

本文的例子能够参考:learn-netty4

本文已收录于 http://www.flydean.com/01-netty-startup/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

退出移动版