陷阱1:處理事件忘記移除key
在select返回值大於0的狀況下,循環處理
Selector.selectedKeys集合,每處理一個必須從Set中移除java
Iterator<SelectionKey> it=set.iterator(); While(it.hasNext()){ SelectionKey key=it.next(); it.remove(); //切記移除 „„處理事件 }
不移除的後果是本次的就緒的key集合下次會再次返回,致使無限循環,CPU消耗100%react
陷阱2:Selector返回的key集合非線程安全linux
Selector.selectedKeys/keys 返回的集合都是非線程安全的
Selector.selectedKeys返回的可移除
Selector.keys 不可變
對selected keys的處理必須單線程處理或者適當同步安全
陷阱3:正確註冊Channel和更新interest
直接註冊不可嗎?
channel.register(selector, ops, attachment);
不是不能夠,效率問題
至少加兩次鎖,鎖競爭激烈
Channel自己的regLock,競爭幾乎沒有
Selector內部的key集合,競爭激烈
更好的方式:加入緩衝隊列,等待註冊,reactor單線程處理框架
If(isReactorThread()){ channel.register(selector,ops,attachment); } else{ register.offer(newEvent(channel,ops,attachment)); selector.wakeup(); }
一樣,SelectionKey.interest(ops)
在linux上會阻塞,須要獲取selector內部鎖作同步
在win32上不會阻塞
屏蔽平臺差別,避免鎖的激烈競爭,採用相似註冊channel的方式:socket
if (this.isReactorThread()) { key.interestOps(key.interestOps() | SelectionKey.OP_READ); } else { this.register.offer(new Event(key,SelectionKey.OP_READ)); selector.wakeup(); }
陷阱4:正確處理OP_WRITE
OP_WRITE處理不當很容易致使CPU 100%
OP_WRITE觸發條件:
前提:interest了OP_WRITE
觸發條件:
socket發送緩衝區可寫
遠端關閉
有錯誤發生
正確的處理方式:
僅在已經鏈接的channel上註冊
僅在有數據可寫的時候才註冊
觸發以後當即取消註冊,不然會繼續觸發致使循環
處理完成後視狀況決定是否繼續註冊
沒有徹底寫入,繼續註冊
所有寫入,無需註冊oop
陷阱5:正確取消註冊channel
SelectableChannel一旦註冊將一直有效直到明確取消
怎麼取消註冊?
channel.close(),內部會調用key.cancel()
key.cancel();
中斷channel的讀寫所在線程引發的channel關閉
可是這樣還不夠!
key.cancel()僅僅是將key加入cancelledKeys
直到下一次select才真正處理
而且channel的socketfd只有在真正取消註冊後纔會close(fd)this
後果是什麼?
服務端,問題不大,select調用頻繁
客戶端,一般只有一個鏈接,關閉channel以後,沒有調用select就關閉了selector
sockfd沒有關閉,停留在CLOSE_WAIT狀態
正確的處理方式,取消註冊也應看成爲事件交給reactor處理,及時wakeup作select
適當的時候調用selector.selectNow()
Netty在超過256鏈接關閉的時候主動調用一次selectNowspa
static final int CLEANUP_INTERVAL=256; private boolean cleanUpCancelledKeys()throws IOException{ if(cancelledKeys>=CLEANUP_INTERVAL){ cancelledKeys=0; selector.selectNow(); returntrue; } returnfalse; } //channel關閉的時候 channel.socket.close(); cancelledKeys++;
陷阱6:同時註冊OP_ACCPET和OP_READ,同時註冊OP_CONNECT和OP_WRITE
在底層來講,只有兩種事件:read和write
Java NIO還引入了OP_ACCEPT和OP_CONNECT
OP_ACCEPT、OP_READ == Read
OP_CONNECT、OP_WRITE == Write
同時註冊OP_ACCEPT和OP_READ ,或者同時註冊OP_CONNECT和OP_WRITE在不一樣平臺上產生錯誤的行爲,避免這樣作!線程
陷阱7:正確處理connect
SocketChannel.connect方法在非阻塞模式下可能返回false,切記判斷返回值
若是是loopback鏈接,可能直接返回true,表示鏈接成功
返回false,後續處理
註冊channel到selector,監聽OP_CONNECT事件
在OP_CONNECT觸發後,調用SocketChannel.finishConnect成功後,鏈接才真正創建
陷阱:
沒有判斷connect返回值
沒有調用finishConnect
在OP_CONNECT觸發後,沒有移除OP_CONNECT,致使SelectionKey一直處於就緒狀態,空耗CPU
OP_CONNECT只能在尚未鏈接的channel上註冊
忠告
儘可能不要嘗試實現本身的nio框架,除非有經驗豐富的工程師儘可能使用通過普遍實踐的開源NIO框架Mina、Netty三、xSocket儘可能使用最新穩定版JDK遇到問題的時候,也許你能夠先看下java的bug database