上文中分析了阻塞,非阻塞,同步和異步概念上的區(qū)別以及各種IO模型的操作流程,本篇文章將主要介紹Java中BIO,NIO和AIO三種IO模型如何使用。需要注意的是,本文中所提到的所有樣例都是在一個(gè)server對(duì)應(yīng)一個(gè)client的情況下工作的,如果你想擴(kuò)展為一個(gè)server服務(wù)多個(gè)client,那么代碼需要做相應(yīng)的修改才能使用。另外,本文只會(huì)講解server端如何處理,客戶端的操作流程可以仿照服務(wù)端進(jìn)行編程,大同小異。文章最后給出了源碼的下載地址。
在Java中,BIO是基于流的,這個(gè)流包括字節(jié)流或者字符流,但是細(xì)心的同學(xué)可能會(huì)發(fā)現(xiàn)基本上所有的流都是單向的,要么只讀,要么只寫(xiě)。在實(shí)際上編程時(shí),在對(duì)IO操作之前,要先獲取輸入流或輸出流,然后對(duì)輸入流讀或?qū)敵隽鲗?xiě)即完成實(shí)際的IO讀寫(xiě)操作。首先需要新建一個(gè)ServerSocket對(duì)象監(jiān)聽(tīng)特定端口,然后當(dāng)有客戶端的連接請(qǐng)求到來(lái)時(shí),在服務(wù)器端獲取一個(gè)Socket對(duì)象,用來(lái)進(jìn)行實(shí)際的通信。
ServerSocket serverSocket = new ServerSocket(PORT); Socket socket = serverSocket.accept();
獲取到Socket對(duì)象后,通過(guò)這個(gè)Socket對(duì)象拿到輸入流和輸出流就可以進(jìn)行相應(yīng)的讀寫(xiě)操作了。
DataInputStream in = new DataInputStream(socket.getInputStream()); DataOutputStream out = new DataOutputStream(socket.getOutputStream());
由于BIO的編程的模型比較簡(jiǎn)單,這里就寫(xiě)這么多,需要下載源代碼的可以到文章末尾。
BIO的編程模型簡(jiǎn)單易行,但是缺點(diǎn)也很明顯。由于采用的是同步阻塞IO的模式,所以server端要為每一個(gè)連接創(chuàng)建一個(gè)線程,一方面,線程之間在進(jìn)行上下文切換的時(shí)候會(huì)造成比較大的開(kāi)銷(xiāo),另一方面,當(dāng)連接數(shù)過(guò)多時(shí),可能會(huì)造成服務(wù)器崩潰的現(xiàn)象產(chǎn)生。
為了解決這個(gè)問(wèn)題,在JDK 1.4的時(shí)候,引入了NIO(New IO)的概念。NIO主要由三個(gè)部分組成,即Channel,Buffer和Selector。Channel可以跟BIO中的Stream類(lèi)比,不同的是Channel是可讀可寫(xiě)的。當(dāng)和Channel進(jìn)行交互的時(shí)候需要Buffer的支持,數(shù)據(jù)可以從Buffer寫(xiě)到Channel中,也可以從Channel中讀到Buffer中,他們的關(guān)系如下圖。
// Read data from channel to bufferSocketChannel socketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); while (socketChannel.read(byteBuffer) > 0) { byteBuffer.flip(); while(byteBuffer.hasRemaining()){ System.out.print((char) byteBuffer.get()); } byteBuffer.clear();}// Write data to channel from buffersocketChannel.write(ByteBuffer.wrap(msg.getBytes()));
NIO中另一個(gè)重要的組件是Selector,Selector可以用來(lái)檢查一個(gè)或多個(gè)Channel是否有新的數(shù)據(jù)到來(lái),這種方式可以實(shí)現(xiàn)在一個(gè)線程中管理多個(gè)Channel的目的,示意圖如下。
serverSocketChannel.configureBlocking(false);
然后調(diào)用select函數(shù),select是個(gè)阻塞函數(shù),它會(huì)阻塞直到某一個(gè)操作被激活。這個(gè)時(shí)候可以獲取一系列的SelectionKey,通過(guò)這個(gè)SelectionKey可以判斷其對(duì)應(yīng)的Channel可進(jìn)行的操作(可讀,可寫(xiě)或者可接受連接),然后進(jìn)行相應(yīng)的操作即可。這里還要注意一個(gè)問(wèn)題就是在判斷完可執(zhí)行的操作后,需要將這個(gè)SelectionKey從集合中移除。
selector.select();Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); if (!selectionKey.isValid()) continue; if (selectionKey.isAcceptable()) { // ready for accepting } else if (selectionKey.isReadable()) { // ready for reading } else if (selectionKey.isWritable()) { // ready for writing } iterator.remove();}
NIO這里最后一個(gè)問(wèn)題是,什么時(shí)候Channel可寫(xiě),這個(gè)問(wèn)題困擾了我很久,經(jīng)過(guò)從網(wǎng)上查資料最后得出的結(jié)論是,只要這個(gè)Channel處于空閑狀態(tài),都是可寫(xiě)的。這個(gè)我也從實(shí)際的程序中論證了。
在JDK 1.7時(shí),Java引入了AIO的概念,AIO還是基于Channel和Buffer的,不同的是它是異步的。用戶線程把實(shí)際的IO操作以及數(shù)據(jù)拷貝全部委托給內(nèi)核來(lái)做,用戶只要傳遞給內(nèi)核一個(gè)用于存儲(chǔ)數(shù)據(jù)的地址空間即可。內(nèi)核處理的結(jié)果通過(guò)兩種方式返回給用戶線程。一是通過(guò)Future對(duì)象,另外一種是通過(guò)回調(diào)函數(shù)的方式,回調(diào)函數(shù)需要實(shí)現(xiàn)CompletionHandler接口。這里只給出通過(guò)回調(diào)方式處理數(shù)據(jù)的樣例,其中關(guān)鍵的步驟已經(jīng)在程序中添加了注釋。
// 創(chuàng)建AsynchronousServerSocketChannel監(jiān)聽(tīng)特定端口,并設(shè)置回調(diào)AcceptCompletionHandlerAsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(PORT)); serverSocketChannel.accept(serverSocketChannel, new AcceptCompletionHandler());// 監(jiān)聽(tīng)回調(diào),當(dāng)用連接時(shí)會(huì)觸發(fā)該回調(diào)private static class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, AsynchronousServerSocketChannel> { @Override public void completed(AsynchronousSocketChannel result, AsynchronousServerSocketChannel attachment) { ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 注冊(cè)read請(qǐng)求以及回調(diào)ReadCompletionHandler result.read(byteBuffer, result, new ReadCompletionHandler(byteBuffer, "client")); // 遞歸監(jiān)聽(tīng) attachment.accept(attachment, this); } @Override public void failed(Throwable exc, AsynchronousServerSocketChannel attachment) { // 遞歸監(jiān)聽(tīng) attachment.accept(attachment, this); }}// 讀取數(shù)據(jù)回調(diào),當(dāng)有數(shù)據(jù)可讀時(shí)觸發(fā)該回調(diào)public class ReadCompletionHandler implements CompletionHandler<Integer, AsynchronousSocketChannel> { private ByteBuffer byteBuffer; private String remoteName; public ReadCompletionHandler(ByteBuffer byteBuffer, String remoteName) { this.byteBuffer = byteBuffer; this.remoteName = remoteName; } @Override public void completed(Integer result, AsynchronousSocketChannel attachment) { if (result <= 0) return; byteBuffer.flip(); System.out.println("[" + this.remoteName + "] " + new String(byteBuffer.array())); byteBuffer.clear(); // 遞歸監(jiān)聽(tīng)數(shù)據(jù) attachment.read(byteBuffer, attachment, this); } @Override public void failed(Throwable exc, AsynchronousSocketChannel attachment) { byteBuffer.clear(); // 遞歸監(jiān)聽(tīng)數(shù)據(jù) attachment.read(byteBuffer, attachment, this); }}
上面給出了BIO,NIO以及AIO在Java中的使用的部分程序,并且分析了其中關(guān)鍵步驟的使用及其需要注意的事項(xiàng)。
需要源碼的同學(xué)可以到這里下載。
聯(lián)系客服