乐趣区

关于nio:RPC的通信NettyNetty的底层是NioJava的Io模型你了解多少

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();}
}
退出移动版