標籤:ble 事件 cto oid 不同的 那些事 引入 har ini
前面一篇中已經介紹了基本IO的使用以及最簡單的阻塞伺服器的例子,本篇就來介紹下NIO的相關內容,前面的分享可以參考目錄:
- 網路IO的基本知識與概念
- 普通IO以及BIO伺服器
- NIO的使用與伺服器Hello world
- Netty入門與伺服器Hello world
- Netty深入淺出
NIO,也叫做new-IO或者non-blocking-IO,就暫且理解為非阻塞IO吧。
為什麼選擇NIO
那麼NIO相對於IO來說,有什麼優勢呢?總結來說:
- IO是面向流的,資料只能從一端讀取到另一端,不能隨意讀寫。NIO則是面向緩衝區的,進行資料的操作更方便了
- IO是阻塞的,既浪費伺服器的效能,也增加了伺服器的風險;而NIO是非阻塞的。
- NIO引入了IO多工器,效率上更高效了。
NIO都有什麼
那麼NIO都提供了什麼呢?
- 基於緩衝區的雙向管道,Channel和Buffer
- IO多工器Selector
- 更為易用的API
Buffer的使用
在NIO中提供了各種不同的Buffer,最常用的就是ByteBuffer:
可以看到,他們都有幾個比較重要的變數:
- capacity——容量,這個值是一開始申請就確定好的。類似c語言申請數組的大小。
- limit——剩餘,在寫入模式下初始的時候等於capacity;在讀模式下,等於最後一次寫入的位置
- mark——標記位,標記一下position的位置,可以調用reset()方法回到這個位置。
- posistion——位置,寫入模式下表示開始寫入的位置;讀模式下表示開始讀的位置
總結來說,NIO的Buffer有兩種模式,讀模式和寫入模式。剛上來就是寫入模式,使用flip()可以切換到讀模式。
關於這幾個位置的使用,可以參考下面的代碼:
public class ByteBufferTest { public static void main(String[] args) { ByteBuffer buffer = ByteBuffer.allocate(88); System.out.println(buffer); String value = "Netty權威指南"; buffer.put(value.getBytes()); System.out.println(buffer); buffer.flip(); System.out.println(buffer); byte[] v = new byte[buffer.remaining()]; buffer.get(v); System.out.println(buffer); System.out.println(new String(v)); }}
得到的輸出為:
java.nio.HeapByteBuffer[pos=0 lim=88 cap=88]java.nio.HeapByteBuffer[pos=17 lim=88 cap=88]java.nio.HeapByteBuffer[pos=0 lim=17 cap=88]java.nio.HeapByteBuffer[pos=17 lim=17 cap=88]Netty權威指南
讀者可以自己領會一下,這幾個變數的含義。另外說明一點,如果遇到自己定義POJO類,就可以像這裡的Buffer重載toString()方法,這樣輸出的時候就很方便了。
最後關於ByteBuffer在Channel中的使用,可以參考下面的代碼:
public class BufferTest { public static void main(String[] args) throws IOException { String file = "xxxx/test.txt"; RandomAccessFile accessFile = new RandomAccessFile(file,"rw"); FileChannel fileChannel = accessFile.getChannel(); // 20個位元組 ByteBuffer buffer = ByteBuffer.allocate(20); int bytesRead = fileChannel.read(buffer); // buffer.put()也能寫入buffer while(bytesRead!=-1){ // 寫切換到讀 buffer.flip(); while(buffer.hasRemaining()){ System.out.println((char)buffer.get()); } // buffer.rewind()重新讀 // buffer.mark()標記position buffer.reset()恢複 // 清除緩衝區 buffer.clear(); // buffer.compact(); 清楚讀過的資料 bytesRead = fileChannel.read(buffer); } }}
這樣,就熟悉了Channel和ByteBuffer的使用。接下來,看看伺服器中的應用吧。
NIO伺服器例子
前面BIO的伺服器,是來一個串連就建立一個新的線程響應。這裡基於NIO的多工,可以這樣寫:
import java.io.IOException;import java.net.InetSocketAddress;import java.net.ServerSocket;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Iterator;import java.util.Set;public class PlainNioServer { public void serve(int port) throws IOException { // 建立channel,並綁定監聽連接埠 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); ServerSocket ssocket = serverSocketChannel.socket(); InetSocketAddress address = new InetSocketAddress(port); ssocket.bind(address); //建立selector,並將channel註冊到selector Selector selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); final ByteBuffer msg = ByteBuffer.wrap("Hi\r\b".getBytes()); for(;;){ try{ selector.select(); }catch (IOException e){ e.printStackTrace(); break; } Set<SelectionKey> readyKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = readyKeys.iterator(); while(iterator.hasNext()){ SelectionKey key = iterator.next(); iterator.remove(); try{ if(key.isAcceptable()){ ServerSocketChannel server = (ServerSocketChannel)key.channel(); SocketChannel client= server.accept(); client.configureBlocking(false); client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, msg.duplicate()); System.out.println("accepted connection from "+client); } if(key.isWritable()){ SocketChannel client = (SocketChannel) key.channel(); ByteBuffer buffer = (ByteBuffer) key.attachment(); while(buffer.hasRemaining()){ if(client.write(buffer)==0){ break; } } client.close(); } }catch (IOException e){ key.cancel(); try{ key.channel().close(); } catch (IOException ex){ ex.printStackTrace(); } } } } } public static void main(String[] args) throws IOException { PlainNioServer server = new PlainNioServer(); server.serve(5555); }}
這裡抽象來說是下面的步驟:
- 建立ServerSocketChannel並綁定連接埠
- 建立Selector多工器,並註冊Channel
- 迴圈監聽是否有感興趣的事件發生selector.select();
- 獲得事件的控制代碼,並進行處理
其中Selector可以一次監聽多個IO處理,效率就提高很多了。
漫談Java IO之 NIO那些事兒