標籤:netty java nio
以服務端啟動,接收用戶端串連整個過程為例分析, 簡略分為 五個過程:
1.NioServerSocketChannel 管道產生,
2.NioServerSocketChannel 管道完成初始化,
3.NioServerSocketChannel註冊至Selector選取器,
4.NioServerSocketChannel管道綁定到指定連接埠,啟動服務
5.NioServerSocketChannel接受用戶端的串連,進行相應IO操作
Ps:netty內部過程遠比這複雜,簡略記錄下方便以後回憶對整個流程的把控.
管道產生調用NioServerSocketChannel類的如下構造方法:
/** * Create a new instance */ public NioServerSocketChannel(EventLoop eventLoop, EventLoopGroup childGroup) { super(null, eventLoop, childGroup, newSocket(), SelectionKey.OP_ACCEPT); config = new DefaultServerSocketChannelConfig(this, javaChannel().socket()); }由ServerBootStrap 這個啟動工具類負責建立, 調用其內部類ServerBootstrapChannelFactory的newChannel()方法完成.
@Override Channel createChannel() { EventLoop eventLoop = group().next(); return channelFactory().newChannel(eventLoop, childGroup); }
構造方法需要傳入兩個參數, EventLoop , EventLoopGroup .
EventLoop 內建單個線程池,主要負責 輪詢selector 這個選取器擷取準備就緒的channel管道,並交給EventLoopGroup進行讀寫操作
EventLoopGroup 內建多個線程池,負責處理IO讀寫操作.
管道初始化主要為NioServerSocketChannel配置一些可選option,attrs屬性, 同時向ChannelPipeline類中添加ServerBootstrapAcceptor 處理器,代碼如下:
@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 p = channel.pipeline(); if (handler() != null) { p.addLast(handler()); } final ChannelHandler currentChildHandler = childHandler; final Entry<ChannelOption<?>, Object>[] currentChildOptions; final Entry<AttributeKey<?>, Object>[] currentChildAttrs; synchronized (childOptions) { currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size())); } synchronized (childAttrs) { currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size())); } p.addLast(new ChannelInitializer<Channel>() { @Override public void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(new ServerBootstrapAcceptor(currentChildHandler, currentChildOptions, currentChildAttrs)); } }); }
netty是基於java的NIo開發的,所以NioServerSocketChannel管道註冊類似NIO的註冊,主要向Selector這個選取器完成註冊.ServerBootstrap啟動類中註冊代碼就如下一行:
channel.unsafe().register(regFuture);
由NioServerSocketChannel內部類unsafe(抽象實作類別AbstractUnsafe)完成註冊,代碼如下:
@Override public final void register(final ChannelPromise promise) { if (eventLoop.inEventLoop()) { register0(promise); } else { try { eventLoop.execute(new Runnable() { @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 if (!ensureOpen(promise)) { return; } doRegister(); registered = true; promise.setSuccess(); pipeline.fireChannelRegistered(); 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); } } }
register(final ChannelPromise promise)方法中程式碼片段
eventLoop.execute(new Runnable() {...}
execute()方法首先會啟動 EventLoop 線程池不斷輪詢Selector, 然後先線程池內部丟一個task進去,內部代碼不在展開.
register0()方法中doRegister()方法如下,本質就是通過NIo的ServersocketChannel完成註冊:
@Override protected void doRegister() throws Exception { boolean selected = false; for (;;) { try { selectionKey = javaChannel().register(eventLoop().selector, 0, this); return; } catch (CancelledKeyException e) { if (!selected) { // Force the Selector to select now as the "canceled" SelectionKey may still be // cached and not removed because no Select.select(..) operation was called yet. eventLoop().selectNow(); selected = true; } else { // We forced a select operation on the selector before but the SelectionKey is still cached // for whatever reason. JDK bug ? throw e; } } } }
NioServerSocketChannel管道建立,初始化,註冊完畢之後就需要綁定到指定連接埠以提供服務.核心代碼在AbstractBootstrap類的doBind0()方法中如下:
private static void doBind0( final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) { // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up // the pipeline in its channelRegistered() implementation. channel.eventLoop().execute(new Runnable() { @Override public void run() { if (regFuture.isSuccess()) { channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); } else { promise.setFailure(regFuture.cause()); } } }); }
channel.eventLoop()用於擷取到建立管道時傳入的 EventLoop線程池(線程已經在註冊時候啟動),然後向線程池內部放入一個綁定連接埠任務.
channel.bind()內部實現代碼如下:
@Override public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { return pipeline.bind(localAddress, promise); }通過DefaultChannelPipeline類的bind()方法執行,DefaultChannelPipeline內部實現如下,:
@Override public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { return tail.bind(localAddress, promise); }
每個DefaultChannelPipeline類內部都會維護著一些DefaultChannelHandlerContext, 可以通過addXXX()方法往DefaultChannelPipeline類裡面增加DefaultChannelHandlerContext,每個DefaultChannelHandlerContext裡面都會維護這個一個handler,用於後期invoke該handler.
tail 是 DefaultChannelPipeline內部最後一個DefaultChannelHandlerContext, bind方法內部實現如下:
@Override public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) { DefaultChannelHandlerContext next = findContextOutbound(MASK_BIND); next.invoker.invokeBind(next, localAddress, promise); return promise; }
private DefaultChannelHandlerContext findContextOutbound(int mask) { DefaultChannelHandlerContext ctx = this; do { ctx = ctx.prev; } while ((ctx.skipFlags & mask) != 0); return ctx; }
tail作為DefaultChannelPipeline內部最後一個DefaultChannelHandlerContext,會一直先前遍曆,直到找到某個DefaultChannelHandlerContext內部的handlerA實現了bind()方法,然後找到該類內部ChannelHandlerInvoker執行個體實現者(DefaultChannelHandlerInvoker), 並調用invokeBind()方法, 該方法本質上是通過handlerA的bind()方法結束操作.
服務現在已經起來,等待用戶端串連,並讀取用戶端資料.
EventLoop線程池在註冊時已經啟動,已經能夠接受用戶端的資訊.主要代碼在NioEventLoop類的run()方法中.如下:
@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. } } } }
主要方法processSelectedKeysPlain()代碼如下:
private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) { // check if the set is empty and if so just return to not create garbage by // creating a new Iterator every time even if there is nothing to process. // See https://github.com/netty/netty/issues/597 if (selectedKeys.isEmpty()) { return; } Iterator<SelectionKey> i = selectedKeys.iterator(); for (;;) { final SelectionKey k = i.next(); final Object a = k.attachment(); i.remove(); if (a instanceof AbstractNioChannel) { processSelectedKey(k, (AbstractNioChannel) a); } else { @SuppressWarnings("unchecked") NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a; processSelectedKey(k, task); } if (!i.hasNext()) { break; } if (needsToSelectAgain) { selectAgain(); selectedKeys = selector.selectedKeys(); // Create the iterator again to avoid ConcurrentModificationException if (selectedKeys.isEmpty()) { break; } else { i = selectedKeys.iterator(); } } } }
上面代碼有兩句還沒明白原因,請教中.
final SelectionKey k = i.next();
final Object a = k.attachment(); //這個attachment()值啥時候被放進去的?
接著看代碼,processSelectedKey方法內部主要實現對感興趣事件的業務操作,比如讀取資料操作,大致的操作流程其實跟連接埠綁定的流程類似.
private static void processSelectedKey(SelectionKey k, AbstractNioChannel ch) { final NioUnsafe unsafe = ch.unsafe(); if (!k.isValid()) { // close the channel if the key is not valid anymore unsafe.close(unsafe.voidPromise()); return; } try { int readyOps = k.readyOps(); // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead // 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()); } }