目錄:
NIO結(jié)構(gòu)
NIO與傳統(tǒng)IO異同
NIO使用步驟
NIO代碼
ByteBuffer難點(diǎn)解析
1:NIO結(jié)構(gòu):
Channel:通道,連接客戶(hù)端和服務(wù)端的一個(gè)管道,管道內(nèi)可以雙向傳輸數(shù)據(jù)。
Selector:選擇器,可以想象成一個(gè)環(huán)狀傳送帶,上面可以接入很多管道,selector還可以對(duì)每個(gè)管道設(shè)置感興趣的顏色(連接(紅色),讀(黃色),寫(xiě)(藍(lán)色),接收數(shù)據(jù))。當(dāng)Selector開(kāi)始輪詢(xún)的時(shí)候Selector這個(gè)傳送帶就一直轉(zhuǎn)動(dòng),當(dāng)某個(gè)管道被傳送到感興趣事件檢查點(diǎn)的時(shí)候,selector會(huì)檢查改管道當(dāng)前顏色(即事件)之前是否被注冊(cè)成了感興趣顏色(事件),如果感興趣,那么Selector就可以對(duì)這個(gè)管道做處理了,比如把管道傳給別的線程,讓別的線程完成讀寫(xiě)操作。
ByteBuffer:字節(jié)緩沖區(qū),本質(zhì)上是一個(gè)連續(xù)的字節(jié)數(shù)組,Selector感興趣的事件發(fā)生后對(duì)管道的讀操作所讀到的數(shù)據(jù)都存儲(chǔ)在ByteBuffer中,而對(duì)管道的寫(xiě)操作也是以ByteBuffer為源頭,值得注意的是ByteBuffer可以有多個(gè)。想想看,我們使用的所有基本類(lèi)型都可以轉(zhuǎn)換成byte字節(jié),比如Integer類(lèi)型占4字節(jié),那么傳遞數(shù)字 1 ByteBuffer底層數(shù)組被占用了4個(gè)格子。

