上篇博文分析了selector的建立,在這之後selector跟ServerSocketChannel,SocketChannel配合使用,在這裏我將會介紹在selector.open();之後,我們以服務器端爲切入點,重點關注下面兩個方法的實現
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.register(selector, SelectionKeyImpl.OP_ACCEPT);
先看ServerSocket.open();
public static ServerSocketChannel open() throws IOException {
return SelectorProvider.provider().openServerSocketChannel();
}
provider這個我們很熟悉了吧?默認就是WindowsSelectorProvider的實例,繼續。它的openServerSocketChannel方法的實現在它父類SelectorProviderImpl中。
public ServerSocketChannel openServerSocketChannel() throws IOException {
return new ServerSocketChannelImpl(this);
}
其實這裏我們都分析過,在創建Pipe時有用到這。既然來了就繼續看下構造。 ServerSocketChannelImpl(SelectorProvider var1) throws IOException {
super(var1);
this.fd = Net.serverSocket(true);
this.fdVal = IOUtil.fdVal(this.fd);
this.state = 0;
}
裏面無非構造了socket,並保存其fd。我們可以知道serverChannel=ServerSocketChannel.open();其實是指向其子類ServerSocketChannelImpl的實例,裏面保存了socket實例,跟fd。繼續。
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(port));
我們使用serverChannel時設置其blocking爲非阻塞,併爲其serverSocket綁定一個ip地址跟端口。我們重點看下第二行,先看ServerSocketChannelImpl的.socket() public ServerSocket socket() {
Object var1 = this.stateLock;
synchronized(this.stateLock) {
if(this.socket == null) {
this.socket = ServerSocketAdaptor.create(this);
}
return this.socket;
}
}
socket是ServerSocktAdaptor(門面模式,他無非是ServerSocketChannelImpl的門面)的實例,利用同步保證其多線程安全且單例,其內部bind方法還是調用ServerSocketChannelImpl的bind方法。 public ServerSocketChannel bind(SocketAddress var1, int var2) throws IOException {
Object var3 = this.lock;
synchronized(this.lock) {
if(!this.isOpen()) {
throw new ClosedChannelException();
} else if(this.isBound()) {
throw new AlreadyBoundException();
} else {
InetSocketAddress var4 = var1 == null?new InetSocketAddress(0):Net.checkAddress(var1);
SecurityManager var5 = System.getSecurityManager();
if(var5 != null) {
var5.checkListen(var4.getPort());
}
NetHooks.beforeTcpBind(this.fd, var4.getAddress(), var4.getPort());
Net.bind(this.fd, var4.getAddress(), var4.getPort());
Net.listen(this.fd, var2 < 1?50:var2);
Object var6 = this.stateLock;
synchronized(this.stateLock) {
this.localAddress = Net.localAddress(this.fd);
}
return this;
}
}
}
無非先驗證channel是否打開,是否綁定過,最終調用Net底層的bind方法爲其綁定端口跟地址。 private static native void bind0(FileDescriptor var0, boolean var1, boolean var2,
InetAddress var3, int var4) throws IOException;
總的來說,就是將 ServerSocketChannel 中的 ServerSocket綁定到指定的IP地址和端口上。我們繼續往下看這次的重點,channel.register(Selector sel, int ops,Object att),其具體邏輯在AbstractSelectableChannel上
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
synchronized (regLock) {
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
if (blocking)
throw new IllegalBlockingModeException();
SelectionKey k = findKey(sel);
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
if (k == null) {
// New registration
synchronized (keyLock) {
if (!isOpen())
throw new ClosedChannelException();
k = ((AbstractSelector)sel).register(this, ops, att);
addKey(k);
}
}
return k;
}
}
我們一點點看,if (blocking)throw new IllegalBlockingModeException();(在這裏,我們能看到爲什麼之前需要設置其blocking爲非阻塞)。在channel中,有維護一個SelectionKey的數組,一個SelectionKey代表了一個channel跟一個selectord的註冊關係。一開始現在keys中找,判斷這個channel是否註冊過該Selector,如果這個channel是第一次註冊這個Selector,那麼肯定找不到,邏輯很簡單。 private SelectionKey findKey(Selector sel) {
synchronized (keyLock) {
if (keys == null)
return null;
for (int i = 0; i < keys.length; i++)
if ((keys[i] != null) && (keys[i].selector() == sel))
return keys[i];
return null;
}
}
如果一開始就找到了SelectorKey,那麼只需要更新interestops跟attach。如果沒有找到SelectorKey,那就需要對傳進來的Selector進行註冊。Selector的regist()方法實現在SelectorImpl中
protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) {
if(!(var1 instanceof SelChImpl)) {
throw new IllegalSelectorException();
} else {
SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this);
var4.attach(var3);
Set var5 = this.publicKeys;
synchronized(this.publicKeys) {
this.implRegister(var4);
}
var4.interestOps(var2);
return var4;
}
}
首先根據channel跟selector生成相應的SelectorKeyIml實例,其構造方法無非對傳入的參數保存,然後把所需的attach實例保存至SelectorKey。然後調用子類的implRegister()並傳入SelectorKeyIml實例。具體實現我們以windowsSelectorImpl爲例子 protected void implRegister(SelectionKeyImpl var1) {
Object var2 = this.closeLock;
synchronized(this.closeLock) {
if(this.pollWrapper == null) {
throw new ClosedSelectorException();
} else {
this.growIfNeeded();
this.channelArray[this.totalChannels] = var1;
var1.setIndex(this.totalChannels);
this.fdMap.put(var1);
this.keys.add(var1);
this.pollWrapper.addEntry(this.totalChannels, var1);
++this.totalChannels;
}
}
}
這裏對傳入的SelectorKeyIml繼續處理,以實現註冊。
由於傳入新的key,所以可能導致原來數組長度不夠,如果需要擴容則擴容。
private void growIfNeeded() {
if(this.channelArray.length == this.totalChannels) {
int var1 = this.totalChannels * 2;
SelectionKeyImpl[] var2 = new SelectionKeyImpl[var1];
System.arraycopy(this.channelArray, 1, var2, 1, this.totalChannels - 1);
this.channelArray = var2;
this.pollWrapper.grow(var1);
}
if(this.totalChannels % 1024 == 0) {
this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, this.totalChannels);
++this.totalChannels;
++this.threadsCount;
}
}
擴容則把數組(channelArray,poolArrayWrapper)長度擴大爲原來的兩倍。
然後把按照已經註冊的channel的數量爲下標將SelectorKeyIml(對於Selector來說SelectorKeyIml相當於封裝了的channel)存在存channel的數組中,然後在SelectorKeyIml中記錄channel註冊在Selector中的位置索引,通過fdMap保存SelectorKeyIml
private WindowsSelectorImpl.MapEntry put(SelectionKeyImpl var1) {
return (WindowsSelectorImpl.MapEntry)this.put(new Integer(var1.channel.getFDVal()),
new WindowsSelectorImpl.MapEntry(var1));
}
可以看到,fdMap以channel的fdVal爲key,與具體的SelectionKey爲值的鍵值對。最後想poolWrapper中添加映射,並自增totalchannels的數目。 void addEntry(int var1, SelectionKeyImpl var2) {
this.putDescriptor(var1, var2.channel.getFDVal());
}
在調用Selector的register後返回SelectorKey給channel,然後回到AbstractSelectableChannel中,最後調用addKey(k);
private void addKey(SelectionKey k) {
assert Thread.holdsLock(keyLock);
int i = 0;
if ((keys != null) && (keyCount < keys.length)) {
// Find empty element of key array
for (i = 0; i < keys.length; i++)
if (keys[i] == null)
break;
} else if (keys == null) {
keys = new SelectionKey[3];
} else {
// Grow key array
int n = keys.length * 2;
SelectionKey[] ks = new SelectionKey[n];
for (i = 0; i < keys.length; i++)
ks[i] = keys[i];
keys = ks;
i = keyCount;
}
keys[i] = k;
keyCount++;
}
在這如果keys大小ok,那麼添加key至數組中,如果大小不夠,擴容。最後return k到,完畢。
總結一下吧。其實使用channel.register(selector,ops)語句,channel.registe -> AbstractSelectableChannel.regist -> windowsSelectorimpl.implRegister();將channel註冊到指定的selector上,實際上是將channel內部的socket的fd保存到pollWrapper的數組上,那麼我們大概能猜到selector.select()方法實現的邏輯就是遍歷這個pollWrapper,查看其fd,得到其通道是否準備好(猜的);