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();
}
}
发表回复