一、Netty介绍:
Netty是一个NIO客户端服务器框架,可以快速轻松地开发网络应用程序它极大地简化和优化了网络编程。
“快速和简单”并不意味着最终的应用程序会受到可维护性或性能问题的影响。Netty经过精心设计,借鉴了各种协议(如FTP、SMTP、HTTP等)的实现经验,以及各种二进制和基于文本的遗留协议。因此,Netty成功地找到了一种方法,可以在不妥协的情况下实现易于开发、高性能、高稳定性和高灵活性。官网英文版(Netty is an NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.
'Quick and easy' doesn't mean that a resulting application will suffer from a maintainability or a performance issue. Netty has been designed carefully with the experiences earned from the implementation of a lot of protocols such as FTP, SMTP, HTTP, and various binary and text-based legacy protocols. As a result, Netty has succeeded to find a way to achieve ease of development, performance, stability, and flexibility without a compromise.)
二、Netty架构:
2.1、核心组件:
Core:
Zero-Copy-Capable Rich ByteBuffer:提供了一种零拷贝的能力,可以高效地进行数据操作。
Universal Communication API:通用的通信API,它提供了一些基础的通信方法,上层应用可以使用这些方法来实现具体的通信功能。
Extensible Event Model:可扩展的事件模型,提供了一种机制,可以根据需要扩展和定制事件。
Transport Services:
Socket & Datagram:最常见的网络通信方式,用于实现TCP和UDP协议。在Netty中,你可以使用Socket和Datagram进行网络通信。
Http Tunnel:HTTP Tunnel是HTTP1.1引入的一个功能,用以解决明文的HTTP Proxy无法代理跑在TLS中的流量(即https)的问题,同时提供了作为任意流量的TCP通道的能力。
In-VM Pipe:JVM的一个进程间通讯方式,用于处理进程间通讯。
Protocol Support:
HTTP & WebSocket:
- HTTP:超文本传输协议,是互联网上应用最为广泛的一种网络协议。Netty对HTTP协议提供了全面的支持,包括HTTP/1.0、HTTP/1.1以及HTTP/2。
- WebSocket:一种网络通信协议,在单个TCP连接上进行全双工通信。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务器主动向客户端推送数据。Netty提供了WebSocket的服务器端和客户端实现,使得开发者可以轻松地构建实时应用。
SSL-StartTLS:
- SSL(安全套接字层):为网络通信提供安全性的协议,通过对传输的数据进行加密来确保数据的机密性和完整性。
- StartTLS:一种协议扩展,用于将明文通信升级为加密通信。它允许客户端和服务器在建立连接后,通过协商将通信升级为SSL/TLS加密通信。Netty支持SSL和StartTLS,为网络通信提供了安全性保障。
Google Protobuf(Protocol Buffers):是Google开发的一种数据序列化协议(类似于XML、JSON等)。它独立于语言,独立于平台。Google提供了多种语言的实现:Java、C、Go和Python。Protobuf具有高效、快速和简单的特点,适合用于高性能、高吞吐量的网络通信场景。Netty对Protobuf提供了良好的支持,使得开发者可以高效地进行数据的序列化和反序列化。
zlib/gzip Compression:zlib和gzip是常用的数据压缩算法,它们可以有效地减小数据的大小,从而提高网络通信的效率。Netty提供了对zlib和gzip压缩算法的支持,使得开发者可以在网络通信中轻松地使用这些压缩算法来减小传输数据的大小。
Large File Transfer:大文件传输是网络应用中常见的需求之一。Netty提供了对大文件传输的支持,通过分块传输、断点续传等技术来确保大文件能够可靠地进行传输。
RTSP(实时流传输协议):是一种网络流媒体协议,用于在媒体服务器和客户端之间建立和控制媒体会话。Netty对RTSP协议提供了支持,使得开发者可以构建实时的流媒体应用。
Legacy Text-Binary Protocols with Unit Testability:传统的文本和二进制协议在网络通信中仍然有一定的应用。Netty不仅支持这些传统的协议,还提供了单元测试能力,使得开发者在开发过程中能够对这些协议的实现进行有效的测试,确保代码的正确性和稳定性。
2.2、架构详解
- 如何使用Netty,通过一个简单的代码入手,上个简易聊天系统服务端核心源码:
ChatServerHandler是一个自定义的ChannelInboundHandlerAdapter的实现,用于处理客户端发送的消息,实现聊天功能,实现代码省略。
- 对案例代码详解:
- 创建EventLoopGroup:
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
创建了两个EventLoopGroup,一个用于接收客户端连接(称为bossGroup),另一个用于处理已接收的连接(称为workerGroup)。
2、设置ServerBootstrap:
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
ServerBootstrap是Netty中用于设置服务器的辅助引导程序。通过.group(bossGroup, workerGroup)`方法,我们指定了bossGroup用于接收客户端连接,而workerGroup用于处理这些连接。
3、设置通道类型和选项:
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
设置了服务器的通道类型为NioServerSocketChannel(基于NIO的服务器套接字通道),设置了服务器的接受队列长度为128(SO_BACKLOG),并开启了TCP心跳保活机制(SO_KEEPALIVE)。
4、添加处理器和日志处理器:
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
............................
});
handler用于处理异常,而childHandler用于初始化新接入的客户端请求。这里添加了一个日志处理器LoggingHandler,用于记录日志信息。同时,通过childHandler设置了客户端请求的处理链,包括字符串编码/解码器、ChatServerHandler等。
5、绑定端口并启动服务器:
ChannelFuture future = bootstrap.bind(port).sync();
System.out.println("Server started on port " + port);
future.channel().closeFuture().sync();
使用bootstrap.bind(port)方法绑定服务器到指定端口,并同步等待完成。然后输出启动信息,并等待服务器通道关闭。
6、关闭EventLoopGroups:
在finally块中,我们优雅地关闭了workerGroup和bossGroup,释放资源。这是很重要的,因为如果程序退出时EventLoopGroups还在运行,可能会导致资源泄露。
- 事件模型:
网络通讯:
引导器(Bootstrap):
- 引导器是Netty中的一个高级抽象,用于简化客户端的初始化和配置过程。它提供了一种简单的方式来启动客户端应用程序,并连接到服务器。
- 引导器的主要功能包括:设置通道类型、配置通道参数、绑定端口、启动连接等。
- 在Netty中,客户端通常使用引导器来启动连接,而服务器端使用服务器引导器(ServerBootstrap)来启动服务器。
通道(Channel):
- 通道是Netty中的一个核心抽象,表示一个可以执行I/O操作的连接或管道。在大多数情况下,通道表示一个网络套接字连接。
- 通道的主要功能包括:读取数据、写入数据、连接关闭等。
- 在Netty中,通道是异步的,这意味着它不会阻塞调用线程,而是返回一个Future或Promise对象,以便在操作完成时通知调用者。
选择器(Selector):
- 选择器是Java NIO中的一个核心组件,用于监视多个通道的状态和事件。它允许应用程序同时监听多个通道上的事件,例如可读、可写、连接等。
- 在Netty中,选择器不是直接使用的组件,而是通过Netty的ChannelFuture和ChannelPromise等机制实现的。Netty使用自己的事件循环模型来处理I/O操作和事件,而不是直接使用Java NIO的选择器机制。
事件驱动:
Reactor线程模型:
- 多路复用器(Acceptor):负责监听服务器端的端口,接收客户端的连接请求。当有新的连接请求到来时,多路复用器会将该连接注册到事件分发器中。
- 事件分发器(Dispatcher):负责将接收到的连接请求分发给相应的工作线程(Worker)进行处理。工作线程会读取客户端发送的数据,并进行协议解码、业务处理等操作。处理完成后,工作线程会将数据发送回客户端。
- 事件处理器(Handler):在Reactor线程模型中,所有的I/O操作都由一个线程完成,即多路复用、事件分发和处理都是在一个Reactor线程上完成的。这种模型可以充分利用多核CPU的并行处理能力,提高系统的并发性能和响应能力。
服务编排:
通常使用ChannelPipeline来管理网络I/O事件的处理流程。ChannelPipeline是一个处理链,它包含了多个ChannelHandler实例,用于拦截和处理网络数据。每个ChannelHandler都负责处理特定类型的I/O事件,例如读事件、写事件、连接事件等。
通过在ChannelPipeline中添加和配置不同的ChannelHandler,可以实现不同的业务逻辑。可以添加一个InboundHandler来处理接收到的数据,再添加一个OutboundHandler来发送数据到客户端。这些Handler可以按照特定的顺序排列在ChannelPipeline中,以实现数据的逐级处理和传递。
支持动态扩展和动态配置。通过动态添加或移除ChannelHandler,可以灵活地调整服务的功能和性能。
协议相关:
为何出现拆包/粘包:
出现拆包/粘包的原因主要是由于TCP协议是基于字节流的,消息之间没有明确的边界。当客户端发送的数据小于TCP发送缓冲区的大小时,TCP为了提升效率,会将多个写入缓冲区的数据包一次发送出去,导致多个数据包粘在一起,形成粘包。同样地,当服务端的应用层没有及时处理接收缓冲区中的数据时,再次读取时可能会出现粘包问题。此外,如果数据发送过快,数据包堆积导致缓冲区积压多个数据后才一次性发送出去,也可能出现拆包现象。
如何解决拆包/粘包问题:
Netty提供了多种编码器和解码器,如MessageToByteEncoder、MessageToMessageEncoder、ByteToMessageDecoder、MessageToMessageDecoder等。这些编码器和解码器能够根据不同的协议规则对数据进行编码和解码,从而保证数据的正确性和完整性。
MessageToByteEncoder将消息转换为字节流进行传输,ByteToMessageDecoder将字节流转换为消息。MessageToMessageEncoder则将消息转换为另一个消息类型,用于在协议中传递更复杂的数据类型。同样地,MessageToMessageDecoder将一个消息类型转换为另一个消息类型。
在Netty中,使用编码器和解码器可以很方便地实现自定义的协议和数据传输格式,从而实现更加灵活和高效的网络通信。同时,Netty还提供了多种处理拆包/粘包问题的策略,如ReplayingDecoder、SkipBytesDecoder等,这些策略能够根据具体情况对接收到的数据进行处理,从而保证数据的正确性和完整性。
Netty的编码器和解码器能够实现自定义的协议和数据传输格式,同时解决拆包/粘包问题,从而提高网络通信的可靠性和稳定性。
内存管理:
Zero Copy,也称为零拷贝技术,是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。
堆外内存,也称为直接内存(Direct Memory),是指由堆外内存直接分配,不会受垃圾收集器管理的内存区域。它主要用于配合堆内存使用,避免堆内存不足时内存溢出的情况。在Java中,堆内存的分配通常是由垃圾收集器自动完成的,而堆外内存的分配则需要程序员手动完成。
ByteBuffer实现堆外内存管理:
- 堆外内存分配:
- 堆外内存回收:
通过 ByteBuffer 分配的堆外内存不需要?动回收,它可以被 JVM ?动回收。 当堆内的 DirectByteBuffer 对象被 GC 回收时,Cleaner 就会?于回收对应的堆外内存
对象池化:
通过使用Stack和WeakOrderQueue,实现了对象的重用和内存池化,提高了性能和资源利用率。
Stack:
Stack是一种先进后出(FILO)的队列结构,用于存储ByteBuf对象。在Netty中,Stack用于实现ByteBuf的引用计数。每当有一个引用指向一个ByteBuf对象时,该对象就会从Stack中移除并返回给调用者。当所有引用都被释放后,该对象会被彻底销毁。
Stack的特点是操作速度快,因为它遵循先进后出的原则,可以直接定位到最近添加的对象。但是,如果Stack中存储的对象数量过多,可能会导致内存占用过高。
WeakOrderQueue:
WeakOrderQueue是一种基于链表的队列结构,用于实现ByteBuf的引用计数和内存池化。在Netty中,WeakOrderQueue用于存储可回收的ByteBuf对象。每当有一个引用指向一个ByteBuf对象时,该对象就会从WeakOrderQueue中移除并返回给调用者。当所有引用都被释放后,该对象会被移到回收池中等待被回收
使用Stack和WeakOrderQueue实现对象池化的过程如下:
创建对象池:
首先,Netty会创建一个对象池,用于存储ByteBuf对象。对象池的大小可以根据需要进行配置。
初始化队列:
Netty使用Stack和WeakOrderQueue来管理对象池中的ByteBuf对象。Stack用于存储当前正在使用的ByteBuf对象,而WeakOrderQueue用于存储可回收的ByteBuf对象。
获取对象:
当需要使用ByteBuf对象时,Netty会从Stack中获取一个可用的ByteBuf对象。如果Stack为空,则从WeakOrderQueue中获取一个可回收的ByteBuf对象,并将其移到Stack中。
释放对象:
当不再需要使用ByteBuf对象时,Netty会将该对象放回Stack或WeakOrderQueue中,以便后续再次使用。如果该对象的引用计数为0,则将其从内存中彻底删除。
内存回收:
当WeakOrderQueue中的ByteBuf对象数量过多时,Netty会自动回收一些对象,以释放内存空间。回收的对象会被移到回收池中等待被再次使用。
本文暂时没有评论,来添加一个吧(●'◡'●)