Java NIO原理圖文分析

一.java NIO 和阻塞I/O的區別
     1. 阻塞I/O通信模型
     2. java NIO原理及通信模型
二.java NIO服務端和客戶端代碼實現
 

具體分析: 

一.java NIO 和阻塞I/O的區別 

1. 阻塞I/O通信模型
 

假如現在你對阻塞I/O已有了一定了解,我們知道阻塞I/O在調用InputStream.read()方法時是阻塞的,它會一直等到數據到來時(或超時)纔會返回;同樣,在調用ServerSocket.accept()方法時,也會一直阻塞到有客戶端連接纔會返回,每個客戶端連接過來後,服務端都會啓動一個線程去處理該客戶端的請求。阻塞I/O的通信模型示意圖如下:

 

 

如果你細細分析,一定會發現阻塞I/O存在一些缺點。根據阻塞I/O通信模型,我總結了它的兩點缺點:
1. 當客戶端多時,會創建大量的處理線程。且每個線程都要佔用棧空間和一些CPU時間

2. 阻塞可能帶來頻繁的上下文切換,且大部分上下文切換可能是無意義的。

在這種情況下非阻塞式I/O就有了它的應用前景。

2. 
java NIO原理及通信模型 

Java NIO是在jdk1.4開始使用的,它既可以說成“新I/O”,也可以說成非阻塞式I/O。下面是java NIO的工作原理:

1. 由一個專門的線程來處理所有的 IO 事件,並負責分發。 
2. 事件驅動機制:事件到的時候觸發,而不是同步的去監視事件。 
3. 線程通訊:線程之間通過 wait,notify 等方式通訊。保證每次上下文切換都是有意義的。減少無謂的線程切換。 

閱讀過一些資料之後,下面貼出我理解的java NIO的工作原理圖:

 

 

(注:每個線程的處理流程大概都是讀取數據、解碼、計算處理、編碼、發送響應。)

Java NIO的服務端只需啓動一個專門的線程來處理所有的 IO 事件,這種通信模型是怎麼實現的呢?呵呵,我們一起來探究它的奧祕吧。java NIO採用了雙向通道(channel)進行數據傳輸,而不是單向的流(stream),在通道上可以註冊我們感興趣的事件。一共有以下四種事件:

 

事件名 對應值
服務端接收客戶端連接事件 SelectionKey.OP_ACCEPT(16)
客戶端連接服務端事件 SelectionKey.OP_CONNECT(8)
讀事件 SelectionKey.OP_READ(1)
寫事件 SelectionKey.OP_WRITE(4)

 

   
   
   
   
   

服務端和客戶端各自維護一個管理通道的對象,我們稱之爲selector,該對象能檢測一個或多個通道 (channel) 上的事件。我們以服務端爲例,如果服務端的selector上註冊了讀事件,某時刻客戶端給服務端發送了一些數據,阻塞I/O這時會調用read()方法阻塞地讀取數據,而NIO的服務端會在selector中添加一個讀事件。服務端的處理線程會輪詢地訪問selector,如果訪問selector時發現有感興趣的事件到達,則處理這些事件,如果沒有感興趣的事件到達,則處理線程會一直阻塞直到感興趣的事件到達爲止。下面是我理解的java NIO的通信模型示意圖:

 


結合代碼理解:

服務器端代碼

package nio;


import java.io.IOException;
import java.net.InetSocketAddress;
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 NioServerTest implements Runnable{
private final int BLOCK = 4096;
/*發送數據緩衝區*/
private final ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK);
/*接受數據緩衝區*/
private final ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK);
//通道管理器  
    private Selector selector;  
    
    private int flag;
    //初始化服務器配置
public void initServer(int port) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//打開服務器管道,只能通過open方法獲取,不能使用new創建
serverSocketChannel.configureBlocking(false);//設置爲非阻塞
InetSocketAddress inetSocketAddress = new InetSocketAddress(port);//創建連接管道端口地址
serverSocketChannel.socket().bind(inetSocketAddress);//給serverSocket管道的socket綁定端口
selector = Selector.open();//打開通道管理器
/*
將通道管理器和該通道綁定,併爲該通道註冊SelectionKey.OP_ACCEPT事件,註冊該事件後,  
當該事件到達時,selector.select()會返回,如果該事件沒到達selector.select()會一直阻塞。
        */
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
System.out.println("服務端啓動成功!"); 
}
/** 
     * 採用輪詢的方式監聽selector上是否有需要處理的事件,如果有,則進行處理 
     * @throws IOException 
     */  
