Java NIO 入门

JimHeimbach 3年前
   <p>很多人用过 InputStream 和 OutputStream 接口,用来操作 文件 、 Socket 等等 IO 操作。</p>    <p>如果是简单的,速度较快的 IO 操作,我们用 Stream 类的接口,依然可以风生水起。</p>    <p>如果你要使用非阻塞的 IO 的话,他们可能就满足不了你了。</p>    <p>熟悉操作系统的人会知道,操作非阻塞 IO 无非几种多路复用:</p>    <ol>     <li>select</li>     <li>poll</li>     <li>epoll</li>     <li>kqueue</li>     <li>IOCP</li>    </ol>    <p>这里的复用模型有几个是操作系统相关的——也就是说,并不是所有的操作系统都可以用,典型的就是 IOCP 是 Windows 的”专利”, kqueue 是 BSD 的”专利”(比如macOS)。</p>    <p>那么 java 作为一门跨平台的语言解决方案,是如何在虚拟机上使用 non-blocking IO 的呢?</p>    <p>具体的实现我们可以不管,它使用了 Selector 的 API,调用方式非常类似 select 。</p>    <h2>Channel & ByteBuffer vs Stream</h2>    <p>在 nio 中,不再使用 Stream API 对 Socket 进行交互,而是使用 Channel 和 ByteBuffer 进行交互,</p>    <p>Channel 负责管道的工作, ByteBuffer 负责缓存的工作。</p>    <p>原先 InputStream 和 OutputStream 的工作就由 Channel 做掉了,如果这个 Channel 支持 Select 模型的话,它就是 SelectableChannel 的子类。</p>    <p>那么,在消息循环的模型中,首先要建立循环,像我们的 Looper.loop() 一样,我们先用 Selector.open() 新建一个 Selector</p>    <pre>  <code class="language-java">Selector eventSelector = Selector.open();    // 设置这个 channel 是非阻塞的  socketChannel.configureBlocking(false);    // 注册到 selector 里,并设置好关心的事件  socketSelectionKey = socketChannel.register(eventSelector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);</code></pre>    <h2>Selector</h2>    <p>接下来调用 eventSelector.select() 阻塞,就能在你关心的事件到来的时候,阻塞就会被唤醒,处理事件。</p>    <p>sample:</p>    <pre>  <code class="language-java">while (connected) {      eventSelector.select();      Set<SelectionKey> keys = eventSelector.selectedKeys();      Iterator<SelectionKey> iterator = keys.iterator();      while (iterator.hasNext()) {          SelectionKey key = iterator.next();          if (key.isReadable()) {              // 当 socket 可读              internalOnRead((ReadableByteChannel) key.channel());          }            if (key.isWritable()) {              // 当 socket 可写              internalOnWrite((WritableByteChannel) key.channel());          }          iterator.remove();      }  }</code></pre>    <h2>总结</h2>    <p>nio 对于客户端的优势几乎没有,但是可以让代码更好管理; 如果这时候你使用的是 ServerSocket ,好处就立马体现了,因为你的业务需求很可能是这样:</p>    <ol>     <li>master 线程,开启 accept.</li>     <li>如果有客户接入,开启一个 worker,用来服务 client。</li>     <li>服务完后,保持或者关闭这个连接。</li>    </ol>    <p>(这个业务模型类似Apache httpd)这样的业务模型可能导致过多的线程开销,使得并发量并不高。</p>    <p>那么,老生常谈的 event-driven 的模型在 java 中,就差不多是这样的逻辑:</p>    <ol>     <li>master 线程,开启 selector, 并为 ServerSocket 注册 accept, read, write 等事件。</li>     <li>客户接入,为 client socket 注册 read, write 事件,依旧在该线程里面进行循环。</li>     <li>当 event trigger 的时候,处理相关业务逻辑。</li>    </ol>    <p>第二个模型只启动了一个线程,所有的 IO 操作都在 OS 里面完成了,用户空间内的资源消耗大大降低,这也是我们把 Server 端的 IO 改成 nio 的优势。</p>    <p> </p>    <p>来自:https://geminiwen.xyz/2017/02/21/learn-nio/</p>    <p> </p>