標籤:
Java NIO是非阻塞IO的實現,基於事件驅動,非常適用於伺服器需要維持大量串連,但是資料交換量不大的情況,例如一些即時通訊的服務等等,它主要有三個部分組成:
- Channels
- Buffers
- Selectors
Channel有兩種ServerSocketChannel 和 SocketChannel,ServerSocketChannel可以監聽新加入的Socket串連,SocketChannel用於讀和寫操作。NIO總是把緩衝區的資料寫入通道,或者把通道裡的資料讀出到緩衝區。
Buffer本質上是一塊用於讀寫的記憶體,只是被封裝成了buffer對象,你可以通過allocateDirect()或者allocate()申請記憶體空間(allocate分配方式產生的記憶體開銷是在JVM中的,而allocateDirect的分配方式產生的開銷在JVM之外,以就是系統級的記憶體配置,使用allocateDirect尤其注意記憶體溢出問題),Buffer尤其需要理解三個概念,
capacity、position、limit,capacity是固定大小,position是當前讀寫位置,limit是一個類似於門限的值,用於控制讀寫的最大的位置。Buffer的常用方法有clear、compact、flip等等,還有比如Buffer的靜態方法wrap等等,這些需要根據capacity、position、limit的值進行理解,上面ifeve上的文章就很詳細了,我就不再累述了。
Selector用於檢測通道,我們通過它才知道哪個通道發生了哪個事件,所以如果需要用selector的話就需要首先進行register,然後遍曆SelectionKey對事件進行處理。它一共有SelectionKey.OP_CONNECT、SelectionKey.OP_ACCEPT、SelectionKey.OP_READ、SelectionKey.OP_WRITE四種事件類型。
我在這裡只是粗略的總結,關於NIO的概念 http://ifeve.com/java-nio-all/ 可以看這裡。
http://blog.csdn.net/ns_code/article/details/15545057 蘭亭風雨的這個demo不錯,我直接照搬過來了。
import java.io.IOException;import java.net.InetSocketAddress;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.util.Iterator;public class NIOServer { private static int BUFF_SIZE=1024; private static int TIME_OUT = 2000; public static void main(String[] args) throws IOException { Selector selector = Selector.open(); ServerSocketChannel serverSocketChannel=ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(10083)); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); TCPProtocol protocol = new EchoSelectorProtocol(BUFF_SIZE); while (true) { if(selector.select(TIME_OUT)==0){ //在等待通道準備的同時,也可以非同步地執行其他任務, 這裡列印* System.out.print("*"); continue; } Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator(); while (keyIter.hasNext()) { SelectionKey key = keyIter.next(); //如果服務端通道感興趣的I/O操作為accept if (key.isAcceptable()){ protocol.handleAccept(key); } //如果用戶端通道感興趣的I/O操作為read if (key.isReadable()){ protocol.handleRead(key); } //如果該索引值有效,並且其對應的用戶端通道感興趣的I/O操作為write if (key.isValid() && key.isWritable()) { protocol.handleWrite(key); } //這裡需要手動從鍵集中移除當前的key keyIter.remove(); } } }}
import java.io.IOException;import java.nio.channels.SelectionKey;public interface TCPProtocol { void handleAccept(SelectionKey key) throws IOException; void handleRead(SelectionKey key) throws IOException; void handleWrite(SelectionKey key) throws IOException;}
import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;public class EchoSelectorProtocol implements TCPProtocol { private int bufSize; // 緩衝區的長度 public EchoSelectorProtocol(int bufSize){ this.bufSize = bufSize; } @Override public void handleAccept(SelectionKey key) throws IOException { System.out.println("Accept"); SocketChannel socketChannel = ((ServerSocketChannel)key.channel()).accept(); socketChannel.configureBlocking(false); socketChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufSize)); } @Override public void handleRead(SelectionKey key) throws IOException { SocketChannel clntChan = (SocketChannel) key.channel(); //擷取該通道所關聯的附件,這裡為緩衝區 ByteBuffer buf = (ByteBuffer) key.attachment(); buf.clear(); long bytesRead = clntChan.read(buf); //如果read()方法返回-1,說明用戶端關閉了串連,那麼用戶端已經接收到了與自己發送位元組數相等的資料,可以安全地關閉 if (bytesRead == -1){ clntChan.close(); }else if(bytesRead > 0){ //如果緩衝區總讀入了資料,則將該通道感興趣的操作設定為為可讀可寫 key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); } } @Override public void handleWrite(SelectionKey key) throws IOException { // TODO Auto-generated method stub ByteBuffer buffer=(ByteBuffer) key.attachment(); buffer.flip(); SocketChannel clntChan = (SocketChannel) key.channel(); //將資料寫入到通道中 clntChan.write(buffer); if (!buffer.hasRemaining()){ //如果緩衝區中的資料已經全部寫入了通道,則將該通道感興趣的操作設定為可讀 key.interestOps(SelectionKey.OP_READ); } //為讀入更多的資料騰出空間 buffer.compact(); }}
import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SocketChannel;public class NIOClient { public static void main(String[] args) throws IOException { SocketChannel clntChan = SocketChannel.open(); clntChan.configureBlocking(false); if (!clntChan.connect(new InetSocketAddress("localhost", 10083))){ //不斷地輪詢串連狀態,直到完成串連 while (!clntChan.finishConnect()){ //在等待串連的時間裡,可以執行其他任務,以充分發揮非阻塞IO的非同步特性 //這裡為了示範該方法的使用,只是一直列印"." System.out.print("."); } } //為了與後面列印的"."區別開來,這裡輸出分行符號 System.out.print("\n"); //分別執行個體化用來讀寫的緩衝區 ByteBuffer writeBuf = ByteBuffer.wrap("send send send".getBytes()); ByteBuffer readBuf = ByteBuffer.allocate("send".getBytes().length-1); while (writeBuf.hasRemaining()) { //如果用來向通道中寫資料的緩衝區中還有剩餘的位元組,則繼續將資料寫入通道 clntChan.write(writeBuf); } StringBuffer stringBuffer=new StringBuffer(); //如果read()接收到-1,表明服務端關閉,拋出異常 while ((clntChan.read(readBuf)) >0){ readBuf.flip(); stringBuffer.append(new String(readBuf.array(),0,readBuf.limit())); readBuf.clear(); } //列印出接收到的資料 System.out.println("Client Received: " + stringBuffer.toString()); //關閉通道 clntChan.close(); }}
Java Socket NIO樣本總結