一、Netty介绍
1.1 简介
在官网结尾,有这样一句话来介绍Netty
Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.
意为 Netty是一个异步、事件驱动的网络应用程序框架,用于疾速开发高性能协定的服务端和客户端。
如何了解这句话?
异步是因为Netty中简直所有操作都是异步的。
而所谓事件驱动,简略了解的话就比方咱们应用电脑,你点什么按钮(即产生什么事件),电脑执行什么操作(即调用什么函数),当然事件不仅限于用户的操作,而在网络中,事件能够是建设连贯、读写数据等等。
1.2 Netty的架构
Netty的架构图如下,摘自官网
外围局部:
- 反对零拷贝的升级版ByteBuffer(对零拷贝概念不懂的童鞋可自行查阅材料)
- 对立交互API
- 可拓展的事件模型
传输服务:
- Socket & Datagram,对应于TCP和UDP
- HTTP隧道
- JVM外部管道传输
协定反对:还有对泛滥协定的反对,包含HTTP、WebSocket,SSL、google protobuf等协定
1.3 Netty的次要个性
- 基于NIO实现,具备高吞吐、低提早个性(没有学习NIO的童鞋须要先学习一下NIO)
- 对泛滥协定的反对,开箱即用
- 各种传输类型的对立API
- 可定制的线程模型(与接下来要说的Reactor模型无关)
1.4 Netty中的组件
这里先介绍一下Netty中的组件,先眼生一下,后续再进行具体介绍
- ByteBuf:缓冲区,包含堆内存和间接内存
- ChannelPromise:异步操作相干
- EventLoop和EventLoopGroup:EventLoop意为事件循环,在单个线程中执行各种IO事件,一个EventLoopGroup对应多个EventLoop
- Channel:每个Channel都会注册到一个EventLoop上,一个EventLoop能够对应多个Channel,每个Channel蕴含了一个ChannelPipeline流水线
- ChannelPipeline和ChannelHandler:ChannelPipeline流水线是一个双向链表,由一连串的ChannelHandler组成
- Bootstrap:启动相干
在下面Netty的次要个性中,看到Netty具备可定制的线程模型的个性,那么这是何意?
其实是Netty能够很轻松的在Reactor的几种线程模型中进行切换,所以咱们须要先理解一下Reactor模式
二、Reactor模式
2.1 什么是Reactor模式
定义如下:
The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers
大抵意思:Reactor是一种专门用于处理事件的模式,用于解决一个或多个输出源的并发服务申请,Service Handler将传入的服务申请散发给关联的Request Handlers来解决。如下图
那平时所说的Reactor模式是基于IO多路复用 + 非阻塞IO来实现的,与之绝对应的是多线程 + 传统IO的模式(即每个线程解决一个连贯)
IO多路复用:通过Selector选择器来实现单个线程治理多个连贯的形式,称为IO多路复用非阻塞IO:当调用read、write等IO操作时,不阻塞以后线程,称为非阻塞IO
而Reactor模式个别实现形式有三种,别离为单Reactor单线程、单Reactor多线程及主从Reactor多线程,接下来逐个进行介绍
2.2 常见的三种Reactor模型
Reactor译为反应器,也叫散发器,它的作用其实就是监听多个连贯,并将监听到的IO事件分发给对应的处理器来解决。
2.2.1 单Reactor单线程
单Reactor单线程是指只有一个Reactor,由该Reactor来注册新连贯以及监听连贯的IO事件,并在单个线程中来解决这些IO事件,如下图
大抵流程:
- Reactor监听客户端的申请事件
- 如果是建设连贯事件,将该事件分发给acceptor解决,acceptor会将该新连贯注册到Reactor上
- 如果是读或写事件,将该事件分发给对应的handler解决
样例代码
基于Java NIO的样例代码如下,能够应用Telnet localhost 9999来测试,服务器将会打印出接管到的内容,并返回接管到的数量
/** * Description: 单Reactor单线程 * Created by kamier */public class SingleReactorSingleThread { public static void main(String[] args) throws IOException { // 启动服务器 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(9999)); serverSocketChannel.configureBlocking(false); // 获取一个Selector Selector selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); int recCount = 1; while (true) { int readyNum = selector.select(); if (readyNum <= 0) continue; Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); if (key.isAcceptable()) { ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) key.channel(); SocketChannel socketChannel = serverSocketChannel1.accept(); socketChannel.configureBlocking(false); // 监听该SocketChannel的读事件 socketChannel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(10); // 打印读取到的内容 while (socketChannel.read(byteBuffer) > 0) { byteBuffer.flip(); while (byteBuffer.hasRemaining()) { System.out.print((char) byteBuffer.get()); } byteBuffer.clear(); } // 批改对该SocketChannel感兴趣的事件集为读写事件 socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); } else if (key.isWritable()) { SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.wrap((":" + ++recCount + "; ").getBytes()); while (byteBuffer.hasRemaining()) { socketChannel.write(byteBuffer); } // 批改对该SocketChannel感兴趣的事件集为读事件 socketChannel.register(selector, SelectionKey.OP_READ); } iterator.remove(); } } }}
优缺点
长处:实现简略,不须要解决并发问题
毛病:单线程无奈利用多CPU的解决能力,性能较低
2.2.2 单Reactor多线程
单Reactor多线程也是只有一个Reactor,与单线程的次要区别在于,应用多线程来进行业务逻辑的解决,如下图:
大抵流程
- Reactor监听客户端的申请事件
- 如果是建设连贯事件,将该事件分发给acceptor解决,acceptor会将该新连贯注册到Reactor上
- 如果是读或写事件,将该事件分发给对应的handler解决,对应的handler将事件交由线程池来解决
样例代码
处理事件的业务逻辑和单线程是一样的,只是将事件交由线程池来解决
/** * Description: 单Reactor多线程 * Created by kamier on 2022/6/13 22:20 */public class SingleReactorMultiThread { public static AtomicInteger recCount = new AtomicInteger(); public static void main(String[] args) throws IOException, InterruptedException { // 创立线程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); // 启动服务器 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(9999)); serverSocketChannel.configureBlocking(false); // 获取一个Selector Selector selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { int readyNum = selector.select(); if (readyNum <= 0) continue; Set<SelectionKey> selectionKeys = selector.selectedKeys(); CountDownLatch latch = new CountDownLatch(selectionKeys.size()); for (SelectionKey key : selectionKeys) { // 提交工作给线程池 threadPoolExecutor.submit(new SelectionKeyTask(key, selector, latch)); } // 这里期待本轮事件全副解决实现 latch.await(); selectionKeys.clear(); } } private static class SelectionKeyTask implements Runnable { SelectionKey key; Selector selector; CountDownLatch latch; SelectionKeyTask (SelectionKey key, Selector selector, CountDownLatch latch) { this.key = key; this.selector = selector; this.latch = latch; } @Override public void run() { try { if (key.isAcceptable()) { ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) key.channel(); SocketChannel socketChannel = serverSocketChannel1.accept(); socketChannel.configureBlocking(false); // 监听该SocketChannel的读事件 socketChannel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(10); // 打印读取到的内容 while (socketChannel.read(byteBuffer) > 0) { byteBuffer.flip(); while (byteBuffer.hasRemaining()) { System.out.print((char) byteBuffer.get()); } byteBuffer.clear(); } // 批改对该SocketChannel感兴趣的事件集为读写事件 socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); } else if (key.isWritable()) { SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.wrap((":" + recCount.incrementAndGet() + "; ").getBytes()); while (byteBuffer.hasRemaining()) { socketChannel.write(byteBuffer); } // 批改对该SocketChannel感兴趣的事件集为读事件 socketChannel.register(selector, SelectionKey.OP_READ); } } catch (Exception e) { e.printStackTrace(); } finally { latch.countDown(); } } }}
优缺点
长处:
- 充分利用多CPU的解决能力
毛病:
- 实现绝对简单,须要解决多线程解决带来的并发问题
- 单个Reactor解决所有连贯的IO事件,在连贯数量较多的状况下,可能存在性能问题
2.2.3 主从Reactor多线程
主从Reactor多线程是指有一个MainReactor以及多个SubReactor,由MainReactor来监听建设连贯事件,而SubReactor用于监听注册到本身上的连贯的读写事件,分工更为明确。如下图:
大抵流程
- MainReactor监听客户端的申请事件
- 如果是建设连贯事件,将该事件分发给acceptor解决,acceptor会将该新连贯注册到某一个SubReactor上(SubReactor能够有多个),并由该SubReactor监听该新连贯的读写事件
- SubReactor监听注册到本身上的连贯的读写事件
- 如果是读或写事件,将该事件分发给对应的handler解决,对应的handler将事件交由线程池来解决
优缺点
长处:
- 主从Reactor分工明确
- 可拓展性强
毛病:
- 实现简单
三、总结
本文对Netty进行了一个简略介绍,并对Reactor的三种模型进行了解说,后续章节会先介绍每个Netty组件的作用以及对其要害代码的解读,最初再通过一个http服务器的样例,来看一下整个服务器的启动流程是什么样的,以及它是如何来解决http申请的?