上篇博文分析了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,得到其通道是否准备好(猜的);