不知不覺,已那麼長時間沒有更新東西了,說來真是汗顏啊。(主要是最近在技術上豁然開朗的感受愈來愈少了-_-|||)java
最近一直在學習Linux相關的東西。又一次接觸到了I/O複用模型(select/poll/epoll),因爲很久沒在用NIO寫過代碼了,今天就小試寫個例子,以鞏固下對I/O複用模型的理解。這不,遇到了一個坑,也產生了一點疑惑。^_^。編程
簡單描述:Selector的select方法返回的key集合中有一個SelectionKey是可讀的,可是調用與此SelectionKey關聯的channel的read方法,老是返回讀取長度是-1。既然返回-1,能夠說明tcp連接已經斷開。在下次調用select方法不該再返回這個SelectionKey,也不該該此SelectionKey是可讀狀態的。但事實並不是如此:瀏覽器
public class NIOMain { public static void main(String[] args) throws Exception { Selector selector = Selector.open(); ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false); serverChannel.socket().bind(new InetSocketAddress(9000), 10); serverChannel.register(selector, SelectionKey.OP_ACCEPT); doSelect(selector); } public static void doSelect(Selector selector)throws Exception{ while (true) { int srt=selector.select(); if(srt<=0){ continue; } Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> iter = keys.iterator(); while(iter.hasNext()){ SelectionKey key = iter.next(); if(key.isAcceptable()){ ServerSocketChannel sChannel= (ServerSocketChannel) key.channel(); SocketChannel cChannel = sChannel.accept(); cChannel.configureBlocking(false); cChannel.register(selector, SelectionKey.OP_READ); }else if(key.isReadable()){ SocketChannel cChannel = (SocketChannel) key.channel(); ByteBuffer bb = ByteBuffer.allocate(1024); int len =cChannel.read(bb); bb.flip(); if(bb.hasArray() && len>0){ System.out.println("from client "+":"+ new String(bb.array(),0,len)); int newInterestOps = key.interestOps(); newInterestOps |= SelectionKey.OP_WRITE; key.interestOps(newInterestOps); }else if(len==-1){ System.out.println("no data");//在這裏不能忘記關閉channel } bb.clear(); } iter.remove(); } } } }
運行此代碼,而後在瀏覽器裏輸入127.0.0.1:9000,回車。結果是控制檯裏首先打印出http協議的信息。而後就是死循環打印no data。緣由可想而知,瀏覽器在發起http請求後,必定時間沒有獲得服務器端的相應,便會斷開tcp連接。此時channel的read方法就會返回-1。坑的是,連接都已經斷開了,Selector還能將它select出來,而且一直是可讀狀態。這就致使了一直死循環打印no data。若是這種事情發生在生產環境,後果然是不堪設想啊。服務器
解決方式雖然比較簡單,但卻不能疏忽遺漏。當channel的read方法返回-1時。調用channel的close方法關閉channel。上邊代碼就是在打印no data的地方添加一行:cChannel.close()。這樣channel對應的SelectionKey也就不會再被select出來了。也就再也不發生死循環了。session
NIO編程中我一直有一個疑惑或者說不肯定,就是何時調用channel的write方法將數據返回給客戶端。框架
在網上看到的一些例子代碼中無非兩種。socket
這兩個方式彷佛均可以工做,跑一些例子也都沒發現什麼問題。可是內心老是感受有一點不夠明確不夠開朗(可能就是由於對系統底層的實現不夠明確的緣由)。Java有一些成熟的開源的NIO框架,好比netty、mina。何不去看看他們是如何處理的呢?好,接下來就看看mina的實現方式。(我這裏看的是mina2.0.2版本)tcp
接下來是我追蹤到AbstractPollingIoProcessor的flushNow方法的代碼學習
因爲篇幅就不貼上writeBuffer方法的所有代碼,其關鍵調用:,writeBuffer方法也是將write方法返回的localWrittenBytes返回。接下來讓咱們抓緊看看write方法的實現吧。並看看到底返回的是什麼東西spa
拋開其餘的細節無論,我們先看看如何實現向客戶返回數據的,mina直接從session中拿到關聯的SocketChannel,而後直接調用SocketChannel的write方法寫數據到客戶端,並將write寫出去數據的長度記錄下來。
讓咱們返回到最開始flushNow方法:
能夠看到,當channel寫出去的數據長度大於零,而且buff裏還有數據要寫時。調用了setInterestedInWrite方法,經過方法名也知道是在註冊對寫事件感興趣是吧,看下代碼明確下吧
沒錯,確實是在註冊對寫事件感興趣。在flushNow方法後邊還有一個對localWrittenBytes等於零的判斷:
經過源代碼裏的註釋,就知道,當localWrittenBytes等於零時,也就是調用channel的write沒有寫出任何數據,此時就是內核的Buufer滿了,是不可寫狀態。因此這裏也調用setInterestedInWrite方法註冊可寫感興趣,以待可寫事件發生後再發送數據到客戶端。
總結一下mina的實現就是:讀取到客戶端請求的數據後,就調用channel的write方法向客戶返回數據,若是channel的write方法沒有把所要返回的數據所有發送完,就註冊對可寫感興趣,以待下次可寫事件觸發時再繼續發送。
就寫到這吧,有啥說的不清楚,說的不許確的地方,還望高手不吝指教(*^__^*) ……