简介
咱们罕用浏览器来拜访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/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」,懂技术,更懂你!