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

Netty到底是什么?你到底懂不懂呢?

时间:2023-03-12 09:45:41 科技观察

Netty是一个易于使用的NIO客户端/服务器框架,它利用Java的高级网络功能来隐藏其背后的复杂性(JavaAPI)。它极大地简化和优化了TCP和UDP套接字服务器等网络编程,性能和安全性等诸多方面更胜一筹。支持多种协议,如FTP、SMTP、HTTP和各种二进制和基于文本的遗留协议。官方的总结是:Netty成功地找到了一种在不牺牲可维护性和性能的情况下实现易开发性、性能、稳定性和灵活性的方法。为什么使用NettyNetty作为一个优秀的网络框架,自然具有令人信服的特点:设计:同一个接口用于多种传输类型。简单但更强大的线程模型。真正的无连接数据报套接字支持。链接逻辑重用。性能:Netty的高性能是其被广泛使用的重要原因。我们可能都认为Java不适合写游戏服务器程序,但是Netty的到来无疑减少了质疑的声音。比原生JavaAPI吞吐量更高,延迟更低。更少的资源消耗(共享池和重用)。减少内存复制。健壮性:原生NIO的client/server编程比较繁琐。如果某个地方处理不好,可能会导致一些意想不到的异常,比如内存溢出,死循环等,而Netty为我们简化了。它消除了对原生API的使用,这使得我们编写的程序更不容易出错。社区:Netty发展迅速的一个重要原因是它的社区非常活跃,这也使得越来越多的开发者采用它。Netty的简单使用,左边是服务端代码,右边是客户端代码。上面的代码基本上就是模板代码,每次使用都是一样的套路。我们唯一需要开发的部分是在handler(…)和childHandler(…)方法中指定的处理程序,例如EchoServerHandler和EchoClientHandler。当然,Netty源码也给了我们很多的handler,比如上面的LoggingHandler,Netty源码里就给我们提供了,需要的时候可以直接使用。我们来看看上面代码中涉及到的一些内容:ServerBootstrap类用于创建服务端实例,Bootstrap类用于创建客户端实例。两个EventLoopGroup:bossGroup和workerGroup与Netty的线程模型有关。可以看到服务器端有两组,客户端只有一组。它们是Netty中的线程池。Netty中的Channel并没有直接使用Java原生的ServerSocketChannel和SocketChannel,而是包装了NioServerSocketChannel和NioSocketChannel与之对应。当然也有对其他协议的支持,比如支持UDP协议的NioDatagramChannel。本文只关心TCP。左边的handler(…)方法指定了一个handler(LoggingHandler),用于服务器端收到新请求时的处理。右侧的handler(...)方法指定客户端在处理请求时需要使用的处理程序。如果想在EchoServer中指定多个handler,也可以像右边的EchoClient一样,使用左边的ChannelInitializerchildHandler(…)来指定childHandler。这里的处理程序用于新创建的连接。我们知道服务器端的ServerSocketChannel在接受一个连接后,需要创建一个SocketChannel的实例。childHandler(…)中设置的处理程序用于处理新创建的SocketChannel,而不是ServerSocketChannel实例。pipeline:handler可以指定多个(需要上面ChannelInitializer类的辅助),它们会组成一个pipeline,它们其实类似于拦截器的概念,现在只要记住每个NioSocketChannel或者NioServerSocketChannel实例里面都会有一个pipeline实例。处理程序的执行顺序也参与管道。ChannelFuture:这个涉及到Netty中的异步编程,类似于JDK中的Future接口。Netty核心组件Bytebuf(字节容器)网络通信最终是通过字节流来传输的。ByteBuf是Netty提供的一个字节容器,里面是一个字节数组。我们通过Netty传输数据的时候,就是通过ByteBuf。我们可以认为ByteBuf是Netty对JavaNIO提供的ByteBuffer字节容器的封装和抽象。很多朋友可能会问:为什么不直接使用JavaNIO提供的ByteBuffer呢?因为ByteBuffer类太复杂,使用起来太麻烦。Bootstrap和ServerBootstrap(引导类)Bootstrap是客户端的引导类/辅助类。具体使用方法如下:EventLoopGroupgroup=newNioEventLoopGroup();try{//创建客户端引导/辅助类:BootstrapBootstrapb=newBootstrap();//指定线程模型b.group(group)。......//尝试建立连接ChannelFuturef=b.connect(host,port).sync();f.channel().closeFuture().sync();}finally{//优雅关闭相关线程组资源group.shutdownGracefully();}ServerBootstrap客户端的启动引导类/辅助类,具体使用方法如下://1.bossGroup用于接收连接,workerGroup用于具体处理EventLoopGroupbossGroup=newNioEventLoopGroup(1);EventLoopGroupworkerGroup=newNioEventLoopGroup();尝试{//2。创建服务器引导程序/辅助类:ServerBootstrapServerBootstrapb=newServerBootstrap();//3.为boot类配置两大线程组,确定线程模型b.group(bossGroup,workerGroup)。......//6.绑定端口ChannelFuturef=b.bind(port).sync();//等待连接关闭f.channel().closeFuture().sync();}最后{//7。优雅关闭相关线程组资源bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();从上面的例子我们可以看出,Bootstrap通常使用connet()方法连接到远程主机和端口,作为NettyTCP协议通信中的客户端。另外,Bootstrap还可以通过bind()方法绑定一个本地端口作为UDP协议通信的一端。ServerBootstrap通常使用bind()方法绑定到本地端口,然后等待客户端的连接。Bootstrap只需要配置一个线程组——EventLoopGroup,而ServerBootstrap需要配置两个线程组——EventLoopGroup,一个用于接收连接,一个用于具体的IO处理。Channel(网络操作抽象类)Channel接口是Netty对网络操作的抽象类。通过Channel我们可以进行I/O操作。一旦客户端成功连接到服务器,一个新的Channel将被创建并绑定到客户端。示例代码如下://通过Bootstrap的connect方法连接到服务器publicChanneldoConnect(InetSocketAddressinetSocketAddress){CompletableFuturecompletableFuture=newCompletableFuture<>();bootstrap.connect(inetSocketAddress).addListener((ChannelFutureListener)future->{if(future.isSuccess()){completableFuture.complete(future.channel());}else{thrownewIllegalStateException();}});返回completableFuture.get();}比较常用的Channel接口实现类有:NioServerSocketChannel(server)NioSocketChannel(client),这两个Channel可以和BIO编程模型中的ServerSocket和Socket这两个概念对应。EventLoop(事件循环)这么说吧!EventLoop(事件循环)接口可以说是Netty中的核心概念!《Netty 实战》本书是这样介绍的:EventLoop定义了Netty的核心抽象,用于处理连接生命周期中发生的事件。难懂吗?说实话,我在学习Netty的时候对这句话不是很理解。说白了,EventLoop的主要作用其实就是监听网络事件,调用事件处理器处理相关的I/O操作(读写)。Channel和EventLoop的关系。Channel和EventLoop的直接联系是什么?Channel是Netty网络操作(读写操作)的抽象类,EventLoop负责处理注册到它的Channel的I/O操作。两者协同执行I/O操作。操作哦。EventloopGroup与EventLoop的关系EventLoopGroup包含多个EventLoop(每个EventLoop内部通常包含一个线程),管理着所有EventLoop的生命周期。而且EventLoop处理的I/O事件都会在其专用的Thread上处理,即Thread和EventLoop属于1:1的关系,从而保证了线程安全。下图是NettyNIO模型对应的EventLoop模型。通过这张图,应该可以把EventloopGroup、EventLoop、Channel联系起来。ChannelHandler(消息处理器)和ChannelPipeline(ChannelHandler对象链表)下面的代码用过Netty的朋友应该很熟悉。我们指定了一个序列化编解码器和一个自定义ChannelHandler来处理消息。b.group(eventLoopGroup).handler(newChannelInitializer(){@OverrideprotectedvoidinitChannel(SocketChannelch){ch.pipeline().addLast(newNettyKryoDecoder(kryoSerializer,RpcResponse.class));ch.pipeline().addLast(newNettyKryoEncoder(kryoSerializer,RpcRequest.class));ch.pipeline().addLast(newKryoClientHandler());}});ChannelHandler是消息的具体处理器,主要负责处理client/server接收和发送的数据。创建Channel时,会自动为其分配自己的ChannelPipeline。一个Channel包含一个ChannelPipeline。ChannelPipeline是一个ChannelHandler链,一个pipeline上可以有多个ChannelHandler。我们可以通过ChannelPipeline上的addLast()方法添加一个或多个ChannelHandler(一个数据或事件可能被多个Handler处理)。当一个ChannelHandler完成处理后,将数据交给下一个ChannelHandler。当ChannelHandler添加到ChannelPipeline时,它??会获得一个ChannelHandlerContext,它表示ChannelHandler和ChannelPipeline之间的“绑定”。ChannelPipeline通过ChannelHandlerContext间接管理ChannelHandler。ChannelFuture(操作执行结果)publicinterfaceChannelFutureextendsFuture{Channelchannel();ChannelFutureaddListener(GenericFutureListener>var1);......ChannelFuturesync()throwsInterruptedException;}Netty是异步非阻塞的,所有的I/O操作都是异步的。Netty其实是不支持异步io的。真正的异步io需要底层操作系统的支持。异步是指数据准备好后,系统通知应用你可以操作数据了。netty的所谓异步,是指另一个用户线程等待数据准备好,通过回调进行处理。不是真正意义上的异步io。因此,我们无法立即知道手术是否成功。但是,您可以通过ChannelFuture接口的addListener()方法注册一个ChannelFutureListener。当操作成功或失败时,监听会自动触发并返回结果。ChannelFuturef=b.connect(host,port).addListener(future->{if(future.isSuccess()){System.out.println("连接成功!");}else{System.err.println("连接失败!”);}}).sync();此外,还可以通过ChannelFuture的channel()方法获取连接关联的Channel。通道channel=f.channel();此外,我们还可以使用ChannelFuture接口的sync()方法将异步操作编程为同步操作。//bind()是异步的,但是,您可以使用`sync()`方法使其同步。ChannelFuturef=b.bind(port).sync();本文参考:https://www.javadoop.com/post/netty-part-1本文参考:https://github.com/Snailclimb/netty-practical-tutorial