当前位置: 首页 > 科技观察

从一个Demo开始,揭开Netty的奥秘

时间:2023-03-13 22:15:06 科技观察

本文转载自微信公众号《阿丸手记》,作者阿万手记。转载本文请联系阿晚注公众号。在上一篇文章中,我们全面了解了I/O多路复用、JavaNIO包和Netty的关系。至此,我们从I/O模型开始,逐步接触到Netty框架。在这个过程中,基本回答了Netty是什么,为什么要用Netty等前置问题。为我们提供了学习Netty最原始的背景知识。有了这些基础,我们就可以开始慢慢揭开Netty的神秘面纱了。本文预计阅读时间5分钟左右,将关注以下问题:如何使用Netty编写服务端demo从demo看Netty的逻辑架构,熟悉各个组件1.写一个server端demo1.1基于master-slave如果你没有用过Netty在Reactor模式下的demo实现,了解Netty写的Server-sideDemo是很有必要的。还记得上一篇我们提到的“主从Reactor模式”吗?可以搭建两个Reactor,masterReactor单独监听serversocket,接受新的连接,然后将建立的SocketChannel注册到指定的slaveReactor,然后从Reactor执行事件的读写、分发、抛出业务处理交给工作线程池来完成。让我们按照这个模式,用Netty写一个服务器程序。直接上代码!自定义业务处理逻辑的简单自定义ChannelHandler类:包含Bootstrap的服务器端启动类:publicclassEchoServer{privateintport;publicEchoServer(intport){this.port=port;}publicstaticvoidmain(String[]args)throwsException{newEchoServer(8833).start();}publicvoidstart()throwsException{//1。Reactor模型的主从多线程EventLoopGroupmainGroup=newNioEventLoopGroup();EventLoopGroupchildGroup=newNioEventLoopGroup();try{//2.构建指南ServerBootstrapServerBootstrapb=newServerBootstrap();b.group(mainGroup,childGroup).channel(NioServerSocketChannel.class)//2.1设置NIO通道.localAddress(newInetSocketAddress(port))//2.2配置本地监听端口.childHandler(newChannelInitializer(){//2.3初始化通道时,配置Handler@OverrideprotectedvoidinitChannel(finalSocketChannelsocketChannel){socketChannel.pipeline().addLast("codec",newHttpServerCodec()).addLast("compressor",newHttpContentCompressor()).addLast("compressor",newHttpContentCompressor()).addLast("聚合器",newHttpObjectAggregator(65536)).addLast("处理程序",newEchoServerHandler());//2.4添加自定义业务逻辑ChannelHandler}});ChannelFuturef=b.bind().sync();//3.开始监听System.out.println("HttpServerstarted,Listeningon"+port);f.channel().closeFuture().sync();}finally{mainGroup.shutdownGracefully().sync();childGroup.shutdownGracefully()。sync();}}}启动后通过curl调用得到响应Demo完成!对于之前认为用JavaNIO封装实现起来非常复杂的“主从Reactor模式”,用Netty简单的完成了。只需创建两个EventLoopGroups并将它们绑定到引导程序ServerBootstrap。mainGroup为主Reactor,childGroup为从Reactor。他们分别使用不同的NioEventLoopGroup。主Reactor负责处理Accept,然后向从Reactor注册Channel。从Reactor主要负责Channel生命周期中的所有I/O事件。1.2Demo分析从上面的demo代码可以看出,所有用Netty编写的服务端程序,至少需要两部分:至少有一个ChannelHandlerBootstrapping1)ChannelHandler组件用于处理客户端发送的数据,可能包括codec,自定义业务逻辑处理等。对于ChannelHandler,有很多种实现。在演示中,我们简单地使用了几个Netty自带的Handler,包括HttpServerCodec、HttpContentCompressor、HttpObjectAggregator和一个自定义的EchoServerHandler。可见Handler的使用是一个非常重要和方便的环节。我们将在以后的文章中详细展开。2)Bootstrapping启动代码部分。用于配置服务器的启动参数,包括监听端口、服务器线程池配置、网络连接属性配置、ChannelHandler配置等。从Demo来看,主要分为这几个步骤:创建一个ServerBootstrap实例来引导程序。创建一个(当我们使用主从Reactor模式时,需要创建两个)NioEventLoopGroup实例来处理事件,比如接受新的客户端连接,读写数据等。指定一个端口作为监听端口服务器的。使用一系列的channelHandlers来初始化每个Channel,包括自定义业务逻辑实现的channelHandlers。调用ServerBootstrap.bind()实际触发引导程序。2、Netty的逻辑架构通过上面的demo演示,我们对Netty的使用有了一个大概的印象。下面我们根据Demo中用到的几个组件来梳理一下Netty的逻辑架构。结合我们的demo和这张逻辑架构图,梳理一下各个组件的流程:服务端使用ServerBootstrap启动引导,在绑定监听端口启动和初始化的时候有mainEventLoopGroup和childEventLoopGroup两个组件,而主要的EventLoopGroup负责监听网络连接事件。当有新的网络连接时,Channel注册到子EventLoopGroup。childEventLoopGroup会被分配一个EventLoop,负责处理Channel的读写事件。当客户端发起I/O读写事件时,服务器端的EventLoop会读取数据,然后通过ChannelPipeline依次触发各个ChannelHandlers对数据进行处理。客户端数据会依次传递给ChannelPipeline的ChannelInboundHandler,在一个handler中处理后传递给下一个handler。当数据回写到客户端时,处理结果会依次传递给ChannelPipeline的ChannelOutboundHandler。在一个handler中处理后,会传递给下一个handler,最后返回给client。以上就是Netty各个组件的逻辑架构。我们暂时只需要了解一个大概的框架即可。稍后我们将详细介绍每个组件。下面是几个比较常见的问题:1)什么是Channel?Channel字面意思是“通道”。它是网络通信的载体,提供网络I/O操作的基本API,如register、bind、connect、read、write、flush等。Netty实现的Channel基于JDKNIOChannel,提供了一个更高层次的抽象并屏蔽了底层的Socket。2)什么是ChannelHandler和ChannelPipelineChannelHandler实现了对客户端发送的数据的处理,可能包括codec,自定义业务逻辑处理等。ChannelPipeline负责组装各种ChannelHandler。当一个I/O读写事件被触发时,ChannelPipeline会依次调用ChannelHandlers列表来拦截和处理Channel的数据。3)什么是EventLoopGroup?EventLoopGroup本质上是一个线程池,是NettyReactor线程模型的具体实现。主要负责接收I/O请求,分配线程执行处理请求。我们在demo中使用了它的实现类NioEventLoopGroup,这也是Netty中最推荐的线程模型。我们还通过构建主EventLoopGroup和子EventLoopGroup来实现“主从Reactor模式”。4)EventLoopGroup、EventLoop、Channel是什么关系?一个EventLoopGroup通常包含一个或多个EventLoop。EventLoop用于处理Channel生命周期中的所有I/O事件,如accept、connect、read、write等I/O事件。EventLoop同时绑定到一个线程,每个EventLoop负责处理多个Channel。参考书目:《Netty in Action》