一、Netty的劣势

只管咱们后面学习NIO的时候,我曾经尽可能的简化代码,然而咱们依旧会发现,JDK NIO的开发仍旧是极为简单,在业务开发中咱们还要思考到业务的解决流程、业务的复用、申请的并发量、申请过程中的编解码问题、网络传输中的半包粘包问题等等,会进一步减少NIO开发的难度!

  • Netty是基于上述难题提供了一个对立的解决方案,Netty提供了一个对于Socket编程的对立模板,即便是一个没有网络编程根底的开发人员,也可能很轻松的开发出一个高并发、低延时的程序!
  • Netty提供了很多默认的编解码性能,可能很轻松的实现一些市面上常见的协定比方FTP、HTTP、SMTP、WebSocket等,可能通过很少的几行代码很轻松的解决网络通讯过程中的半包、粘包问题!
  • 定制能力强,Netty优良的代码格调和弱小的扩大能力,能够让咱们通过ChannelHandler对通信框架进行灵便的扩大,以及通过管道流(PipLine)实现业务性能的互相隔离与复用!
  • 成熟、稳固,Netty修复了当初JDK曾经发现了的,所有的JDK NIO BUG,其中最驰名的就是臭名远扬的JDK空轮训BUG!
  • 社区沉闷,版本迭代周期短,发现的BUG能够被及时修复,同时,更多的新性能会退出;

二、Netty的架构设计

这是来自官网的一张架构图,咱们能够大抵的理解Netty的模块!

  • Core核心层,是Netty的最次要的实现,后续的所有扩大都建设在Core之上,他提供了事件的可扩大模型、网络通讯编程的通用API、数据零拷贝和数据载体的封装和复用!
  • Transport Service服务传输层,Netty提供了底层网络通讯的能力,它形象了底层网络TCP、UDP等协定的网络通信,使得用户不在为一个网络通信开发的底层技术所头疼,似的注意力可能更加专一于业务!也正是因为这一层的封装,使得NIO/BIO之间可能无缝切换!
  • Protocol Support协定层,Netty简直实现类市面上的大部分支流协定、包含HTTP、SSL、Protobuf、压缩、大文件传输、WebSocket、文本、二进制等支流协定, 而且Netty反对自定义扩大协定。Netty丰盛的协定使得用户的开发成本大大降低,应用内置的协定能够很轻松的开发一个相似于Tomcat的Http服务器!

三、Netty的根本应用和介绍

通过下面的介绍,咱们大略理解了Netty的根本架构,上面咱们会看一下Netty的根本应用,而后我会逐行解析,心愿可能通过这一节课帮忙大家入门Netty编程,也为前面的源码学习更好的铺垫一下!

1. 开发一个服务端

咱们应用Netty开发一个简略的服务端代码:

服务端接管到客户端的音讯,打印进去,而后被动中断连贯!

启动服务端源代码:

