RPC的通信Netty,Netty的底层是Nio,Java的Io模型你理解多少?
I/O 模型简略的了解:就是用什么样的通道进行数据的发送和接管,很大水平上决定了程序通信的性能,Java 共反对 3 种网络编程模型/IO 模式:BIO、NIO、AIO。
什么是BIO?
同步并阻塞(传统阻塞型),服务器实现模式为一个连贯一个线程,即客户端有连贯申请时服务器 端就须要启动一个线程进行解决,如果这个连贯不做任何事件会造成不必要的线程开销、BIO 形式实用于连贯数目比拟小且固定的架构,这种形式对服务器资源要求比拟高,并发局限于利用中,JDK1.4以前的惟一抉择,但程序简略易了解。
同步非阻塞IO
同步非阻塞,服务器实现模式为一个线程解决多个申请(连贯),即客户端发送的连贯申请都会注 册到多路复用器上,多路复用器轮询到连贯有 I/O 申请就进行解决,NIO 形式实用于连贯数目多且连贯比拟短(轻操作)的架构,比方聊天服务器,弹幕零碎,服务器间通信等。编程比较复杂,JDK1.4 开始反对。
- Java AIO(NIO.2) : 异步非阻塞,AIO 引入异步通道的概念,采纳了 Proactor 模式,简化了程序编写,无效 的申请才启动线程,它的特点是先由操作系统实现后才告诉服务端程序启动线程去解决,个别实用于连接数较 多且连接时间较长的利用。AIO 形式应用于连贯数目多且连贯比拟长(重操作)的架构,比方相册服务器,充沛调用 OS 参加并发操作,编程比较复杂,JDK7 开始反对。
Java BIO 问题剖析
- 每个申请都须要创立独立的线程,与对应的客户端进行数据 Read,业务解决,数据 Write 。
- 当并发数较大时,须要创立大量线程来解决连贯,系统资源占用较大。
- 连贯建设后,如果以后线程临时没有数据可读,则线程就阻塞在 Read 操作上,造成线程资源节约
nio介绍
- Java NIO 全称 java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改良的输出/输入的新个性,被统称为 NIO(即 New IO),是同步非阻塞的
- NIO 相干类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写。【根本案例】
- NIO 有三大外围局部:Channel(通道),**Buffer(缓冲区), Selector(**选择器)
Selector 、 Channel 和 Buffer 的关系图
Selector 、 Channel 和 Buffer 的关系图
- 每个channel都会对应一个Buffer
- Selector 对应一个线程, 一个线程对应多个 channel(连贯)
- 该图反馈了有三个channel注册到该selector//程序
- 程序切换到哪个channel是有事件决定的,Event就是一个重要的概念
- Selector 会依据不同的事件,在各个通道上切换
- Buffer 就是一个内存块 , 底层是有一个数组
- 数据的读取写入是通过Buffer,这个和BIO,BIO中要么是输出流,或者是输入流, 不能双向,然而 NIO 的 Buffer 是能够读也能够写, 须要 flip 办法切换 channel 是双向的, 能够返回底层操作系统的状况, 比方 Linux , 底层的操作系统通道就是双向的。
缓冲区(Buffer)
缓冲区(Buffer):缓冲区实质上是一个能够读写数据的内存块,能够了解成是一个容器对象**(含数组)**,该对象提供了一组办法,能够更轻松地应用内存块,,缓冲区对象内置了一些机制,可能跟踪和记录缓冲区的状态变动状况。Channel 提供从文件、网络读取数据的渠道,然而读取或写入的数据都必须经由 Buffer。
- mark: 标记值,应用mark()函数能够标记以后position,在应用reset()后会将position重置为mark值,默认为-1,即没有mark值
- position: 以后地位
- limit: 限度值
- capacity: buffer总容量
缓冲区(Buffer)
channel
NIO 的通道相似于流,但有些区别如下:
- 通道能够同时进行读写,而流只能读或者只能写
- 通道能够实现异步读写数据
- 通道能够从缓冲读数据,也能够写数据到缓冲:
- BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel) 是双向的,能够读操作,也能够写操作。
- Channel 在 NIO 中是一个接口
public interface Channel extends Closeable{}
- 常 用 的 Channel 类 有 : FileChannel 、 DatagramChannel 、 ServerSocketChannel 和 SocketChannel 。【ServerSocketChanne 相似 ServerSocket , SocketChannel 相似 Socket】
Selector 示意图和特点阐明
- Java 的 NIO,用非阻塞的 IO 形式。能够用一个线程,解决多个的客户端连贯,就会应用到 Selector(选择器)
- Selector 可能检测多个注册的通道上是否有事件产生(留神:多个 Channel 以事件的形式能够注册到同一个 Selector),如果有事件产生,便获取事件而后针对每个事件进行相应的解决。这样就能够只用一个单线程去管 理多个通道,也就是治理多个连贯和申请。
- 只有在连贯/通道真正有读写事件产生时,才会进行读写,就大大地缩小了零碎开销,并且不用为每个连贯都 创立一个线程,不必去保护多个线程
- 防止了多线程之间的上下文切换导致的开销
Selector 示意图和特点阐明
Netty 的 IO 线程 NioEventLoop 聚合了 Selector(选择器,也叫多路复用器),能够同时并发解决成千盈百个客
户端连贯。
- 当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程能够进行其余工作。
线程通常将非阻塞 IO 的闲暇工夫用于在其余通道上执行 IO 操作,所以独自的线程能够治理多个输出和输入
通道。
因为读写操作都是非阻塞的,这就能够充沛晋升 IO 线程的运行效率,防止因为频繁 I/O 阻塞导致的线程挂
起。
- 一个 I/O 线程能够并发解决 N 个客户端连贯和读写操作,这从根本上解决了传统同步阻塞 I/O 一连贯一线程模型,架构的性能、弹性伸缩能力和可靠性都失去了极大的晋升。
NIO 非阻塞 网络编程相干的(Selector、SelectionKey、ServerScoketChannel 和 SocketChannel) 关系梳理图
- 当客户端连贯时,会通过 ServerSocketChannel 失去 SocketChannel
- Selector 进行监听 select 办法, 返回有事件产生的通道的个数
- 将socketChannel注册到Selector上,register(Selectorsel,intops),一个selector上能够注册多个SocketChannel
- 注册后返回一个 SelectionKey, 会和该 Selector 关联(汇合)
- 进一步失去各个 SelectionKey (有事件产生)
- 在通过 SelectionKey 反向获取 SocketChannel , 办法 channel() 7) 能够通过 失去的 channel , 实现业务解决
简略的NiO网络编程代码
public class NIOServer { public static void main(String[] args) throws Exception{ //创立ServerSocketChannel -> ServerSocket ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //失去一个Selecor对象 Selector selector = Selector.open(); //绑定一个端口6666, 在服务器端监听 serverSocketChannel.socket().bind(new InetSocketAddress(6666)); //设置为非阻塞 serverSocketChannel.configureBlocking(false); //把 serverSocketChannel 注册到 selector 关怀 事件为 OP_ACCEPT serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("注册后的selectionkey 数量=" + selector.keys().size()); // 1 //循环期待客户端连贯 while (true) { //这里咱们期待1秒,如果没有事件产生, 返回 if(selector.select(1000) == 0) { //没有事件产生 System.out.println("服务器期待了1秒,无连贯"); continue; } //如果返回的>0, 就获取到相干的 selectionKey汇合 //1.如果返回的>0, 示意曾经获取到关注的事件 //2. selector.selectedKeys() 返回关注事件的汇合 // 通过 selectionKeys 反向获取通道 Set<SelectionKey> selectionKeys = selector.selectedKeys(); System.out.println("selectionKeys 数量 = " + selectionKeys.size()); //遍历 Set<SelectionKey>, 应用迭代器遍历 Iterator<SelectionKey> keyIterator = selectionKeys.iterator(); while (keyIterator.hasNext()) { //获取到SelectionKey SelectionKey key = keyIterator.next(); //依据key 对应的通道产生的事件做相应解决 if(key.isAcceptable()) { //如果是 OP_ACCEPT, 有新的客户端连贯 //该该客户端生成一个 SocketChannel SocketChannel socketChannel = serverSocketChannel.accept(); System.out.println("客户端连贯胜利 生成了一个 socketChannel " + socketChannel.hashCode()); //将 SocketChannel 设置为非阻塞 socketChannel.configureBlocking(false); //将socketChannel 注册到selector, 关注事件为 OP_READ, 同时给socketChannel //关联一个Buffer socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024)); System.out.println("客户端连贯后 ,注册的selectionkey 数量=" + selector.keys().size()); //2,3,4.. } if(key.isReadable()) { //产生 OP_READ //通过key 反向获取到对应channel SocketChannel channel = (SocketChannel)key.channel(); //获取到该channel关联的buffer ByteBuffer buffer = (ByteBuffer)key.attachment(); channel.read(buffer); System.out.println("form 客户端 " + new String(buffer.array())); } //手动从汇合中挪动以后的selectionKey, 避免反复操作 keyIterator.remove(); } } }}
客户端
public class NIOClient { public static void main(String[] args) throws Exception{ //失去一个网络通道 SocketChannel socketChannel = SocketChannel.open(); //设置非阻塞 socketChannel.configureBlocking(false); //提供服务器端的ip 和 端口 InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666); //连贯服务器 if (!socketChannel.connect(inetSocketAddress)) { while (!socketChannel.finishConnect()) { System.out.println("因为连贯须要工夫,客户端不会阻塞,能够做其它工作.."); } } //...如果连贯胜利,就发送数据 String str = "hello, 尚硅谷~"; //Wraps a byte array into a buffer ByteBuffer buffer = ByteBuffer.wrap(str.getBytes()); //发送数据,将 buffer 数据写入 channel socketChannel.write(buffer); System.in.read(); }}