Channel的註冊是在SelectableChannel中定義的:
1 public abstract SelectionKey register(Selector sel, int ops, Object att) 2 throws ClosedChannelException; 3 4 public final SelectionKey register(Selector sel, int ops) 5 throws ClosedChannelException { 6 return register(sel, ops, null); 7 }
而其具體實現是在AbstractSelectableChannel中:
1 public final SelectionKey register(Selector sel, int ops, 2 Object att) 3 throws ClosedChannelException { 4 synchronized (regLock) { 5 if (!isOpen()) 6 throw new ClosedChannelException(); 7 if ((ops & ~validOps()) != 0) 8 throw new IllegalArgumentException(); 9 if (blocking) 10 throw new IllegalBlockingModeException(); 11 SelectionKey k = findKey(sel); 12 if (k != null) { 13 k.interestOps(ops); 14 k.attach(att); 15 } 16 if (k == null) { 17 // New registration 18 synchronized (keyLock) { 19 if (!isOpen()) 20 throw new ClosedChannelException(); 21 k = ((AbstractSelector)sel).register(this, ops, att); 22 addKey(k); 23 } 24 } 25 return k; 26 } 27 }
其中regLock和keyLock是兩個物件,分別用來做註冊鎖和key集合鎖
1 // Lock for key set and count 2 private final Object keyLock = new Object(); 3 4 // Lock for registration and configureBlocking operations 5 private final Object regLock = new Object();
isOpen判斷Channel是否關閉,只有在Channel關閉後才會令isOpen返回false;接著檢驗傳入進來的ops(SelectionKey的狀態,包括OP_READ、OP_WRITE、OP_CONNECT、OP_ACCEPT四種)是否滿足條件,validOps方法在不同的Channel子類中有不同的實現:
SocketChannel中:
1 public final int validOps() { 2 return (SelectionKey.OP_READ 3 | SelectionKey.OP_WRITE 4 | SelectionKey.OP_CONNECT); 5 }
那麼ops只要是上面三種狀態的任意一種或者一種以上,再和validOps的結果運算都為0,若是其他值則丟擲IllegalArgumentException異常;
ServerSocketChannel中:
1 public final int validOps() { 2 return SelectionKey.OP_ACCEPT; 3 }
和上面同理ServerSocketChannel在註冊時,只能傳入OP_ACCEPT狀態。
回到AbstractSelectableChannel的register方法,接下來是對blocking成員的判斷,
1 boolean blocking = true;
這是很重要的一步,因為NIO是既支援阻塞模式也支援非阻塞模式,但是若使用非阻塞模式,那麼必然需要Selector的輪詢,若是在註冊Selector之前沒有通過Channel呼叫configureBlocking方法設定為非阻塞模式,那麼就會在此時註冊時丟擲IllegalBlockingModeException異常。
configureBlocking方法的實現也是在AbstractSelectableChannel中:
1 public final SelectableChannel configureBlocking(boolean block) 2 throws IOException { 3 synchronized (regLock) { 4 if (!isOpen()) 5 throw new ClosedChannelException(); 6 if (blocking == block) 7 return this; 8 if (block && haveValidKeys()) 9 throw new IllegalBlockingModeException(); 10 implConfigureBlocking(block); 11 blocking = block; 12 } 13 return this; 14 }
前兩個判斷邏輯都很簡單,在Channel開啟的情況下根據引數block設定阻塞或者非阻塞模式,注意到第二個判斷說明重複設定相同的阻塞模式直接返回,而第三個判斷則表明block 和blocking不相等,那麼就是在之前設定為了非阻塞模式,而haveValidKeys則間接表明已經完成了註冊,並且已經擁有了自己的SelectionKey集合,此時再設定為非阻塞模式就會引起IllegalBlockingModeException異常。
haveValidKeys方法:
1 private SelectionKey[] keys = null; 2 private int keyCount = 0; 3 4 private boolean haveValidKeys() { 5 synchronized (keyLock) { 6 if (keyCount == 0) 7 return false; 8 for (int i = 0; i < keys.length; i++) { 9 if ((keys[i] != null) && keys[i].isValid()) 10 return true; 11 } 12 return false; 13 } 14 }
邏輯比較簡單,先檢查keys的個數,為0直接返回沒有可用的SelectionKey,接著遍歷keys集合,找到一個可用的就返回true,其中isValid方法在AbstractSelectionKey中實現:
1 private volatile boolean valid = true; 2 3 public final boolean isValid() { 4 return valid; 5 }
可以看到在初始化時valid = true就代表自身是可用狀態,當SelectionKey執行cancel方法撤銷時或者在Channel關閉時的撤銷都會改變:
1 public final void cancel() { 2 // Synchronizing "this" to prevent this key from getting canceled 3 // multiple times by different threads, which might cause race 4 // condition between selector's select() and channel's close(). 5 synchronized (this) { 6 if (valid) { 7 valid = false; 8 ((AbstractSelector)selector()).cancel(this); 9 } 10 } 11 }
Channel關閉時的撤銷在後續的部落格給出,這裡先不討論。
在configureBlocking中的implConfigureBlocking是一個抽象方法,具體的實現和使用的Channel有關,ServerSocketChannel和SocketChannel的實現分別是在ServerSocketChannelImpl和
SocketChannelImpl中,這兩個的實現方式也是完全一樣:
1 protected void implConfigureBlocking(boolean var1) throws IOException { 2 IOUtil.configureBlocking(this.fd, var1); 3 }
而IOUtil的configureBlocking方法是一個native方法,主要是對底層的操作,這裡就不討論了。
繼續回到AbstractSelectableChannel的register方法,在對阻塞模式判斷完畢後,呼叫findKey方法:
1 private SelectionKey findKey(Selector sel) { 2 synchronized (keyLock) { 3 if (keys == null) 4 return null; 5 for (int i = 0; i < keys.length; i++) 6 if ((keys[i] != null) && (keys[i].selector() == sel)) 7 return keys[i]; 8 return null; 9 } 10 }
在同步塊中,首先判斷keys是否初始化過,如果是第一次註冊,那麼keys必定為null,直接就返回null結束;否則已經註冊過,則遍歷keys這個SelectionKey集合,找的傳入的Selector 持有的SelectionKey後直接返回該SelectionKey物件,若沒找到則返回null;
接著對findKey方法的返回值k判斷
若k不為null,則說明註冊過這個Selector ,先呼叫interestOps方法,該方法是在SelectionKeyImpl中實現的:
1 public SelectionKey interestOps(int var1) { 2 this.ensureValid(); 3 return this.nioInterestOps(var1); 4 }
首先通過ensureValid檢驗當前的SelectionKey是否可用(沒有被撤銷,呼叫cancel方法會撤銷):
1 private void ensureValid() { 2 if (!this.isValid()) { 3 throw new CancelledKeyException(); 4 } 5 }
比較簡單,使用之前說過的isValid方法,檢查當前SelectionKey是否可用
nioInterestOps方法:
1 public SelectionKey nioInterestOps(int var1) { 2 if ((var1 & ~this.channel().validOps()) != 0) { 3 throw new IllegalArgumentException(); 4 } else { 5 this.channel.translateAndSetInterestOps(var1, this); 6 this.interestOps = var1; 7 return this; 8 } 9 }
這個判斷和一開始的register中的檢查ops狀態是否合法一樣,若是合法需要呼叫Channel的translateAndSetInterestOps方法,同樣不同的Channel有不同的實現:
SocketChannel是在SocketChannelImpl中實現的:
1 public void translateAndSetInterestOps(int var1, SelectionKeyImpl var2) { 2 int var3 = 0; 3 if ((var1 & 1) != 0) { 4 var3 |= Net.POLLIN; 5 } 6 7 if ((var1 & 4) != 0) { 8 var3 |= Net.POLLOUT; 9 } 10 11 if ((var1 & 8) != 0) { 12 var3 |= Net.POLLCONN; 13 } 14 15 var2.selector.putEventOps(var2, var3); 16 }
之前說過SelectionKey有四種狀態:
1 public static final int OP_READ = 1 << 0; // 0 2 public static final int OP_WRITE = 1 << 2; // 4 3 public static final int OP_CONNECT = 1 << 3; // 8 4 public static final int OP_ACCEPT = 1 << 4; // 16
正如之前所說的SocketChannel只允許存在OP_READ、OP_WRITE 、OP_CONNECT 這三種狀態,所以上面就根據這三種狀態得到對應的POLL事件,最後給SelectionKey繫結的Selector設定POLL事件響應。
putEventOps的實現是在WindowsSelectorImpl中:
1 public void putEventOps(SelectionKeyImpl var1, int var2) { 2 Object var3 = this.closeLock; 3 synchronized(this.closeLock) { 4 if (this.pollWrapper == null) { 5 throw new ClosedSelectorException(); 6 } else { 7 int var4 = var1.getIndex(); 8 if (var4 == -1) { 9 throw new CancelledKeyException(); 10 } else { 11 this.pollWrapper.putEventOps(var4, var2); 12 } 13 } 14 } 15 }
還是一樣若是Selector關閉則丟擲異常,否則得到SelectionKey的index(在Selector中儲存的SelectionKey陣列的下標),判斷下標的合法性,然後給pollWrapper設定事件響應,而pollWrapper的putEventOps方法是一個native方法,這裡就不仔細討論了。
pollWrapper是存放socket控制程式碼fdVal和事件響應events的,用八個位來儲存一對。
而ServerSocketChannel的translateAndSetInterestOps實現和上面一樣,只不過只負責OP_ACCEPT 狀態:
1 public void translateAndSetInterestOps(int var1, SelectionKeyImpl var2) { 2 int var3 = 0; 3 if ((var1 & 16) != 0) { 4 var3 |= Net.POLLIN; 5 } 6 7 var2.selector.putEventOps(var2, var3); 8 }
還是回到AbstractSelectableChannel的register方法中,interestOps呼叫結束後呼叫SelectionKey的attach方法:
1 private volatile Object attachment = null; 2 3 private static final AtomicReferenceFieldUpdater<SelectionKey,Object> 4 attachmentUpdater = AtomicReferenceFieldUpdater.newUpdater( 5 SelectionKey.class, Object.class, "attachment" 6 ); 7 8 public final Object attach(Object ob) { 9 return attachmentUpdater.getAndSet(this, ob); 10 }
這裡直接使用了原子更新器物件來更新attachment 。
k不為null的情況解決了,接下來就是解決k為null的情況,即第一次註冊,或者是再次註冊沒有找到和Selector對應的的SelectionKey。
首先在同步塊內還是先檢查Channel是否關閉,若沒有關閉,呼叫AbstractSelector的register方法完成Selector對SelectionKey的註冊:
而這個register方法的實現是在SelectorImpl中:
1 protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) { 2 if (!(var1 instanceof SelChImpl)) { 3 throw new IllegalSelectorException(); 4 } else { 5 SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this); 6 var4.attach(var3); 7 Set var5 = this.publicKeys; 8 synchronized(this.publicKeys) { 9 this.implRegister(var4); 10 } 11 12 var4.interestOps(var2); 13 return var4; 14 } 15 }
檢查Channel型別是否符合,然後直接建立一個SelectionKeyImpl物件:
1 final SelChImpl channel; 2 public final SelectorImpl selector; 3 4 SelectionKeyImpl(SelChImpl var1, SelectorImpl var2) { 5 this.channel = var1; 6 this.selector = var2; 7 }
SelectionKeyImpl構造很簡單,直接給兩個成員賦值;然後呼叫SelectionKeyImpl物件的attach方法更新附件,接著在同步塊中呼叫抽象方法implRegister
implRegister方法是在WindowsSelectorImpl中實現的:
1 protected void implRegister(SelectionKeyImpl var1) { 2 Object var2 = this.closeLock; 3 synchronized(this.closeLock) { 4 if (this.pollWrapper == null) { 5 throw new ClosedSelectorException(); 6 } else { 7 this.growIfNeeded(); 8 this.channelArray[this.totalChannels] = var1; 9 var1.setIndex(this.totalChannels); 10 this.fdMap.put(var1); 11 this.keys.add(var1); 12 this.pollWrapper.addEntry(this.totalChannels, var1); 13 ++this.totalChannels; 14 } 15 } 16 }
首先呼叫growIfNeeded方法,因為Selector選擇器解決非阻塞,就是使用輪詢的方式,它儲存了一個SelectionKeyImpl陣列,而SelectionKeyImpl記錄了channel以及SelectionKey的狀態,那麼就是根據SelectionKey的狀態和channel來完成通訊。由於在服務端的時候需要和多個客戶端連線,那麼這個陣列必定是動態維持的,所以就考慮到擴容。
1 private SelectionKeyImpl[] channelArray = new SelectionKeyImpl[8]; 2 private int totalChannels = 1;
可以看到這個channelArray一開始固定初始化大小是8,而totalChannels 一開始就是1,這是為了方便後面的操作,channelArray 中下標為0的元素沒用使用,直接從下標為1開始。
growIfNeeded方法:
1 private void growIfNeeded() { 2 if (this.channelArray.length == this.totalChannels) { 3 int var1 = this.totalChannels * 2; 4 SelectionKeyImpl[] var2 = new SelectionKeyImpl[var1]; 5 System.arraycopy(this.channelArray, 1, var2, 1, this.totalChannels - 1); 6 this.channelArray = var2; 7 this.pollWrapper.grow(var1); 8 } 9 10 if (this.totalChannels % 1024 == 0) { 11 this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, this.totalChannels); 12 ++this.totalChannels; 13 ++this.threadsCount; 14 } 15 16 }
因為totalChannels 是從1開始,所以直接判斷totalChannels是否達到了陣列長度,若已達到就需要擴容,可以看到每次擴容都是原來兩倍,從原陣列下標為1的地方開始一直到最後一個元素,拷貝到新陣列下標為1的位置上,再更新channelArray,同時還要給pollWrapper擴容。
pollWrapper的grow方法:
1 void grow(int var1) { 2 PollArrayWrapper var2 = new PollArrayWrapper(var1); 3 4 for(int var3 = 0; var3 < this.size; ++var3) { 5 this.replaceEntry(this, var3, var2, var3); 6 } 7 8 this.pollArray.free(); 9 this.pollArray = var2.pollArray; 10 this.size = var2.size; 11 this.pollArrayAddress = this.pollArray.address(); 12 } 13 14 void replaceEntry(PollArrayWrapper var1, int var2, PollArrayWrapper var3, int var4) { 15 var3.putDescriptor(var4, var1.getDescriptor(var2)); 16 var3.putEventOps(var4, var1.getEventOps(var2)); 17 }
邏輯很簡單,就是把原來的socket控制程式碼fdVal和事件響應events複製到新的PollArrayWrapper物件中,且位置不變。
再回到growIfNeeded,可以看到第二個判斷是檢查totalChannels是否達到了1024的整數次方(totalChannels初始是1,排除0),若是則需要pollWrapper.addWakeupSocket(this.wakeupSourceFd, this.totalChannels)這個操作在WindowsSelectorImpl構造方法時也被呼叫:
1 WindowsSelectorImpl(SelectorProvider var1) throws IOException { 2 super(var1); 3 this.wakeupSourceFd = ((SelChImpl)this.wakeupPipe.source()).getFDVal(); 4 SinkChannelImpl var2 = (SinkChannelImpl)this.wakeupPipe.sink(); 5 var2.sc.socket().setTcpNoDelay(true); 6 this.wakeupSinkFd = var2.getFDVal(); 7 this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, 0); 8 }
addWakeupSocket方法:
1 void addWakeupSocket(int var1, int var2) { 2 this.putDescriptor(var2, var1); 3 this.putEventOps(var2, Net.POLLIN); 4 }
可以看到設定的事件響應是Net.POLLIN,其實就對應OP_READ,而這個wakeupSourceFd是初始化時就設定的wakeupPipe的source的描述符fdVal,即一開始建立的ServerSocketChannel端的SocketChannel(SourceChannel)的描述符fdVal,之前說過Selector的select方法是一個阻塞的操作,呼叫select方法時只有註冊在Selector上的Channel有事件就緒時才會被喚醒;如果說有很多Channel註冊了,但是隻有一個Channel事件就緒,那麼豈不是要做很多無用的輪詢,而fdVal就是解決這個問題,實際上交給作業系統的輪詢的是wakeupSourceFd,作業系統在輪詢pollWrapper中的這些wakeupSourceFd描述符後就能知道哪些wakeupSourceFd上有事件就緒。
可以看到在growIfNeeded後面有一個++this.threadsCount操作,實際上Channel事件的輪詢是交給執行緒來做的,WindowsSelectorImpl中有如下成員:
1 private int threadsCount = 0; 2 private final List<WindowsSelectorImpl.SelectThread> threads = new ArrayList();
SelectThread是Thread的子類,threadsCount記錄輪詢執行緒個數。
那麼就有這種關係,在pollWrapper中,總是以wakeupSourceFd描述符開頭,後面跟著1024個Channel的描述,再往後就又是這種形式;那麼作業系統在輪詢pollWrapper中的這些wakeupSourceFd知道哪些wakeupSourceFd上有事件就緒,進而得到pollWrapper中的wakeupSourceFd起始的偏移地址,每個執行緒只負責輪詢1024個Channel的描述,哪個wakeupSourceFd上有事件就緒,就讓負責的執行緒去輪詢,這樣就減少了不必要的輪詢。
所以在totalChannels達到1024的整數次方時,需要增加新的輪詢執行緒。
growIfNeeded方法結束,channelArray中增添新的SelectionKeyImpl,並且設定下標(呼應前面獲取下標的操作),然後將SelectionKeyImpl存放在fdMap
fdMap儲存的時Channel的描述符和SelectionKeyImpl的對映關係:
1 private static final class MapEntry { 2 SelectionKeyImpl ski; 3 long updateCount = 0L; 4 long clearedCount = 0L; 5 6 MapEntry(SelectionKeyImpl var1) { 7 this.ski = var1; 8 } 9 } 10 11 private static final class FdMap extends HashMap<Integer, WindowsSelectorImpl.MapEntry> { 12 static final long serialVersionUID = 0L; 13 14 private FdMap() { 15 } 16 17 private WindowsSelectorImpl.MapEntry get(int var1) { 18 return (WindowsSelectorImpl.MapEntry)this.get(new Integer(var1)); 19 } 20 21 private WindowsSelectorImpl.MapEntry put(SelectionKeyImpl var1) { 22 return (WindowsSelectorImpl.MapEntry)this.put(new Integer(var1.channel.getFDVal()), new WindowsSelectorImpl.MapEntry(var1)); 23 } 24 25 private WindowsSelectorImpl.MapEntry remove(SelectionKeyImpl var1) { 26 Integer var2 = new Integer(var1.channel.getFDVal()); 27 WindowsSelectorImpl.MapEntry var3 = (WindowsSelectorImpl.MapEntry)this.get(var2); 28 return var3 != null && var3.ski.channel == var1.channel ? (WindowsSelectorImpl.MapEntry)this.remove(var2) : null; 29 } 30 }
程式碼邏輯都很簡單,就不詳細介紹了。
接著呼叫keys的add方法,keys是父類SelectorImpl的成員:
1 protected HashSet<SelectionKey> keys = new HashSet();
接著呼叫pollWrapper的addEntry方法:
1 void addEntry(int var1, SelectionKeyImpl var2) { 2 this.putDescriptor(var1, var2.channel.getFDVal()); 3 }
可以看到僅僅是新增了channel的描述符fdVal,還沒有設定事件響應,最後totalChannels自增implRegister方法結束。
回到SelectorImpl的register方法,在implRegister方法結束後,呼叫SelectionKeyImpl的interestOps方法,前面說過的,在此時設定了事件響應,最後返回SelectionKeyImpl物件賦給AbstractSelectableChannel方法中的k,之後呼叫addKey方法,返回k,register方法呼叫全部結束。
addKey方法:
1 private void addKey(SelectionKey k) { 2 assert Thread.holdsLock(keyLock); 3 int i = 0; 4 if ((keys != null) && (keyCount < keys.length)) { 5 // Find empty element of key array 6 for (i = 0; i < keys.length; i++) 7 if (keys[i] == null) 8 break; 9 } else if (keys == null) { 10 keys = new SelectionKey[3]; 11 } else { 12 // Grow key array 13 int n = keys.length * 2; 14 SelectionKey[] ks = new SelectionKey[n]; 15 for (i = 0; i < keys.length; i++) 16 ks[i] = keys[i]; 17 keys = ks; 18 i = keyCount; 19 } 20 keys[i] = k; 21 keyCount++; 22 }
邏輯很清晰,首先檢查有沒有沒有使用的key,若存在,直接用k覆蓋結束;若keys沒有初始化大小為3的陣列,先初始化keys,再將k放在下標為0的位置結束;若是keys已經初始化且keyCount == keys.length,就需要給keys擴容,並將原來的元素拷貝,最後將k放在新keys下標為keyCount的位置。
Channel的註冊到此全部結束。