一、NIO類庫簡介
站在用戶的角度思考問題,與客戶深入溝通,找到汶上網(wǎng)站設計與汶上網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設計與互聯(lián)網(wǎng)技術結合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:網(wǎng)站制作、成都網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、域名與空間、網(wǎng)頁空間、企業(yè)郵箱。業(yè)務覆蓋汶上地區(qū)。
1、緩沖區(qū)Buffer
Buffer是一個對象,包含一些要寫入和讀出的數(shù)據(jù)。
在NIO中,所有的數(shù)據(jù)都是用緩沖區(qū)處理的,讀取數(shù)據(jù)時,它是從通道(Channel)直接讀到緩沖區(qū)中,在寫入數(shù)據(jù)時,也是從緩沖區(qū)寫入到通道。
緩沖區(qū)實質(zhì)上是一個數(shù)組,通常是一個字節(jié)數(shù)組(ByteBuffer),也可以是其它類型的數(shù)組,此外緩沖區(qū)還提供了對數(shù)據(jù)的結構化訪問以及維護讀寫位置等信息。
Buffer類的繼承關系如下圖所示:
2、通道Channel
Channel是一個通道,網(wǎng)絡數(shù)據(jù)通過Channel讀取和寫入。通道和流的不同之處在于通道是雙向的(通道可以用于讀、寫后者二者同時進行),流只是在一個方向上移動。
Channel大體上可以分為兩類:用于網(wǎng)絡讀寫的SelectableChannel(ServerSocketChannel和SocketChannel就是其子類)、用于文件操作的FileChannel。
下面的例子給出通過FileChannel來向文件中寫入數(shù)據(jù)、從文件中讀取數(shù)據(jù),將文件數(shù)據(jù)拷貝到另一個文件中:
public class NioTest { public static void main(String[] args) throws IOException { copyFile(); } //拷貝文件 private static void copyFile() { FileInputStream in=null; FileOutputStream out=null; try { in=new FileInputStream("src/main/java/data/in-data.txt"); out=new FileOutputStream("src/main/java/data/out-data.txt"); FileChannel inChannel=in.getChannel(); FileChannel outChannel=out.getChannel(); ByteBuffer buffer=ByteBuffer.allocate(1024); int bytesRead = inChannel.read(buffer); while (bytesRead!=-1) { buffer.flip(); outChannel.write(buffer); buffer.clear(); bytesRead = inChannel.read(buffer); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //寫文件 private static void writeFileNio() { try { RandomAccessFile fout = new RandomAccessFile("src/main/java/data/nio-data.txt", "rw"); FileChannel fc=fout.getChannel(); ByteBuffer buffer=ByteBuffer.allocate(1024); buffer.put("hi123".getBytes()); buffer.flip(); try { fc.write(buffer); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //讀文件 private static void readFileNio() { FileInputStream fileInputStream; try { fileInputStream = new FileInputStream("src/main/java/data/nio-data.txt"); FileChannel fileChannel=fileInputStream.getChannel();//從 FileInputStream 獲取通道 ByteBuffer byteBuffer=ByteBuffer.allocate(1024);//創(chuàng)建緩沖區(qū) int bytesRead=fileChannel.read(byteBuffer);//將數(shù)據(jù)讀到緩沖區(qū) while(bytesRead!=-1) { /*limit=position * position=0; */ byteBuffer.flip(); //hasRemaining():告知在當前位置和限制之間是否有元素 while (byteBuffer.hasRemaining()) { System.out.print((char) byteBuffer.get()); } /* * 清空緩沖區(qū) * position=0; * limit=capacity; */ byteBuffer.clear(); bytesRead = fileChannel.read(byteBuffer); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
3、多路復用器Selector
多路復用器提供選擇已經(jīng)就緒的任務的能力。Selector會不斷的輪詢注冊在其上的Channel,如果某個Channel上面發(fā)送讀或者寫事件,這個Channel就處于就緒狀態(tài),會被Selector輪詢出來,然后通過SelectionKey可以獲取就緒Channel的集合,進行后續(xù)的I/O操作。
一個多路復用器Selector可以同時輪詢多個Channel,由于JDK使用了epoll代替了傳統(tǒng)的select實現(xiàn),所以它沒有最大連接句柄1024/2048的限制,意味著只需要一個線程負責Selector的輪詢,就可以接入成千上萬的客戶端。其模型如下圖所示:
用單線程處理一個Selector。要使用Selector,得向Selector注冊Channel,然后調(diào)用它的select()方法。這個方法會一直阻塞到某個注冊的通道有事件就緒。一旦這個方法返回,線程就可以處理這些事件,事件的例子有如新連接進來,數(shù)據(jù)接收等。
注:
1、什么select模型?
select是事件觸發(fā)機制,當?shù)却氖录l(fā)生就觸發(fā)進行處理,多用于Linux實現(xiàn)的服務器對客戶端的處理。
可以阻塞地同時探測一組支持非阻塞的IO設備,是否有事件發(fā)生(如可讀、可寫,有高優(yōu)先級錯誤輸出等),直至某一個設備觸發(fā)了事件或者超過了指定的等待時間。也就是它們的職責不是做IO,而是幫助調(diào)用者尋找當前就緒的設備。
2、什么是epoll模型?
epoll的設計思路,是把select/poll單個的操作拆分為1個epoll_create+多個epoll_ctrl+一個wait。此外,內(nèi)核針對epoll操作添加了一個文件系統(tǒng)”eventpollfs”,每一個或者多個要監(jiān)視的文件描述符都有一個對應的eventpollfs文件系統(tǒng)的inode節(jié)點,主要信息保存在eventpoll結構體中。而被監(jiān)視的文件的重要信息則保存在epitem結構體中。所以他們是一對多的關系。
二、NIO服務器端開發(fā)
功能說明:開啟服務器端,對每一個接入的客戶端都向其發(fā)送hello字符串。
使用NIO進行服務器端開發(fā)主要有以下幾個步驟:
1、創(chuàng)建ServerSocketChannel,配置它為非阻塞模式
serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false);
2、綁定監(jiān)聽,配置TCP參數(shù),如backlog大小
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
3、創(chuàng)建一個獨立的I/O線程,用于輪詢多路復用器Selector
4、創(chuàng)建Selector,將之前創(chuàng)建的ServerSocketChannel注冊到Selector上,監(jiān)聽SelectionKey.ACCEPT
selector=Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
5、啟動I/O線程,在循環(huán)體內(nèi)執(zhí)行Selector.select()方法,輪詢就緒的Channel
while(true) { try { //select()阻塞到至少有一個通道在你注冊的事件上就緒了 //如果沒有準備好的channel,就在這一直阻塞 //select(long timeout)和select()一樣,除了最長會阻塞timeout毫秒(參數(shù))。 selector.select(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); break; } }
6、當輪詢到了處于就緒狀態(tài)的Channel時,需對其進行判斷,如果是OP_ACCEPT狀態(tài),說明是新的客戶端接入,則調(diào)用ServerSocketChannel.accept()方法接受新的客戶端
//返回已經(jīng)就緒的SelectionKey,然后迭代執(zhí)行 Set<SelectionKey> readKeys=selector.selectedKeys(); for(Iterator<SelectionKey> it=readKeys.iterator();it.hasNext();) { SelectionKey key=it.next(); it.remove(); try { if(key.isAcceptable()) { ServerSocketChannel server=(ServerSocketChannel) key.channel(); SocketChannel client=server.accept(); client.configureBlocking(false); client.register(selector,SelectionKey.OP_WRITE); } else if(key.isWritable()) { SocketChannel client=(SocketChannel) key.channel(); ByteBuffer buffer=ByteBuffer.allocate(20); String str="hello"; buffer=ByteBuffer.wrap(str.getBytes()); client.write(buffer); key.cancel(); } }catch(IOException e) { e.printStackTrace(); key.cancel(); try { key.channel().close(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } }
7、設置新接入的客戶端鏈路SocketChannel為非阻塞模式,配置其他的一些TCP參數(shù)
if(key.isAcceptable()) { ServerSocketChannel server=(ServerSocketChannel) key.channel(); SocketChannel client=server.accept(); client.configureBlocking(false); ... }
8、將SocketChannel注冊到Selector,監(jiān)聽OP_WRITE
client.register(selector,SelectionKey.OP_WRITE);
9、如果輪詢的Channel為OP_WRITE,則說明要向SockChannel中寫入數(shù)據(jù),則構造ByteBuffer對象,寫入數(shù)據(jù)包
else if(key.isWritable()) { SocketChannel client=(SocketChannel) key.channel(); ByteBuffer buffer=ByteBuffer.allocate(20); String str="hello"; buffer=ByteBuffer.wrap(str.getBytes()); client.write(buffer); key.cancel(); }
完整代碼如下:
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 ServerSocketChannelDemo { public static void main(String[] args) { ServerSocketChannel serverSocketChannel; Selector selector=null; try { serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.socket().bind(new InetSocketAddress(8080)); selector=Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } while(true) { try { //select()阻塞到至少有一個通道在你注冊的事件上就緒了 //如果沒有準備好的channel,就在這一直阻塞 //select(long timeout)和select()一樣,除了最長會阻塞timeout毫秒(參數(shù))。 selector.select(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); break; } //返回已經(jīng)就緒的SelectionKey,然后迭代執(zhí)行 Set<SelectionKey> readKeys=selector.selectedKeys(); for (Iterator<SelectionKey> it=readKeys.iterator();it.hasNext();) { SelectionKey key=it.next(); it.remove(); try { if(key.isAcceptable()) { ServerSocketChannel server=(ServerSocketChannel) key.channel(); SocketChannel client=server.accept(); client.configureBlocking(false); client.register(selector,SelectionKey.OP_WRITE); } else if(key.isWritable()) { SocketChannel client=(SocketChannel) key.channel(); ByteBuffer buffer=ByteBuffer.allocate(20); String str="hello"; buffer=ByteBuffer.wrap(str.getBytes()); client.write(buffer); key.cancel(); } } catch(IOException e) { e.printStackTrace(); key.cancel(); try { key.channel().close(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } } } } }
我們用telnet localhost 8080模擬出多個客戶端:
程序運行結果如下:
總結
以上就是本文關于Java NIO服務器端開發(fā)詳解的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!
文章標題:JavaNIO服務器端開發(fā)詳解
網(wǎng)頁URL:http://www.ekvhdxd.cn/article48/jiogep.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供定制網(wǎng)站、網(wǎng)站策劃、軟件開發(fā)、虛擬主機、用戶體驗、搜索引擎優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)