RPC的通信Netty,Netty的底层是Nio,Java的Io模型你理解多少?


I/O 模型简略的了解:就是用什么样的通道进行数据的发送和接管,很大水平上决定了程序通信的性能,Java 共反对 3 种网络编程模型/IO 模式:BIO、NIO、AIO。

什么是BIO?

同步并阻塞(传统阻塞型),服务器实现模式为一个连贯一个线程,即客户端有连贯申请时服务器 端就须要启动一个线程进行解决,如果这个连贯不做任何事件会造成不必要的线程开销、BIO 形式实用于连贯数目比拟小且固定的架构,这种形式对服务器资源要求比拟高,并发局限于利用中,JDK1.4以前的惟一抉择,但程序简略易了解。

同步非阻塞IO

同步非阻塞,服务器实现模式为一个线程解决多个申请(连贯),即客户端发送的连贯申请都会注 册到多路复用器上,多路复用器轮询到连贯有 I/O 申请就进行解决,NIO 形式实用于连贯数目多且连贯比拟短(轻操作)的架构,比方聊天服务器,弹幕零碎,服务器间通信等。编程比较复杂,JDK1.4 开始反对。

  1. Java AIO(NIO.2) : 异步非阻塞,AIO 引入异步通道的概念,采纳了 Proactor 模式,简化了程序编写,无效 的申请才启动线程,它的特点是先由操作系统实现后才告诉服务端程序启动线程去解决,个别实用于连接数较 多且连接时间较长的利用。AIO 形式应用于连贯数目多且连贯比拟长(重操作)的架构,比方相册服务器,充沛调用 OS 参加并发操作,编程比较复杂,JDK7 开始反对。

Java BIO 问题剖析

  1. 每个申请都须要创立独立的线程,与对应的客户端进行数据 Read,业务解决,数据 Write 。
  2. 当并发数较大时,须要创立大量线程来解决连贯,系统资源占用较大。
  3. 连贯建设后,如果以后线程临时没有数据可读,则线程就阻塞在 Read 操作上,造成线程资源节约

nio介绍

  1. Java NIO 全称 java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改良的输出/输入的新个性,被统称为 NIO(即 New IO),是同步非阻塞的
  2. NIO 相干类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写。【根本案例】
  3. NIO 有三大外围局部:Channel(通道),**Buffer(缓冲区), Selector(**选择器)
Selector 、 Channel 和 Buffer 的关系图

Selector 、 Channel 和 Buffer 的关系图

  1. 每个channel都会对应一个Buffer
  2. Selector 对应一个线程, 一个线程对应多个 channel(连贯)
  3. 该图反馈了有三个channel注册到该selector//程序
  4. 程序切换到哪个channel是有事件决定的,Event就是一个重要的概念
  5. Selector 会依据不同的事件,在各个通道上切换
  6. Buffer 就是一个内存块 , 底层是有一个数组
  7. 数据的读取写入是通过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 的通道相似于流,但有些区别如下:

  • 通道能够同时进行读写,而流只能读或者只能写
  • 通道能够实现异步读写数据
  • 通道能够从缓冲读数据,也能够写数据到缓冲:
  1. BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel) 是双向的,能够读操作,也能够写操作。
  2. Channel 在 NIO 中是一个接口 public interface Channel extends Closeable{}
  3. 常 用 的 Channel 类 有 : FileChannel 、 DatagramChannel 、 ServerSocketChannelSocketChannel 。【ServerSocketChanne 相似 ServerSocket , SocketChannel 相似 Socket】

Selector 示意图和特点阐明

  1. Java 的 NIO,用非阻塞的 IO 形式。能够用一个线程,解决多个的客户端连贯,就会应用到 Selector(选择器)
  2. Selector 可能检测多个注册的通道上是否有事件产生(留神:多个 Channel 以事件的形式能够注册到同一个 Selector),如果有事件产生,便获取事件而后针对每个事件进行相应的解决。这样就能够只用一个单线程去管 理多个通道,也就是治理多个连贯和申请。
  3. 只有在连贯/通道真正有读写事件产生时,才会进行读写,就大大地缩小了零碎开销,并且不用为每个连贯都 创立一个线程,不必去保护多个线程
  4. 防止了多线程之间的上下文切换导致的开销

Selector 示意图和特点阐明

  1. Netty 的 IO 线程 NioEventLoop 聚合了 Selector(选择器,也叫多路复用器),能够同时并发解决成千盈百个客

    户端连贯。

  2. 当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程能够进行其余工作。
  3. 线程通常将非阻塞 IO 的闲暇工夫用于在其余通道上执行 IO 操作,所以独自的线程能够治理多个输出和输入

    通道。

  4. 因为读写操作都是非阻塞的,这就能够充沛晋升 IO 线程的运行效率,防止因为频繁 I/O 阻塞导致的线程挂

    起。

  5. 一个 I/O 线程能够并发解决 N 个客户端连贯和读写操作,这从根本上解决了传统同步阻塞 I/O 一连贯一线程模型,架构的性能、弹性伸缩能力和可靠性都失去了极大的晋升。

NIO 非阻塞 网络编程相干的(Selector、SelectionKey、ServerScoketChannel 和 SocketChannel) 关系梳理图

  1. 当客户端连贯时,会通过 ServerSocketChannel 失去 SocketChannel
  2. Selector 进行监听 select 办法, 返回有事件产生的通道的个数
  3. 将socketChannel注册到Selector上,register(Selectorsel,intops),一个selector上能够注册多个SocketChannel
  4. 注册后返回一个 SelectionKey, 会和该 Selector 关联(汇合)
  5. 进一步失去各个 SelectionKey (有事件产生)
  6. 在通过 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();    }}