Netty的流程和范例(自主拼凑研发)
一,netty的原理
1.要了解netty,就要了解一下三个概念:nio,bio,aio
Bio:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情就会造成不必要的线程开销,当然可以通过线程池机制改善。它适用于连接数目小的框架,Bio适合流量很高的应用,如文件传输。
例:
InputStream is = new FileInputStream("input.bin");
int byte = is.read(); // 当前线程等待结果到达直至错误
再比如Tomcat是一个Web服务器,它是采取一个请求一个线程,当有1000客户端时,会耗费很多内存。通常一个线程将花费 256kb到1mb的stack空间。
Nio:同步并阻塞,NIO相当于是线程池方式的BIO,服务器实现模式为一个请求一个线程,即客户端发送的的连接请求都会注册到多路复合器(轮询器)上,轮询到I/O连接就启动一个线程处理。
nio类库是jdk1.4中引入的,。同步阻塞IO是以流的方式处理数据。NIO是基于块(Block)的,它以块为基本单位处理数据 (硬盘上存储的单位也是按Block来存储,这样性能上比基于流的方式要好一些)。
例:
while (true) {
selector.select(); // 从多个通道请求事件
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectorKey key = (SelectionKey) it.next();
handleKey(key);
it.remove();
}
}
所以NIO适用于连接多,且等待时间较短的架构,比如聊天
Aio(NIO.2):异步非阻塞,服务器实现模式为一个请求一个线程,客户端I/O请求都是OS(操作系统)先完成了再通知服务器应用启动线程进行处理,所以比较适合连接数目多且连接时间长(重复操作)的架构,比如相册服务器。
2.Bio和nio的比较图
二.netty的工作机制
流程图:
1. bossGroup线程和workerGroup线程
bossGroup线程:由这个线程池提供的线程是boss种类的,用于创建、连接、绑定socket,然后把这些socket传 给worker线程池。在服务器端每个监听的socket都 有一个boss线 程来处理。在客户端,只有一个boss线程来处理所有的socket。
workerGroup线程:Worker线 程执行所有的异步I/O。 他们不是通用的线程,开发人员需要注意不要把与其不同的任务赋给线程,这可能导致线程被阻塞、无法处理他们真正关心的任务,反过来会导致死锁和一些莫名其妙的性能问题。
ServerBootstrap:是一个对服务端做配置和启动的类
EventLoopGroup:它是继承于ScheduledExecutorService线程池接口的接口,而NioEventLoopGroup就是它的一个实现类。在服务器启动时,创建两个线程池,xi惯上一个叫bossGroup,一个叫workGroup,如果在构造函数中不指定创建的线程数量,会默认创建当前cpu个数的2倍个。
ChannelInitializer:当一个链接建立时,我们需要知道怎么来接收或者发送数据,当然,我们有各种各样的Handler实现来处理它,那么ChannelInitializer便是用来配置这些Handler,它会提供一个ChannelPipeline,并把Handler加入到ChannelPipeline。
关系:
可以这么说,ServerBootstrap监听的一个端口对应一个boss线程,它们一 一对应。比如你需要netty监听80和443端口,那么就会有两个boss线程分别负责处理来自两个端口的socket请求。在boss线程接收了socket连接请求后,会产生一个channel(一个打开的socket对应一个打开的channel),并把这个channel交给ServerBootstrap初始化时指定的ChannelInitializer来处理,boss线程则继续处理socket的请求。从worker线程池中找出一个worker线程来继续处理这个请求。
如果是Oio的话,那个这个channel上所有的socket消息,从开始到channel(socket)关闭,都只由这个特定的worker来处理,也就是说一个打开的socket对应一个指定的worker线程,这个worker线程在socket没有关闭的情况下,也只能为这个socket处理消息,无法服务其他socket。
当一个连接到达,Netty会注册一个channel,然后EventLoopGroup(worker)会分配一个EventLoop绑定到这个channel,在这个channel的整个生命周期过程中,都会由绑定的这个EventLoop来为它服务,而这个EventLoop就是一个线程。
2.Buffer(内存):
Netty的缓存分为堆内存(HeapByteBuf)和直接内存(DirectByteBuf)缓冲区。堆内存缓冲区的特点是分配和回收速度快,可以被JVM自动回收,缺点是进行Socket的IO读写,需要额外进行一次内存复制,将堆内存对应的缓冲区复制到内核中,性能会有一定程度的下降;直接内存缓冲区的特点是非堆内存,,它在堆外进行内存分配,相比于堆内存,它的分配和回收速度会慢一些,但是将它写入或者从Socket Channel中读取时,由于少了一次内存复制,速度比堆内存快。
3. Channel
Netty是一个线程服务于很多请求,当从Java NIO获得一个Selector事件,将激活通道Channel。
包括了网络的读,写,客户端发起连接,主动关闭连接,获取对方网络地址等,封装了Socket的操作,当有Socket操作发生时,会触发事件相应操作如:channelRead、channelReadComplete、exceptionCaught等方法
4.Pipeline:
可以看作是一条流水线,原始的原料(字节流)进来,经过加工,最后输出。
channel中的核心组件包括:
pipeline中保存了channel的引用,创建完pipeline之后,整个pipeline是这个样子的
pipeline中的每个节点是一个ChannelHandlerContext对象,每个context节点保存了它包裹的执行器 ChannelHandler 执行操作所需要的上下文,其实就是pipeline,因为pipeline包含了channel的引用,可以拿到所有的context信息。
pipline里添加节点:
ChannelPipeline p = ch.pipeline();
p.addLast(new ChannelInboundHandler