NIO Server端多路複用開發的通常步驟是:
//打開選擇器 Selector selector = Selector.open(); //打開通到 ServerSocketChannel socketChannel = ServerSocketChannel.open(); //配置非阻塞模型 socketChannel.configureBlocking(false); //綁定端口 socketChannel.bind(new InetSocketAddress(8080)); //註冊事件,OP_ACCEPT只適用於ServerSocketChannel socketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iter = selectionKeys.iterator(); while(iter.hasNext()) { SelectionKey key = iter.next(); if(key.isAcceptable()) { SocketChannel channel = ((ServerSocketChannel)key.channel()).accept(); channel.configureBlocking(false); channel.register(selector,SelectionKey.OP_READ); } if(key.isWritable()) { } if(key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer readBuffer = ByteBuffer.allocate(512); channel.read(readBuffer); readBuffer.flip(); //handler Buffer //通常是響應客戶端的數據 //直接是write寫不就完事了嘛,爲啥須要write事件? //channel.write(...) } iter.remove(); } }
剛開始對NIO的寫操做理解的不深,不知道爲何要註冊寫事件,什麼時候註冊寫事件,爲何寫完以後要取消註冊寫事件。java
若是有channel
在Selector
上註冊了SelectionKey.OP_WRITE
事件,在調用selector.select();
時,系統會檢查內核寫緩衝區是否可寫(何時是不可寫的呢,好比緩衝區已滿,channel
調用了shutdownOutPut
等等),若是可寫,selector.select();
當即返回,隨後進入key.isWritable()
分支。編程
固然你在channel
上能夠直接調用write(...)
,也能夠將數據發送出去,但這樣不夠靈活,並且可能浪費CPU
。網絡
看一個場景,服務端須要發送一個200M
的Buffer
,看看使用OP_WRITE
事件和不使用的區別。異步
//不使用事件,缺點是,程序運行到這會等到200M文件發送完成後才繼續往下執行,不符合異步事件模型 //的編程思想,若是緩衝區一直處於不可寫狀態,那麼該過程一直在這裏死循環,浪費了CPU資源。 ByteBuffer buffer = .... //200M的Buffer while(buffer.hasRemaining()) { //該方法只會寫入小於socket's output buffer空閒區域的任何字節數 //並返回寫入的字節數,多是0字節。 channel.write(buffer); } //使用事件的方式,誰好誰壞,一看便知 if(key.isReadable()) { ByteBuffer buffer = .... //200M的Buffer //註冊寫事件 key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); //綁定Buffer key.attach(buffer); } //isWritable分支 if(key.isWritable()) { ByteBuffer buffer = (ByteBuffer) key.attachment(); SocketChannel channel = (SocketChannel) key.channel(); if (buffer.hasRemaining()) { channel.write(buffer) } else { //發送完了就取消寫事件,不然下次還會進入該分支 key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); } }
客戶端開發的通常步驟:
//打開選擇器 Selector selector = Selector.open(); //打開通道 SocketChannel socketChannel = SocketChannel.open(); //配置非阻塞模型 socketChannel.configureBlocking(false); //鏈接遠程主機 socketChannel.connect(new InetSocketAddress("127.0.0.1",8080)); //註冊事件 socketChannel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ); //循環處理 while (true) { selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> iter = keys.iterator(); while(iter.hasNext()) { SelectionKey key = iter.next(); if(key.isConnectable()) { //鏈接創建或者鏈接創建不成功 SocketChannel channel = (SocketChannel) key.channel(); //完成鏈接的創建 if(channel.finishConnect()) { } } if(key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(500 * 1024 * 1024); buffer.clear(); channel.read(buffer); //buffer Handler } iter.remove(); } }
起初對OP_CONNECT
事件還有finishConnect
不理解,OP_CONNECT
事件什麼時候觸發,特別是爲何要在key.isConnectable()
分支裏調用finishConnect
方法後才能進行讀寫操做。socket
首先,在non-blocking
模式下調用socketChannel.connect(new InetSocketAddress("127.0.0.1",8080));
鏈接遠程主機,若是鏈接能當即創建就像本地鏈接同樣,該方法會當即返回true
,不然該方法會當即返回false
,而後系統底層進行三次握手創建鏈接。鏈接有兩種結果,一種是成功鏈接,第二種是異常,可是connect
方法已經返回,沒法經過該方法的返回值或者是異常來通知用戶程序創建鏈接的狀況,因此由OP_CONNECT
事件和finishConnect
方法來通知用戶程序。無論系統底層三次鏈接是否成功,selector
都會被喚醒繼而觸發OP_CONNECT
事件,若是握手成功,而且該鏈接未被其餘線程關閉,finishConnect
會返回true
,而後就能夠順利的進行channle
讀寫。若是網絡故障,或者遠程主機故障,握手不成功,用戶程序能夠經過finishConnect
方法得到底層的異常通知,進而處理異常。線程