基本概念
Java NIO(Native IO) 是JDK1.4 開始提供的新的API。為所有原始類型提供Buffer緩存,字符集編碼等解決方案。它通過一個本地的DirectBuffer來直接為APP分配內存,避免JVM參與其中。通過這樣的方式來提高效率
NIO模型
NIO是一個同步非阻塞的線程模型,同步是指線程不斷輪詢IO事件是否就緒,非阻塞是指線程在等待IO就緒之前,可以做其他的任務。同步核心為Selector
,Selector 代替了線程對IO事件的輪詢,避免了阻塞同時減少了線程的消耗。非阻塞的核心是通道(Channel)和緩沖區(Buffer),當IO事件就緒時,就可以將數據寫進緩沖區,再由緩沖區交給線程。從而保證
IO的成功
NIO的三個核心組件
- Channels : 通道是I/O傳輸過程中需要通過的入口,緩沖區是這些數據的傳輸的來源或者目的地
- 在NIO中如果想要將Data傳輸到目的地的Buffer中,則首先需要傳輸到目的地的Channel中。
- 然后目的地Buffer再從屬于自己的Channel中取出數據放置到Buffer中
- Buffers:
- 緩沖區,可以理解為DirectBuffer區域向線程輸出數據的緩沖地帶。
- 當IO可用時而且數據到達時,可以預先寫入緩沖區。通過這樣的方式,線程就不需要特意的等待IO
- Selectors: 顧名思義就是用來調度各個IO事件,Selector允許單線程處理多個Channel
- 首先向Selector注冊Channel
- 調用其select方法。這個方法會阻塞到某個注冊的通道有時間就緒(輪詢)
- 堅挺Selector感興趣的四個事件
OP_ACCEPT
,OP_CONNECT
,OP_READ
,OP_WRITE
雖然Java NIO 中除此之外還有很多類和組件,但在我看來,Channel,Buffer 和 Selector 構成了核心的API。其它組件,如Pipe和FileLock,只不過是與三個核心組件共同使用的工具類。
基本使用
基于NIO的一個Server
Server 端
public static void main(String[] args){
try {
//開啟一個Selector
Selector selector = Selector.open();
//Server端的Channel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress("localhost",8083);
//監聽 localhost:8083 端口
serverSocketChannel.bind(inetSocketAddress);
//設置為非阻塞的模式
serverSocketChannel.configureBlocking(false);
//serverSocket 只能接收 新的連接,所以這個只會返回 {@link SelectionKey#OP_ACCEPT}.
int ops = serverSocketChannel.validOps();
//將Channel注冊到selector 上,返回的SelectionKey 標識著這個Channel的狀態,感興趣的Event,可用的event等
SelectionKey selectionKey = serverSocketChannel.register(selector,ops);
while (true){
System.out.println("Waiting for the Connections come");
//將 準備進行IO操作的Channel集合返回。這是個阻塞的方法,當至少有一個Channel ready的時候才會返回!
selector.select();
//
Set<SelectionKey> selectionKeys = selector.selectedKeys();
selectionKeys.stream().forEach(key ->{
try {
//如果IO的Connector是可用的,那么
if(key.isAcceptable()){
//接收一個來自客戶端的請求,
SocketChannel socketClientChannel = serverSocketChannel.accept();
//因為這個已經設置了非阻塞式的,所以這里的連接可能會是null
if (socketClientChannel != null){
socketClientChannel.configureBlocking(false);
//將selector的OP_READ 注冊為clientSocket
socketClientChannel.register(selector, SelectionKey.OP_READ);
}
}else if(key.isReadable()){
//因為我們前面將ClientChannel 注冊到OP_READ上面,所有這里的SelectorKey的channel一定是Client
SocketChannel crunchifyClient = (SocketChannel) key.channel();
if (crunchifyClient == null){
log("no connections valid sleep 100ms ");
Thread.sleep(100);
}
ByteBuffer crunchifyBuffer = ByteBuffer.allocate(1024);
//將Channel將Channel 中的數據寫入到Buffer中。。
crunchifyClient.read(crunchifyBuffer);
String result = new String(crunchifyBuffer.array()).trim();
log("Message received: " + result);
crunchifyClient.close();
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void log(String s) {
System.out.println(s);
}
}
Cilent 端
public class NIOClient implements Runnable {
public NIOClient(String connectID) {
this.connectID = connectID;
}
private String connectID;
public static void main(String[] args) {
for (int count = 0; count < 50;count++){
new Thread(new NIOClient(""+count)).start();
}
}
public static void log(String msg) {
System.out.println(msg);
}
@Override
public void run() {
try {
Selector selector = Selector.open();
InetSocketAddress socketAddress = new InetSocketAddress("localhost", 8083);
//
// Thread.sleep(1000); //每10ms發出一個
SocketChannel socketChannel = SocketChannel.open(socketAddress);
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_CONNECT);
for (int index = 0; index < 100; index++) {
byte[] bytes = new String("connectID" +connectID + "this is the " + index + "data").getBytes();
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
socketChannel.write(byteBuffer);
byteBuffer.clear();
Thread.sleep(2000);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}