最近开始学习一个新的框架,为了防止自己学过之后就忘的坏习惯,所以写一些心得在这里。
1.首先Netty是个什么东西呢?
假设你正在为一个重要的大型公司开发一款全新的任务关键型的应用程序。在第一次会议
上,你得知该系统必须要能够扩展到支撑 150 000 名并发用户,并且不能有任何的性能损失,这
时所有的目光都投向了你。你会怎么说呢?
如果你可以自信地说:“当然,没问题。”那么大家都会向你脱帽致敬。但是,我们大多数人
可能会采取一个更加谨慎的立场,例如:“听上去是可行的。”然后,一回到计算机旁,我们便开
始搜索“high performance Java networking”(高性能 Java 网络编程)。
如果你现在搜索它,在第一页结果中,你将会看到下面的内容:
Netty: Home
netty.io/
Netty 是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器
和客户端。
最早期的java只是支持一些阻塞I/O,性能极差,每个线程只能处理一个客户端,如果访问客户稍多一点就会不停地创建线程,而我们都知道jvm的内存资源是极其宝贵得,如果一直创建线程就会导致jvm宕机或者内存溢出,所以早期一些大公司的处理方式都是通过c语言去处理这一块的逻辑。
后来出现了NIO,可以看做是NO I/O,也可以看做是NEW I/O。非阻塞式设计是可以一个线程对多个连接,线程池的创建可以把线程所占用的资源控制在自己手里,这样就可以避免内存溢出的异常。java中的选择器就是一个非阻塞式设计实现的关键,它可以使用较少的线程去处理较多的连接,没有I/O操作的时候也可以去处理其他业务。
尽管有一些公司直接去使用java NIO Api去构建一些工程,但是要正确和安全的使用这些Api却并不容易,所以最好还是把这些工作直接交给网络编程专家——Netty来实现。
2.Netty的一些特性
设计:
统一的API,支持多种传输类型,阻塞式和非阻塞式。
简单而强大的线程模型。
链接逻辑支持复用。
易于使用:
详细的javadoc和大量的例子
不需要超过java1.6+支持(一些特性需要java1.7+支持)
性能:
拥有比java核心API更高的吞吐量和更低的延迟
因为池化,消耗更低的资源
最少的内存复制
健壮性:
不会因为快速、慢速或者超载而引发内存溢出异常
消除在高速网络I/O中出现不公平读写的比率
社区:
版本更新快速而且频繁
3.Netty核心组件
Channel、Future、回调、ChannelHandler、事件。
Channel代表了Netty的每一个基本构件,代表了Netty对每一个硬件的操作,打开关闭连接,读写操作。
Future提供了对应用程序的异步通知,可以把它看做是一个结果的占位符。它在未来的某个时刻完成。
回调是提供给另外方法对本方法的一个引用,后者可以适当的时候调用前者。
ChannelHandler是对进站的数据的逻辑处理,用来写我们的核心逻辑。
事件是Netty用来通知我们状态的改变或者是操作的状态得,通过事件的不同我们可以执行一些自己的操作,例如写一些日志啊,数据转换啊之类的事。
在这里我多说两个Netty的组件,它们是ChannelPipeline和EventLoop。ChannelPipeline可以看做是多个ChannelHandler的有序排列,数据是按照ChannelPipeline中ChannelHandler的顺序依次进行逻辑处理,进站和出站的ChannelHandler可以放在一个ChannelPipeline中,Netty会自动判断它是进站和出站的CahnnelHandler。EventLoop是Netty分给每个Channel的一个事件循环,用来注册事件,将事件分派给ChannelHandler,安排进一步的动作。
4.Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipeline、编码器、解码器、引导程序
Channel—Socket, Netty的Cahnnel接口提供的API大大降低了使用Socket类的复杂性。 EventLoop—控制流、多线程处理、并发;
一个 EventLoopGroup 包含一个或者多个 EventLoop
一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
一个 Channel 在它的生命周期内只注册于一个 EventLoop;
一个 EventLoop 可能会被分配给一个或多个 Channel。
注意,在这种设计中,一个给定 Channel 的 I/O 操作都是由相同的 Thread 执行的,实际
上消除了对于同步的需要。
ChannelFuture—异步通知。
Netty 中所有的 I/O 操作都是异步的。因为一个操作可能不会立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此,Netty 提供了ChannelFuture 接口,其 addListener()方法注册了一个 ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知。
ChannelHandler——Netty主要组件
它充当了所有处理入站和出站数据的应用程序逻辑的容器。因为 ChannelHandler 的方法是由网络事件(其中术语“事件”的使用非常广泛)触发的。事实上,ChannelHandler 可专门用于几乎任何类型的动作,例如将数据从一种格式转换为另外一种格式,或者处理转换过程中所抛出的异常。
ChannelPipeline——ChannelHandler 链的容器
ChannelPipeline定义了用于在该链上传播入站和出站事件流的 API,当 Channel 被创建时,它会被自动地分配到它专属的 ChannelPipeline。
ChannelHandler 安装到 ChannelPipeline 中的过程如下所示:
一个ChannelInitializer的实现被注册到了ServerBootstrap中 ;
当 ChannelInitializer.initChannel()方法被调用时,ChannelInitializer将在 ChannelPipeline 中安装一组自定义的 ChannelHandler;
ChannelInitializer 将它自己从 ChannelPipeline 中移除。
如果一个消息或者任何其他的入站事件被读取,那么它会从 ChannelPipeline 的头部开始流动,并被传递给第一个 ChannelInboundHandler。这个 ChannelHandler 不一定会实际地修改数据,具体取决于它的具体功能,在这之后,数据将会被传递给链中的下一个ChannelInboundHandler。最终,数据将会到达 ChannelPipeline 的尾端,届时,所有处理就都结束了。
当ChannelHandler 被添加到ChannelPipeline 时,它将会被分配一个ChannelHandlerContext,其代表了 ChannelHandler 和 ChannelPipeline 之间的绑定。虽然这个对象可以被用于获取底层的 Channel,但是它主要还是被用于写出站数据。
在 Netty 中,有两种发送消息的方式。你可以直接写到 Channel 中,也可以 写到和 ChannelHandler相关联的ChannelHandlerContext对象中。前一种方式将会导致消息从ChannelPipeline 的尾端开始流动,而后者将导致消息从 ChannelPipeline 中的下一个 ChannelHandler 开始流动。
为什么需要适配器类?
有一些适配器类可以将编写自定义的 ChannelHandler 所需要的努力降到最低限度,因为它们提
供了定义在对应接口中的所有方法的默认实现。
下面这些是编写自定义 ChannelHandler 时经常会用到的适配器类:
ChannelHandlerAdapter
ChannelInboundHandlerAdapter
ChannelOutboundHandlerAdapter
ChannelDuplexHandler
编码器和解码器
Netty发送一次消息就会发生一次数据转码,你将会发现对于入站数据来说,channelRead 方法/事件已经被重写了。对于每个从入站Channel 读取的消息,这个方法都将会被调用。随后,它将调用由预置解码器所提供的 decode()方法,并将已解码的字节转发给 ChannelPipeline 中的下一个 ChannelInboundHandler。
引导程序(Bootstrap和ServerBootstrap)
引导程序对于客户端和服务端是不同的,对于客户端来说是Bootstrap,对于服务端来说是ServerBootstrap。
这两种的区别是:
类别 | BootStrap | ServerBootStrap |
---|---|---|
网络编程中的作用 | 链接到远程主机和端口 | 绑定到一个本地端口 |
EventLoopGroup中的数量 | 1 | 2 |
ServerBootstrap 将绑定到一个端口,因为服务器必须要监听连接,而 Bootstrap 则是由想要连接到远程节点的客户端应用程序所使用的。