简介

咱们罕用浏览器来拜访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代码如下:

@Slf4jpublic 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/

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

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