netty 源碼分析二

來源:互聯網
上載者:User

標籤: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());        }    }



聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.