NIO Select Knowledge
select sample code:
// create a channel and set to a non-blocking ServerSocketChannel ServerChannel = ServerSocketChannel.open (); serverChannel.configureBlocking (false); serverChannel.socket () the bind (new new InetSocketAddress (Port));. // Open Selector Selector Selector = Selector.open (); // Register the Selector serverChannel.register (Selector, SelectionKey.OP_ACCEPT); // loop through or to, is the main () method to find SelectionKey by select, using the event monitor what type of treatment response // where needed Note that the registration method two the while (to true) { int n-the selector.select = (); IF (n-== 0) Continue; the Iterator ITE = this.selector.selectedKeys () Iterator ();. the while (ite.hasNext ( )) { the SelectionKey Key = (the SelectionKey) ite.next (); IF (key.isAcceptable ()) { = ClntChan the SocketChannel ((a ServerSocketChannel) key.channel ()) Accept ();. ClntChan.configureBlocking (to false); // register selector to the client connected to the channel, and // specifies attributes of the key value for the channel OP_READ, // specify for the associated accessory channel clntChan.register (key.selector (), SelectionKey.OP_READ, ByteBuffer.allocate (bufSize)); } IF (key.isReadable ()) { handleRead (Key); } IF (key.isWritable () && key.isValid ()) { handleWrite (Key); } IF (key.isConnectable ()) { System.out.println ( "to true isConnectable ="); } ite.remove (); } }
Source Reading
By the previous article we know, netty is actually made up of two Reactor, the former Acceptor maintain a binding interface that handles the connection of the client, and then read and write, decoding and encoding work to another Reactor, let's look what such a process, understand the overall process and then understand the details of the system logic implementation.
Two classes of components:
AbstractEventExecutorGroup
- MultithreadEventExecuteGroup
- MultithreadEventLoopGroup
- NioEventLoopGroup
- MultithreadEventLoopGroup
AbstractChannel
- AbstractNioChannel
- AbstractNioByteChannel
- NioSocketChannel
- AbstractNioByteChannel
Important categories:
- NioServerSocketChannel: server port connecting the channel
- ChannelPipeline: Handler store chain
- NioEventLoop: SingleThreadEventLoop implementation which register the Channel's to a Selector and so does the multi-plexing of these in the event loop is responsible for reading the network connection and client requests access Reactor thread is NioEventLoop
We look directly bind method, AbstractBootstrap class
/** * Create a new {@link Channel} and bind it. */ public ChannelFuture bind(String inetHost, int inetPort) { return bind(new InetSocketAddress(inetHost, inetPort)); } /** * Create a new {@link Channel} and bind it. */ public ChannelFuture bind(SocketAddress localAddress) { validate(); if (localAddress == null) { throw new NullPointerException("localAddress"); } return doBind(localAddress); } private ChannelFuture doBind(final SocketAddress localAddress) { final ChannelFuture regFuture = initAndRegister(); final Channel channel = regFuture.channel(); if (regFuture.cause() != null) { return regFuture; } final ChannelPromise promise; if (regFuture.isDone()) { promise = channel.newPromise(); doBind0(regFuture, channel, localAddress, promise); } else { // Registration future is almost always fulfilled already, but just in case it's not. promise = new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE); regFuture.addListener(new ChannelFutureListener() { @Override void operationComplete public (ChannelFuture Future) throws Exception { doBind0 (regFuture, Channel, localAddress is, Promise); } }); } return Promise; } / ** * * * * / Final ChannelFuture initAndRegister () { Channel Channel; the try { / / NO.1 Acceptor listening port binding thread waiting for the connection from the client Channel createChannel = (); } the catch (the Throwable T) { return VoidChannel.INSTANCE.newFailedFuture (T); } the try { // No.2 附加属性,添加 Handler init(channel); } catch (Throwable t) { channel.unsafe().closeForcibly(); return channel.newFailedFuture(t); } ChannelPromise regFuture = channel.newPromise(); // No.3 registed 注册连接事件 channel.unsafe().register(regFuture); if (regFuture.cause() != null) { if (channel.isRegistered()) { channel.close(); } else { channel.unsafe().closeForcibly(); } } // If we are here and the promise is not failed, it's one of the following cases: // 1) If we attempted registration from the event loop, the registration has been completed at this point. // i.e. It's safe to attempt bind() or connect() now beause the channel has been registered. // 2) If we attempted registration from the other thread, the registration request has been successfully // added to the event loop's task queue for later execution. // i.e. It's safe to attempt bind() or connect() now: // because bind() or connect() will be executed *after* the scheduled registration task is executed // because register(), bind(), and connect() are all bound to the same thread. return regFuture; }
init method
@Override void init(Channel channel) throws Exception { final Map<ChannelOption<?>, Object> options = options(); synchronized (options) { channel.config().setOptions(options); } final Map<AttributeKey<?>, Object> attrs = attrs(); synchronized (attrs) { for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) { @SuppressWarnings("unchecked") AttributeKey<Object> key = (AttributeKey<Object>) e.getKey(); channel.attr(key).set(e.getValue()); // ChannelPipeline following a list storing the internal list are put handler, p is the parent class ChannelPipeline } } // child following ChannelPipeline is child Reactor, where there will be no judge would have added to the list handler (handle () method we begin with the case), if not // the tail of this method will want to add a ServerBootstrapAcceptor as default list node // that childHandler where the handler to join it? In ServerBootstrapAcceptor, we will introduce follow the ChannelPipeline channel.pipeline P = (); IF (! Handler () = null) { p.addLast (Handler ()); } Final currentChildHandler of ChannelHandler = childHandler; Final the Entry <ChannelOption <>? , Object> [] currentChildOptions; Final the Entry <? AttributeKey <>, Object> [] currentChildAttrs; the synchronized (childOptions) { currentChildOptions = childOptions.entrySet () toArray (newOptionArray (childOptions.size ())).; } The synchronized (childAttrs) { currentChildAttrs = childAttrs.entrySet () toArray (newAttrArray (childAttrs.size ()));. } // Note here: ServerBootstrapAcceptor pipleline placed in // performs when receiving a subsequent client request the method of pipleline, specifically in // NioMessageUnsafe read method p.addLast (new new ChannelInitializer <Channel> () { @Override public void initChannel (CH Channel) throws Exception { ch.pipeline (). addLast (new new ServerBootstrapAcceptor ( currentChildHandler, currentChildOptions, currentChildAttrs)); } }); }
register method
// is not judged to be self-initiated action NioEventLoop, if there is no concurrency issues directly Channel Register // if it is initiated by another thread, then the package was placed into a message queue Task asynchronously. Because here is where ServerBootstrap // Register the operation thread of execution, all should be packaged as Task delivered to NioEventLoop executed. @Override public void Final Register (ChannelPromise Final Promise) { IF (eventLoop.inEventLoop ()) { register0 (Promise); } {the else the try { eventLoop.execute (the Runnable new new () { @Override public void RUN () { register0 ( Promise); } }); } catch (Throwable t) { logger.warn( "Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, t); closeForcibly(); closeFuture.setClosed(); promise.setFailure(t); } } } private void register0(ChannelPromise promise) { try { // check if the channel is still open as it could be closed in the mean time when the register // call was outside of the eventLoop (! ensureOpen (Promise)) IF { return; } // Register logic Nos. 1 the doRegister (); Registered = to true; promise.setSuccess (); after // No.2 successfully registered, in circulation and TailHandler channelPiple in the HeaderHandler (Note: the internal selectKey registered as 0, not OP_ACCEPT, will modify later) pipeline.fireChannelRegistered (); // channelPiple No. 3 in circulation in the HeaderHandler and TailHandler, HeaderHandler the read method calls // selectKey amended as OP_ACCEPT IF (isActive ()) { pipeline.fireChannelActive (); } } catch (Throwable t) { // Close the channel directly to avoid FD leak. closeForcibly(); closeFuture.setClosed(); if (!promise.tryFailure(t)) { logger.warn( "Tried to fail the registration promise, but it is complete already. " + "Swallowing the cause of the registration failure:", t); } } }
initAndRegisted method, we can know from the next major method of operation of the process is to initialize and registration, is above No.1 2 3 is operating on the channel. createChannel method ServerBootstrap this class
@Override Channel createChannel () { // Next method retrieves a thread to do the work of connecting EventLoop eventLoop = Group () Next ();. Return ChannelFactory () newChannel (eventLoop, childGroup);. }
Method MultithreadEventExecuteGroup next class, group () is returned bossGroup, a method for obtaining its next available thread from the thread group
@Override public EventExecutor next() { return children[Math.abs(childIndex.getAndIncrement() % children.length)]; }
NioServerSocketChannel by createChannel () method of reflection is created, while registering an event connected
/** * Create a new instance */ public NioServerSocketChannel(EventLoop eventLoop, EventLoopGroup childGroup) { super(null, eventLoop, childGroup, newSocket(), SelectionKey.OP_ACCEPT); config = new DefaultServerSocketChannelConfig(this, javaChannel().socket()); }
Join the server has done the join operation with the client, then the next step should be to the IO operation should reach read and write process (create a new thread for processing) workGroup of Reactor, the server processing NioEventLoop class run the method,
NioEventLoop run method
@Override protected void run() { for (;;) { oldWakenUp = wakenUp.getAndSet(false); try { if (hasTasks()) { selectNow(); } else { select(); // 'wakenUp.compareAndSet(false, true)' is always evaluated // before calling 'selector.wakeup()' to reduce the wake-up // overhead. (Selector.wakeup() is an expensive operation.) // // However, there is a race condition in this approach. // The race condition is triggered when 'wakenUp' is set to // true too early. // // 'wakenUp' is set to true too early if: // 1) Selector is waken up between 'wakenUp.set(false)' and // 'selector.select(...)'. (BAD) // 2) Selector is waken up between 'selector.select(...)' and // 'if (wakenUp.get()) { ... }'. (OK) // // In the first case, 'wakenUp' is set to true and the // following 'selector.select(...)' will wake up immediately. // Until 'wakenUp' is set to false again in the next round, // 'wakenUp.compareAndSet(false, true)' will fail, and therefore // any attempt to wake up the Selector will fail, too, causing // the following 'selector.select(...)' call to block // unnecessarily. // // To fix this problem, we wake up the selector again if wakenUp // is true immediately after selector.select(...). // It is inefficient in that it wakes up the selector for both // the first case (BAD - wake-up required) and the second case // (OK - no wake-up required). if (wakenUp.get()) { selector.wakeup(); } } cancelledKeys = 0; final long ioStartTime = System.nanoTime(); needsToSelectAgain = false; if (selectedKeys != null) { processSelectedKeysOptimized(selectedKeys.flip()); } else { processSelectedKeysPlain(selector.selectedKeys()); } final long ioTime = System.nanoTime() - ioStartTime; final int ioRatio = this.ioRatio; runAllTasks(ioTime * (100 - ioRatio) / ioRatio); if (isShuttingDown()) { closeAll(); if (confirmShutdown()) { break; } } } catch (Throwable t) { logger.warn("Unexpected exception in the selector loop.", t); // Prevent possible consecutive immediate failures that lead to // excessive CPU consumption. try { Thread.sleep(1000); } catch (InterruptedException e) { // Ignore. } } } } // Whether or processSelectedKeysPlain processSelectedKeysOptimized method has the following method after // you can see what is listening to is select the method of read and write operations. static void processSelectedKey Private (the SelectionKey K, AbstractNioChannel CH) { Final NioUnsafe the unsafe ch.unsafe = (); IF (! k.isValid ()) { // Close Channel The IF IS Not Valid The Key anymore unsafe.close (the unsafe. voidPromise ()); return; } the try { int k.readyOps the readyOps = (); // Check for Also readOps Possible workaround of 0 to Which the JDK bugs otherwise Lead On May // to a spin loop if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { unsafe.read(); if (!ch.isOpen()) { // Connection already closed - no need to handle write. return; } } if ((readyOps & SelectionKey.OP_WRITE) != 0) { // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write ch.unsafe().forceFlush(); } if ((readyOps & SelectionKey.OP_CONNECT) != 0) { // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking // See https://github.com/netty/netty/issues/924 int ops = k.interestOps(); ops &= ~SelectionKey.OP_CONNECT; k.interestOps(ops); unsafe.finishConnect(); } } catch (CancelledKeyException e) { unsafe.close(unsafe.voidPromise()); } }
If the event is read, the call unsafe.read () method, will come AbstractNioMessageChannel inner class NioMessageUnsafe read method
public abstract class AbstractNioMessageChannel extends AbstractNioChannel { protected AbstractNioMessageChannel( Channel parent, EventLoop eventLoop, SelectableChannel ch, int readInterestOp) { super(parent, eventLoop, ch, readInterestOp); } @Override protected AbstractNioUnsafe newUnsafe() { return new NioMessageUnsafe(); } private final class NioMessageUnsafe extends AbstractNioUnsafe { private final List<Object> readBuf = new ArrayList<Object>(); private void removeReadOp() { SelectionKey key = selectionKey(); int interestOps = key.interestOps(); if ((interestOps & readInterestOp) != 0) { // only remove readInterestOp if needed key.interestOps(interestOps & ~readInterestOp); } } @Override public void read() { assert eventLoop().inEventLoop(); if (!config().isAutoRead()) { removeReadOp(); } final ChannelConfig config = config(); final int maxMessagesPerRead = config.getMaxMessagesPerRead(); final boolean autoRead = config.isAutoRead(); final ChannelPipeline pipeline = pipeline(); boolean closed = false; Throwable exception = null; // 这里有个循环 try { for (;;) { // No.1 int localRead = doReadMessages(readBuf); if (localRead == 0) { break; } if (localRead < 0) { closed = true; break; } if (readBuf.size() >= maxMessagesPerRead | !autoRead) { break; } } } catch (Throwable t) { exception = t; } int size = readBuf.size(); for (int i = 0; i < size; i ++) { // No.2 调用 pipleline里的方法 pipeline.fireChannelRead(readBuf.get(i)); } readBuf.clear(); pipeline.fireChannelReadComplete(); if (exception != null) { if (exception instanceof IOException) { // ServerChannel should not be closed even on IOException because it can often continue // accepting incoming connections. (e.g. too many open files) closed = !(AbstractNioMessageChannel.this instanceof ServerChannel); } pipeline.fireExceptionCaught(exception); } if (closed) { if (isOpen()) { close(voidPromise()); } } } } ... ...
And finally to the doReadMessages method of NioServerSocketChannel.
@Override protected int doReadMessages (List <Object> buf) throws Exception { the SocketChannel CH = javaChannel () Accept ();. The try { IF (CH = null!) { // Open childGroup Reactor in a thread so that read and write operations, and passed into the array NioSocketChannel buf.add (new new NioSocketChannel (the this, childEventLoopGroup () Next (), CH).); return. 1; } } the catch (the Throwable T) { logger.warn ( "the Failed to Create AN accepted from new new Channel A Socket ", T);. the try { ch.close (); } the catch (the Throwable T2) { logger.warn (" the Failed to Close Socket A ", T2).; } } return 0; }
doReadMessages 执行结束后我们会继续继续执行 No.2 处的代码,执行 pipleline 中放入对象的的方法,又上一篇可以知道将会执行
ServerBootstrapAcceptor的 channnelRead 方法
@Override @SuppressWarnings ( "an unchecked") public void channelRead (ChannelHandlerContext CTX, Object MSG) { Channel Child = (Channel) MSG; childHandler ChannelPiple added to the client's SocketChannel Nos. 1 starts when // // look back examples of what we started, this is the place to join childHandler child.pipeline () addLast (childHandler);. // set No. 2 SocketChannel client's TCP parameters for (Entry <ChannelOption <>, Object?> e: childOptions) { the try { IF (child.config () the setOption ((ChannelOption <Object>) e.getKey (), e.getValue ())!.) { logger.warn ( "Unknown Channel Option:" + E); } } catch (Throwable t) { logger.warn("Failed to set a channel option: " + child, t); } } for (Entry<AttributeKey<?>, Object> e: childAttrs) { child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue()); } // No.3 child.unsafe().register(child.newPromise()); }
Reference material
- http://www.blogjava.net/DLevin/archive/2015/09/02/427045.html (Reator model)
- https://www.jianshu.com/p/052035037297
- https://www.jianshu.com/p/0d497fe5484a