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();}
}