2:與傳統(tǒng)IO比較:
1:傳統(tǒng)IO一般是一個(gè)線程等待連接,連接過(guò)來(lái)之后分配給processor線程,processor線程與通道連接后如果通道沒(méi)有數(shù)據(jù)過(guò)來(lái)就會(huì)阻塞(線程被動(dòng)掛起)不能做別的事情。NIO則不同,首先:在Selector線程輪詢(xún)的過(guò)程中就已經(jīng)過(guò)濾掉了不感興趣的事件,其次:在processor處理感興趣事件的read和write都是非阻塞操作即直接返回的,線程沒(méi)有被掛起。
2:傳統(tǒng)IO的管道是單向的,NIO的管道是雙向的
3:兩者都是同步的,也就是Java程序親力親為的去讀寫(xiě)數(shù)據(jù),不管傳統(tǒng)IO還是NIo都需要read和write方法,這些都是Java程序調(diào)用的而不是系統(tǒng)幫我們調(diào)用的。NIO2.0里這點(diǎn)得到了改觀,即使用異步非阻塞AsynchronousXXX四個(gè)類(lèi)來(lái)處理。
3:使用NIO步驟:(服務(wù)端)
首先:創(chuàng)建一個(gè)傳送帶
然后:創(chuàng)建一個(gè)管道,設(shè)置管道為非阻塞,綁定端口
然后:把管道放到傳送帶上
再然后:?jiǎn)?dòng)傳送帶
其次:傳送帶感興趣事件檢查點(diǎn)查獲一個(gè)感興趣管道,轉(zhuǎn)給其他線程對(duì)管道進(jìn)行非阻塞讀寫(xiě)
最后:全使用完,關(guān)閉管道
過(guò)程很清晰,跟我們現(xiàn)實(shí)世界中的傳送帶效果一樣。
4:代碼體現(xiàn):
注意:只寫(xiě)服務(wù)器端關(guān)鍵步驟,客戶(hù)端可以參考這些代碼
public class Server implements Runnable {
private Selector selector;
private ByteBuffer buffer = ByteBuffer.allocate(1024);
public Server(int port) {
? ? ? ? try {
? ? ? ? ? ? //1 創(chuàng)建一個(gè)傳送帶
? ? ? ? ? ? selector = Selector.open();
? ? ? ? ? ? //2 創(chuàng)建一個(gè)管道
? ? ? ? ? ? ServerSocketChannel ssc = ServerSocketChannel.open();
? ? ? ? ? ? //3 設(shè)置服務(wù)器通道為非阻塞方式
? ? ? ? ? ? ssc.configureBlocking(false);
? ? ? ? ? ? //4 綁定TCP地址
? ? ? ? ? ? ssc.bind(new InetSocketAddress(port));
? ? ? ? ? ? //5 把管道放到傳送帶上,并在傳送帶上注冊(cè)一個(gè)感興趣事件,此處傳送帶感興趣事件為連接事件
? ? ? ? ? ? ssc.register(selector, SelectionKey.OP_ACCEPT);
? ? ? ? ? ? System.out.println("Server start, port:" + port);
? ? ? ? } catch (IOException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
public void run() {?
?while (true) {
? ? try {
? ? ? ? ? ? ? ? //1 啟動(dòng)傳送帶,開(kāi)始輪詢(xún)
? ? ? ? ? ? ? ? selector.select();
? ? ? ? ? ? ? ? //2 所有感興趣事件的keys
? ? ? ? ? ? ? ?SelectionKey Iterator keys = selector.selectedKeys().iterator();
? ? ? ? ? ? ? ? //3 遍歷所有感興趣事件集合
? ? ? ? ? ? ? ? while (keys.hasNext()) {
? ? ? ? ? ? ? ? ? ? SelectionKey key = keys.next();
? ? ? ? ? ? ? ? ? ? keys.remove();
? ? ? ? ? ? ? ? ? ? if(key.isValid()) { //如果key的狀態(tài)是有效的
? ? ? ? ? ? ? ? ? ? ? ? if(key.isAcceptable()) { //如果key是阻塞狀態(tài),則調(diào)用accept()方法
? ? ? ? ? ? ? ? ? ? ? ? ? ? accept(key);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? if(key.isReadable()) { //如果key是可讀狀態(tài),則調(diào)用read()方法
? ? ? ? ? ? ? ? ? ? ? ? ? ? read(key);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? private void accept(SelectionKey key) {
? ? ? ? try {
? ? ? ? ? ? //1 獲取服務(wù)器通道
? ? ? ? ? ? ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
? ? ? ? ? ? //2 執(zhí)行阻塞方法
? ? ? ? ? ? SocketChannel sc = ssc.accept();
? ? ? ? ? ? //3 設(shè)置阻塞模式為非阻塞
? ? ? ? ? ? sc.configureBlocking(false);
? ? ? ? ? ? //4 注冊(cè)到多路復(fù)用選擇器上,并設(shè)置讀取標(biāo)識(shí)
? ? ? ? ? ? sc.register(selector, SelectionKey.OP_READ);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
? ? private void read(SelectionKey key) {
? ? ? ? try {
? ? ? ? ? ? //1 清空緩沖區(qū)中的舊數(shù)據(jù)
? ? ? ? ? ? buffer.clear();
? ? ? ? ? ? //2 獲取之前注冊(cè)的SocketChannel通道
? ? ? ? ? ? SocketChannel sc = (SocketChannel) key.channel();
? ? ? ? ? ? //3 將sc中的數(shù)據(jù)放入buffer中
? ? ? ? ? ? int count = sc.read(buffer);
? ? ? ? ? ? if(count == -1) { // == -1表示通道中沒(méi)有數(shù)據(jù)
? ? ? ? ? ? ? ? key.channel().close();
? ? ? ? ? ? ? ? key.cancel();
? ? ? ? ? ? ? ? return;
? ? ? ? ? ? }
? ? ? ? ? ? //讀取到了數(shù)據(jù),將buffer的position復(fù)位到0
? ? ? ? ? ? buffer.flip();
? ? ? ? ? ? byte[] bytes = new byte[buffer.remaining()];
? ? ? ? ? ? //將buffer中的數(shù)據(jù)寫(xiě)入byte[]中
? ? ? ? ? ? buffer.get(bytes);
? ? ? ? ? ? String body = new String(bytes).trim();
? ? ? ? ? ? System.out.println("Server:" + body);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
? ? public static void main(String[] args) {
? ? ? ? new Thread(new Server(8379)).start();
? ? }
}
其他:
涉及到ByteBuffer分類(lèi)及ByteBuffer的讀寫(xiě)這里就不過(guò)多介紹了,就是一些指針和模式的變動(dòng),主要是flip方法,調(diào)用flip方法之后的變化從寫(xiě)模式變成讀模式