/** * 服务端 * * @author 源码学徒 * @date 2021年4月19日12:39:21 */public class EchoServer {    public static void main(String[] args) {        //设置事件循环组        EventLoopGroup boss = new NioEventLoopGroup(1);        EventLoopGroup worker = new NioEventLoopGroup();        try {            //设置服务启动器            ServerBootstrap serverBootstrap = new ServerBootstrap();            serverBootstrap.group(boss, worker)                    .channel(NioServerSocketChannel.class)                    .localAddress(8989)                    //设置服务管道                    .childHandler(new ChannelInitializer<SocketChannel>() {                        @Override                        protected void initChannel(SocketChannel socketChannel) throws Exception {                            socketChannel.pipeline().addLast(new EchoServerHandler());                        }                    });            //同步阻塞 直到绑定实现            ChannelFuture channelFuture = serverBootstrap.bind().sync();            //监听通道敞开办法 期待服务端通道敞开实现            channelFuture.channel().closeFuture().sync();        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            //优雅的敞开线程组            boss.shutdownGracefully();            worker.shutdownGracefully();        }    }}

服务端读取数据处理器源代码 : EchoServerHandler

/** * 服务端打印解决的handler * * @author huangfu * @date 2021年4月19日12:42:34 */public class EchoServerHandler extends ChannelInboundHandlerAdapter {    @Override    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {        ByteBuf byteBuf = (ByteBuf) msg;        try {            //转换为字符串            String message = byteBuf.toString(StandardCharsets.UTF_8);            System.out.println(message);        }finally {            ReferenceCountUtil.release(byteBuf);            ctx.channel().close();        }    }}

2. 服务端源代码介绍

EventLoopGroup boss = new NioEventLoopGroup(1);EventLoopGroup worker = new NioEventLoopGroup();

定义两个事件循环组,还记得咱们第五章将的咱们对NIO的优化版本吗?当初临时你能够把它了解为两个selector组

  • boss外部只蕴含一个selector选择器,用于接管新连贯!
  • worker外部默认是CPU*2个selector选择器,用于解决咱们的业务逻辑!

boss接管到新连贯后,将新连贯产生的SocketChannel交给worker线程组解决! 用一个当初比拟风行的比喻:boss相当于老板,worker相当于工人,boss不解决工作,只负责在里面跑业务拉活谈合同。接到新活之后,就把这个活交给worker来干,本人再去接下一个活!

这两行代码倡议参考上一章的NIO的优化版本学习!

ServerBootstrap serverBootstrap = new ServerBootstrap();

ServerBootstrap是Netty为咱们提供的一个疾速启动配置类,为什么Netty相较于NIO开发比较简单,就是因为Netty为咱们提供了一个网络编程的代码的模板,那么想要应用这些模板,就必须要通知模板一些必要的参数,供模板读取,而ServerBootstrap正式这个作用,通过初始化的时候,咱们设置各种参数,咱们将之保留到ServerBootstrap中,后续Netty服务启动的时候,会读取咱们初始化的时候配置的各种参数,实现本人的初始化

 serverBootstrap.group(boss, worker)

将咱们后面初始化的两个事件循环组绑定起来,保留到serverBootstrap中,供后续读取!

.channel(NioServerSocketChannel.class)

咱们这里开发的是一个服务端程序,所以咱们应用的是NioServerSocketChannel,在Netty中分为两种SocketChannel,一种是NioServerSocketChannel一种是NioSocketChannel,两者都继承与JDK的ServerSocketChannel和SocketChannel,是Netty官网对于通信管道的扩大:

  • NioServerSocketChannel: 服务端通道
  • NioSocketChannel: 客户端通道

有对于两个通道,咱们前面会详细分析,这里你们先记着,NioServerSocketChannel代表着服务端通道!NioSocketChannel代表着客户端通道,不要搞混了!

.localAddress(8989)

设置一个端口号,服务端对外裸露的端口号!

.childHandler(new ChannelInitializer<SocketChannel>() {    @Override    protected void initChannel(SocketChannel socketChannel) throws Exception {        socketChannel.pipeline().addLast(new EchoServerHandler());    }});

这个代码及其重要,前面也会重点提及,这里次要是做服务编排的,想客户端Socket绑定一个通道,并增加咱们的业务解决类 xxxxHandler,当一个客户端连贯上服务端后,后续所有的业务解决,都会由这里注册的各种Handler来解决,这就是Netty提供的可能让开发人员专一于业务开发的次要逻辑所在,前面会对这一块代码进行一个重点的解说,这里也只须要记住,咱们注册这些Handler后,客户端发来的数据都会在这些Handler中流转解决!

ChannelFuture channelFuture = serverBootstrap.bind().sync();

下面的初始化实现了,开始进行服务器启动和端口绑定,依据下面设置的端口号绑定,仔细的同学可能会发现我还调用了一个sync办法,Netty是基于异步事件来开发的,这里咱们进行bind调用之后,因为是异步执行,所以咱们并不知道什么时候实现,所以这里调用了一个阻塞的办法(sync),始终阻塞期待绑定实现后才持续往下走!

channelFuture.channel().closeFuture().sync();

获取一个服务端的通道对象,并且对服务端的通道对象增加一个敞开的监听,并调用阻塞办法(sync),调用后,程序会始终阻塞再这里,期待服务端管道敞开,才会持续往下走!一般来说,服务端管道除非咱们被动停机活因为异样解体,否则服务端管道会始终存活,那么改程序将会始终阻塞在这里,造成一个服务!

boss.shutdownGracefully();worker.shutdownGracefully();

优雅停机,该段程序会将通道标记为不可用状态,期待程序处理完毕后,开释Netty所创立的所有资源!

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {....}

当服务端察觉到客户端发来数据时,回调该逻辑!

3. 开发一个客户端

/** * 打印程序客户端 * * @author huangfu * @date 2021年4月19日13:58:16 */public class EchoClient {    public static void main(String[] args) {        EventLoopGroup worker = new NioEventLoopGroup();        try {            Bootstrap bootstrap = new Bootstrap();            bootstrap.remoteAddress("127.0.0.1", 8989)                    .group(worker)                    .channel(NioSocketChannel.class)                    .handler(new ChannelInitializer<SocketChannel>() {                        @Override                        protected void initChannel(SocketChannel ch) throws Exception {                            ch.pipeline().addLast(new EchoClientHandler());                        }                    });            ChannelFuture channelFuture = bootstrap.connect().sync();            channelFuture.channel().closeFuture().sync();        } catch (Exception e) {            e.printStackTrace();        } finally {            worker.shutdownGracefully();        }    }}

客户端解决Handler

/** * @author huangfu * @date */public class EchoClientHandler extends ChannelInboundHandlerAdapter {    @Override    public void channelActive(ChannelHandlerContext ctx) throws Exception {        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();        buffer.writeBytes("Hello Netty".getBytes(StandardCharsets.UTF_8));        ctx.channel().writeAndFlush(buffer);    }}

4. 客户端源代码介绍

客户端的代码和服务端的代码基本一致,所以这里不做全副解说,只做不一样的解说:

EventLoopGroup worker = new NioEventLoopGroup();

这里客户端只有一个事件循环组,为什么?因为客户端不存在新连贯嘛!

Bootstrap bootstrap = new Bootstrap();

这个里和服务端的ServerBootstrap基本一致,是为了保留客户端的配置所设置的一个类!

ChannelFuture channelFuture = bootstrap.connect().sync();

连贯服务端,并期待链接实现!

Handler的重载办法也产生了变动:
public void channelActive(ChannelHandlerContext ctx) throws Exception {....}

这里的含意是,当客户端被激活后既链接到服务端后,回调该逻辑!

仔细的同学在练习的时候可能会发现一点问题,咱们发现客户端会有 handler和childHandler两种办法

.handler()//设置服务管道.childHandler()
  • handler: 是绑定在 ServerSocketChannel之上的,负责服务端的逻辑解决!
  • childHandler: 是绑定在SockerChannel之上的,当客户端绑定胜利后,会产生一个SocketChannel对象会调用该handler的绑定!

总结

本节课比较简单,次要是对Netty的根本应用有一个比较简单的认知,心愿大家课下多练习,争取会简略的应用Netty!