public void listion() throws IOException{
System.out.println("服務端監聽開始!"); 
// 輪詢訪問selector
while(true){
//當註冊的事件到達時,方法返回;否則,該方法會一直阻塞  
selector.select();
/*
 前面說註冊器Seclector要監聽ON_ACCEPT事件現在事件來了  需要服務端做相應處理了
           這裏的處理方式是 啓動一個 SocketChannel對象來處理客戶端的這個連接,這裏的處理
           是給當前的這個事件key綁定一個SocketChannel對象 ,告訴selector 如有ON_READ事件
           進來就用此SocketChannl來處理 
        */
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while(it.hasNext()){
SelectionKey selectionKey = it.next();
//刪除已選的key,以防重複處理  
                it.remove();  
                handleClientRequest(selectionKey);
}
flag++;
}
}
public void handleClientRequest(SelectionKey selectionKey) throws IOException{
ServerSocketChannel serverChannel = null;
SocketChannel clientChannel = null;
String receiveText;
String sendText;
int count;
if(selectionKey.isAcceptable()){//客戶端請求連接事件
try {
//啓動服務器的一個channel對象對某個事件進行處理,這個channel對象代表客戶端的連接
serverChannel = (ServerSocketChannel)selectionKey.channel();
clientChannel = serverChannel.accept();//處於阻塞狀態
clientChannel.configureBlocking(false);//設置成非阻塞
sendText = "客戶端你可以連我了....................";
sendBuffer.clear();
sendBuffer.put(sendText.getBytes());
sendBuffer.flip();
clientChannel.write(sendBuffer);
//在和客戶端連接成功之後,爲了可以接收到客戶端的信息,需要給通道設置讀的權限。
clientChannel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}else if(selectionKey.isReadable()){//客戶端請求讀事件
clientChannel = (SocketChannel)selectionKey.channel();
receiveBuffer.clear();//在讀取客戶端的數據要清空緩衝區,把緩衝區的position設置爲0,limit=capacity
count = clientChannel.read(receiveBuffer);
receiveText = new String(receiveBuffer.array(),0,count);
System.out.println("獲取客戶端的數據:" + receiveText + "-" + flag);
//爲了可以向客戶端發送數據,需要給通道設置寫的權限。
clientChannel.register(selector, SelectionKey.OP_WRITE);
}else if(selectionKey.isWritable()){//客戶端請求寫事件
clientChannel = (SocketChannel)selectionKey.channel();
sendText = "客戶端你好"+flag;
sendBuffer.clear();
sendBuffer.put(sendText.getBytes());
sendBuffer.flip();//在向客戶端寫數據之前,將緩衝區的position設置爲0,limit=position未回覆爲0時的位置
clientChannel.write(sendBuffer);
clientChannel.register(selector, SelectionKey.OP_READ);
}
}
public void run() {
try {
listion();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
NioServerTest server = new NioServerTest();
server.initServer(8888);
new Thread(server).start();
}
}


客戶端:

package nio;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NioClientTest implements Runnable{
private final int BLOCK = 4096;
/*發送數據緩衝區*/
private final ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK);
/*接受數據緩衝區*/
private final ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK);
private Selector selector;
private int flag = 0;
public void initClient() throws IOException{
InetSocketAddress hostAddress = new InetSocketAddress("localhost",8888);//連接服務器地址
SocketChannel socketChannel = SocketChannel.open();//打開通道
socketChannel.configureBlocking(false);//設置爲非阻塞
//打開通道管理器
selector = Selector.open();
//將通道管理器和該通道綁定,併爲該通道註冊SelectionKey.OP_CONNECT事件。  
socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 客戶端連接服務器,其實方法執行並沒有實現連接,需要在listen()方法中調用socketChannel.finishConnect();才能完成連接  
socketChannel.connect(hostAddress);
}
public void listen() throws IOException{
System.out.println("客戶端監聽開始-" + flag);
while(true){//通道管理器selector對通道進行輪詢操作
selector.select();//阻塞狀態
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
iterator.remove();
handleServerResult(selectionKey);
}
keys.clear();
flag++;
}
}
public void handleServerResult(SelectionKey selectionKey) throws IOException{
String sendText;
String receiveText;
int count;
SocketChannel socketChannel;
if(selectionKey.isConnectable()){
socketChannel = (SocketChannel)selectionKey.channel();
if(socketChannel.isConnectionPending()){//如果socket正在連接,則完成連接
socketChannel.finishConnect();
sendBuffer.clear();
sendText = "服務器你好,我連接上你了,有何指示"+flag;
sendBuffer.put(sendText.getBytes());
sendBuffer.flip();
socketChannel.write(sendBuffer);
}
socketChannel.register(selector, SelectionKey.OP_READ);
}else if(selectionKey.isReadable()){
receiveBuffer.clear();
socketChannel = (SocketChannel)selectionKey.channel();
count = socketChannel.read(receiveBuffer);
receiveText = new String(receiveBuffer.array(),0,count);
System.out.println("接收到服務器的指示:"+receiveText + "-" + flag);
socketChannel.register(selector, SelectionKey.OP_WRITE);
}else if(selectionKey.isWritable()){
sendBuffer.clear();
socketChannel = (SocketChannel)selectionKey.channel();
sendText = "服務器你好,有何指示"+flag;
sendBuffer.put(sendText.getBytes());
sendBuffer.flip();
socketChannel.write(sendBuffer);
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
public void run() {
try {
listen();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
NioClientTest client = new NioClientTest();
try {
client.initClient();
} catch (IOException e) {
e.printStackTrace();
}
new Thread(client).start();
}

}

 


原始博客地址http://weixiaolu.iteye.com/blog/1479656

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章