一、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!