第 10 章 Netty 核心源碼剖析①

Netty 啓動過程源碼剖析
Netty 接受請求過程源碼剖析

Pipeline Handler HandlerContext 創建源碼剖析
ChannelPipeline 調度 handler 的源碼剖析
Netty 心跳(heartbeat)服務源碼剖析
Netty 核心組件 EventLoop 源碼剖析
handler 中加入線程池和 Context 中添加線程池的源碼剖析
在這裏插入圖片描述

//在 io.netty.example 包下,有很多Netty源碼案例,可以用來分析

/** Server
 * Echoes back any received data from a client.
 */
public final class EchoServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // Configure the server. 1111
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
        //2222
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     p.addLast(new LoggingHandler(LogLevel.INFO));
                     //p.addLast(new EchoServerHandler());
                 }
             });

            // Start the server. 3333
            ChannelFuture f = b.bind(PORT).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

10.1 Netty 啓動過程源碼剖析

在這裏插入圖片描述

說明:

1)先看啓動類:main 方法中,首先創建了關於SSL 的配置類。

2)重點分析下 創建了兩個EventLoopGroup 對象:

    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    EventLoopGroup workerGroup = new NioEventLoopGroup();

(1) 這兩個對象是整個 Netty 的核心對象,可以說,整個 Netty 的運作都依賴於他們。bossGroup 用於接受 Tcp 請求,他會將請求交給 workerGroup ,workerGroup 會獲取到真正的連接,然後和連接進行通信,比如讀寫解碼編碼等操作。

(2) EventLoopGroup 是 事件循環組(線程組) 含有多個 EventLoop, 可以註冊channel ,用於在事件循環中去進行選擇(和選擇器相關).。
在這裏插入圖片描述

(3) new NioEventLoopGroup(1); 這個1 表示 bossGroup 事件組有1個線程你可以指定,如果 new NioEventLoopGroup() 會含有默認個線程 cpu核數*2, 即可以充分的利用多核的優勢

 DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
 "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

會創建EventExecutor 數組 children = new EventExecutor[nThreads];

每個元素的類型就是 NIOEventLoop, NIOEventLoop 實現了 EventLoop 接口 和 Executor 接口
在這裏插入圖片描述
try 塊中創建了一個 ServerBootstrap 對象,他是一個引導類,用於啓動服務器和引導整個程序的初始化。它和 ServerChannel 關聯, 而 ServerChannel 繼承了 Channel,有一些方法 remoteAddress等。 隨後,變量 b 調用了 group 方法將兩個 group 放入了自己的字段中,用於後期引導使用
在這裏插入圖片描述
(4) 然後添加了一個 channel,其中參數一個Class對象,引導類將通過這個 Class 對象反射創建 ChannelFactory。然後添加了一些TCP的參數。[說明:Channel 的創建在bind 方法 ,會找到channel = channelFactory.newChannel(); ]
在這裏插入圖片描述
(5) 再添加了一個服務器專屬的日誌處理器 handler。
在這裏插入圖片描述
(6)再添加一個 SocketChannel(不是 ServerSocketChannel)的 handler。

(7)然後綁定端口並阻塞至連接成功。

(8)最後main線程阻塞等待關閉。

(9)finally 塊中的代碼將在服務器關閉時優雅關閉所有資源

分析 MultithreadEventExecutorGroup

參數說明:
@param nThreads         使用的線程數,默認爲 core *2 [可以追蹤源碼]
@param executor         執行器:如果傳入null,則採用Netty默認的線程工廠和默認的執行器ThreadPerTaskExecutor
@param chooserFactory   單例new DefaultEventExecutorChooserFactory()
@param args            args 在創建執行器的時候傳入固定參數 
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
      EventExecutorChooserFactory chooserFactory, Object... args) {
        if (nThreads <= 0) { //
            throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
        }

        if (executor == null) { //如果傳入的執行器是空的則採用默認的線程工廠和默認的執行器
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }
        //創建指定線程數的執行器數組
        children = new EventExecutor[nThreads];
        //初始化線程數組
        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            try {
                // 創建 new NioEventLoop
                children[i] = newChild(executor, args);
                success = true;
            } catch (Exception e) {
                // TODO: Think about if this is a good exception type
                throw new IllegalStateException("failed to create a child event loop", e);
            } finally {
                // 如果創建失敗,優雅關閉
                if (!success) {
                    for (int j = 0; j < i; j ++) {
                        children[j].shutdownGracefully();
                    }

                    for (int j = 0; j < i; j ++) {
                        EventExecutor e = children[j];
                        try {
                            while (!e.isTerminated()) {
                                e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException interrupted) {
                            // Let the caller handle the interruption.
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }
        }

        chooser = chooserFactory.newChooser(children);

        final FutureListener<Object> terminationListener = new FutureListener<Object>() {
            @Override
            public void operationComplete(Future<Object> future) throws Exception {
                if (terminatedChildren.incrementAndGet() == children.length) {
                    terminationFuture.setSuccess(null);
                }
            }
        };

        //爲每一個單例線程池添加一個關閉監聽器
        for (EventExecutor e: children) {
            e.terminationFuture().addListener(terminationListener);
        }

        Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
        //將所有的單例線程池添加到一個 HashSet 中。
        Collections.addAll(childrenSet, children);
        readonlyChildren = Collections.unmodifiableSet(childrenSet);
    }
    
說明:
1)如果 executor 是null,創建一個默認的 ThreadPerTaskExecutor,使用 Netty 默認的線程工廠。
2)根據傳入的線程數(CPU*2)創建一個線程池(單例線程池)數組。
3)循環填充數組中的元素。如果異常,則關閉所有的單例線程池。
4)根據線程選擇工廠創建一個 線程選擇器。
5)爲每一個單例線程池添加一個關閉監聽器。將所有的單例線程池添加到一個 HashSet 中。

ServerBootstrap 創建和構造過程

ServerBootstrap 是個空構造,但是有默認的成員變量
在這裏插入圖片描述
分析一下 ServerBootstrap 基本使用情況

ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new EchoServerHandler());
                 }
             });
             
說明:
1)鏈式調用:group方法,將boss和worker傳入,boss 賦值給parentGroup屬性,worker 賦值給 childGroup 屬性
2)channel 方法傳入 NioServerSocketChannel class 對象。會根據這個 class 創建 channel 對象。
3)option 方法傳入 TCP 參數,放在一個LinkedHashMap 中。
4)handler 方法傳入一個 handler 中,這個hanlder 只專屬於 ServerSocketChannel 而不是 SocketChannel
 childHandler 傳入一個 hanlder ,這個handler 將會在每個客戶端連接的時候調用。供 SocketChannel 使用

綁定端口的分析

// Start the server.
ChannelFuture f = b.bind(PORT).sync();

服務器就是在這個bind方法裏啓動完成的

bind 方法代碼, 追蹤到 創建了一個端口對象,並做了一些空判斷, 核心代碼doBind,我們看看
 public ChannelFuture bind(SocketAddress localAddress) {
                validate();
                if (localAddress == null) {
                    throw new NullPointerException("localAddress");
                }
                return doBind(localAddress);
            }

doBind 源碼剖析, 核心是兩個方法 initAndRegister 和  doBind0

private ChannelFuture doBind(final SocketAddress localAddress) {
            final ChannelFuture regFuture = initAndRegister();//下面分析
            final Channel channel = regFuture.channel();
            if (regFuture.cause() != null) {
                return regFuture;
            }

            if (regFuture.isDone()) {
                // At this point we know that the registration was complete and successful.
                ChannelPromise promise = channel.newPromise();
                //============================================
                //說明:執行 doBind0 方法,完成對端口的綁定
                //============================================
                doBind0(regFuture, channel, localAddress, promise);//下面分析
                return promise;
            } else {
                // Registration future is almost always fulfilled already, but just in case it's not.
                final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
                regFuture.addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        Throwable cause = future.cause();
                        if (cause != null) {
                            promise.setFailure(cause);
                        } else {
                            // Registration was successful, so set the correct executor to use.
                            // See https://github.com/netty/netty/issues/2586
                            promise.registered();

                            doBind0(regFuture, channel, localAddress, promise);
                        }
                    }
                });
                return promise;
            }
    }

分析說明 final ChannelFuture regFuture = initAndRegister();


final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
//說明: channelFactory.newChannel() 方法 的作用 通過 ServerBootstrap 的通道工廠
        //反射創建一個 NioServerSocketChannel, 具體追蹤源碼可以得到下面結論
(1) 通過 NIO 的SelectorProvider 的 openServerSocketChannel 方法得到JDK 的 channel。
    目的是讓 Netty 包裝 JDK 的 channel。
(2) 創建了一個唯一的 ChannelId,創建了一個 NioMessageUnsafe,用於操作消息,
    創建了一個 DefaultChannelPipeline 管道,是個雙向鏈表結構,用於過濾所有的進出的消息。
(3) 創建了一個 NioServerSocketChannelConfig 對象,用於對外展示一些配置。
            channel = channelFactory.newChannel();//NioServerSocketChannel
          
            
//說明:init 初始化這個 NioServerSocketChannel, 具體追蹤源碼可以得到如下結論
(1) init 方法,這是個抽象方法(AbstractBootstrap類的),由 ServerBootstrap 實現
    (可以追一下源碼 //setChannelOptions(channel, options, logger);)。
(2) 設置 NioServerSocketChannel 的 TCP 屬性。
(3) 由於 LinkedHashMap 是非線程安全的,使用同步進行處理。
(4) 對 NioServerSocketChannel 的 ChannelPipeline 添加 ChannelInitializer 處理器。
(5) 可以看出, init 的方法的核心作用在和 ChannelPipeline 相關。
(6) 從 NioServerSocketChannel 的初始化過程中,我們知道,pipeline 是一個雙向鏈表,
    並且,他本身就初始化了 head 和 tail,這裏調用了他的 addLast 方法,
    也就是將整個 handler 插入到 tail 的前面,因爲 tail 永遠會在後面,需要做一些系統的固定工作。
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            return new DefaultChannelPromise(new FailedChannel(), 
            GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }
    
說明:
1)基本說明: initAndRegister() 初始化 NioServerSocketChannel 通道並註冊各個 handler,返回一個 future
2)通過 ServerBootstrap 的通道工廠反射創建一個 NioServerSocketChannel。
3)init 初始化這個 NioServerSocketChannel。
4)config().group().register(channel) 通過 ServerBootstrap 的 bossGroup 註冊 NioServerSocketChannel。
5)最後,返回這個異步執行的佔位符即 regFuture。

init(channel) 方法 會調用addLast, 現在進入到 addLast 方法內查看

 public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            checkMultiplicity(handler);
            //ChannelHandlerContext是添加Handler時創建的
            newCtx = newContext(group, filterName(name, handler), handler);
            addLast0(newCtx);
            if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }
            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }
        
        callHandlerAdded0(newCtx);
        return this;
    }
說明:
1)addLast方法,在 DefaultChannelPipeline 類中
2)addLast方法這就是 pipeline 方法的核心
3)檢查該 handler 是否符合標準。
4)創建一個 AbstractChannelHandlerContext 對象,這裏說一下,
ChannelHandlerContext 對象是 ChannelHandler 和 ChannelPipeline 之間的關聯,
每當有 ChannelHandler 添加到 Pipeline 中時,都會創建 Context。
Context 的主要功能是管理他所關聯的 Handler 和同一個 Pipeline 中的其他 Handler 之間的交互。
5)將 Context 添加到鏈表中。也就是追加到 tail 節點的前面。最後,同步或者異步或者晚點異步的調用 callHandlerAdded0 方法

 private void addLast0(AbstractChannelHandlerContext newCtx) {
        AbstractChannelHandlerContext prev = tail.prev;
        newCtx.prev = prev;
        newCtx.next = tail;
        prev.next = newCtx;
        tail.prev = newCtx;
    }

分析: doBind0(regFuture, channel, localAddress, promise);
前面說了 dobind方法有2個重要的步驟,initAndRegister 說完,接下來看 doBind0 方法, 代碼如下
在這裏插入圖片描述

private static void doBind0(
            final ChannelFuture regFuture, final Channel channel,
            final SocketAddress localAddress, final ChannelPromise promise) {

        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (regFuture.isSuccess()) {
                   //bind方法這裏下斷點,這裏下斷點,來玩!!
                 channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {
                    promise.setFailure(regFuture.cause());
                }
            }
        });
    }


說明:
1)該方法的參數爲 initAndRegister 的 future,NioServerSocketChannel,端口地址,NioServerSocketChannel 的 promise

2)這裏就可以根據前面下的斷點,一直debug: 將調用 LoggingHandler 的 invokeBind 方法, 最後會追到
            //DefaultChannelPipeline 類的bind
            //然後進入到 unsafe.bind方法debug , 注意要追蹤到
            // unsafe.bind , 要debug 第二圈的時候,才能看到.
                @Override
        public void bind(
                ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
                throws Exception {
            unsafe.bind(localAddress, promise);
        }
            //繼續追蹤 AbstractChannel 的
            public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
                //....

            try {
                // 可以看到,這裏最終的方法就是 doBind 方法,執行成功後,執行通道的 fireChannelActive 方法,
                //告訴所有的 handler,已經成功綁定。
                doBind(localAddress);//
            } catch (Throwable t) {
                safeSetFailure(promise, t);
                closeIfClosed();
                return;
            }
            }

最終doBind 就會追蹤到 NioServerSocketChannel的doBind, 說明 Netty 底層使用的是 Nio
 @Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }
 protected ServerSocketChannel javaChannel() {
        return (ServerSocketChannel) super.javaChannel();
    }
    
    
回到 bind 方法(alt+v),最後一步:safeSetSuccess(promise),告訴 promise 任務成功了。
其可以執行監聽器的方法了。到此整個啓動過程已經結束了 ,ok了


繼續 atl+V服務器就回進入到(NioEventLoop類)一個循環代碼,進行監聽

  @Override
    protected void run() {
        for (;;) {
            try {
    }

在這裏插入圖片描述
在這裏插入圖片描述

10.2 Netty 接受請求過程源碼剖析

在這裏插入圖片描述
在這裏插入圖片描述
分析: processSelectedKeys(); masterGroup怎麼將請求Client的Channel註冊到到workerGroup的的NioEventLoop的Selector上的???
在這裏插入圖片描述

1.斷點位置 NioEventLoop 的如下方法 processSelectedKey

 if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read(); //斷點位置
            }

2.執行 瀏覽器 http://localhost:8007/, 客戶端發出請求

3.從的斷點我們可以看到, readyOps 是16 ,也就是 Accept事件。說明瀏覽器的請求已經進來了。

4.這個 unsafe 是 boss 線程NioServerSocketChannelAbstractNioMessageChannel$NioMessageUnsafe 對象。 我們進入到AbstractNioMessageChannel$NioMessageUnsafe 的read 方法中

5.read 方法代碼並分析:

@Override
        public void read() {
            assert eventLoop().inEventLoop();
            final ChannelConfig config = config();
            final ChannelPipeline pipeline = pipeline();
            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
            allocHandle.reset(config);

            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    do {
                        int localRead = doReadMessages(readBuf);////111
                        if (localRead == 0) {
                            break;
                        }
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }

                        allocHandle.incMessagesRead(localRead);
                    } while (allocHandle.continueReading());
                } catch (Throwable t) {
                    exception = t;
                }

                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    readPending = false;
                    pipeline.fireChannelRead(readBuf.get(i));////222
                }
                readBuf.clear();
                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (exception != null) {
                    closed = closeOnReadError(exception);

                    pipeline.fireExceptionCaught(exception);
                }

                if (closed) {
                    inputShutdown = true;
                    if (isOpen()) {
                        close(voidPromise());
                    }
                }
            } finally {
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }
說明:
  1. 檢查該 eventloop 線程是否是當前線程。assert eventLoop().inEventLoop()

  2. 執行 doReadMessages 方法,並傳入一個 readBuf 變量,這個變量是一個 List,也就是容器。

  3. 循環容器,執行 pipeline.fireChannelRead(readBuf.get(i));

  4. doReadMessages 是讀取 boss 線程中的 NioServerSocketChannel 接受到的請求。並把這些請求放進容器,一會我們debug 下doReadMessages 方法.

  5. 循環遍歷 容器中的所有請求,調用 pipeline 的 fireChannelRead 方法,用於處理這些接受的請求或者其他事件,在read 方法中,循環調用 ServerSocket 的 pipeline 的 fireChannelRead 方法, 開始執行 管道中的 handler 的 ChannelRead 方法(debug 進入)

6.追蹤一下doReadMessages方法, 就可以看得更清晰

protected int doReadMessages(List<Object> buf) throws Exception {
        SocketChannel ch = SocketUtils.accept(javaChannel());
        buf.add(new NioSocketChannel(this, ch));
        return 1;
    }

說明:

  1. 通過工具類,調用 NioServerSocketChannel 內部封裝的 serverSocketChannel 的 accept 方法,這是 Nio 做法。
  2. 獲取到一個 JDK 的 SocketChannel,然後,使用 NioSocketChannel 進行封裝。最後添加到容器中
  3. 這樣容器buf 中就有了NioSocketChannel

7.回到read方法,繼續分析 循環執行 pipeline.fireChannelRead 方法

  1. 前面分析 doReadMessages 方法的作用是通過 ServerSocket 的 accept 方法獲取到 Tcp 連接,然後封裝成 Netty 的 NioSocketChannel 對象。最後添加到 容器中

  2. 在read 方法中,循環調用 ServerSocket 的 pipeline 的 fireChannelRead 方法, 開始執行 管道中的 handler 的 ChannelRead 方法(debug 進入)
    在這裏插入圖片描述

  3. 經過dubug (多次),可以看到會反覆執行多個 handler的ChannelRead ,我們知道,因爲pipeline 裏面又 4 個 handler ,分別是 Head,LoggingHandler,ServerBootstrapAcceptor,Tail。
    在這裏插入圖片描述

  4. 我們重點看看 ServerBootstrapAcceptor。debug 之後,斷點會進入到 ServerBootstrapAcceptor 中來。我們來看看 ServerBootstrapAcceptor 的 channelRead 方法(要多次debug纔可以)

  5. channelRead 方法

io.netty.bootstrap.ServerBootstrap.ServerBootstrapAcceptor#channelRead

public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;

            child.pipeline().addLast(childHandler);

            setChannelOptions(child, childOptions, logger);

            for (Entry<AttributeKey<?>, Object> e: childAttrs) {
                child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
            }

            try {//將客戶端連接註冊到 worker 線程池
            //將該 NioSocketChannel 註冊到 childGroup 中的一個 EventLoop 上,並添加一個監聽器。
                childGroup.register(child).addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (!future.isSuccess()) {
                            forceClose(child, future.cause());
                        }
                    }
                });
            } catch (Throwable t) {
                forceClose(child, t);
            }
        }

說明:

  1. msg 強轉成 Channel ,實際上就是 NioSocketChannel 。
  2. 添加 NioSocketChannel 的 pipeline 的 handler ,就是我們 main 方法裏面設置的 childHandler 方法裏的。
  3. 設置 NioSocketChannel 的各種屬性。
  4. 將該 NioSocketChannel 註冊到 childGroup 中的一個 EventLoop 上(默認輪詢next方法選出),並添加一個監聽器。
  5. 這個 childGroup 就是我們 main 方法創建的數組 workerGroup。

8.進入 register 方法查看(步步追蹤會到)

@Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {

            AbstractChannel.this.eventLoop = eventLoop;

            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {

                eventLoop.execute(new Runnable() {
                    @Override
                    public void run() {
                        register0(promise);//進入到這裏
                    }
                });

            }
        }

繼續進入到 下面方法, 執行管道中可能存在的任務, 這裏我們就不追了

9.最終會調用 doBeginRead 方法,也就是 AbstractNioChannel 類的方法

@Override
    protected void doBeginRead() throws Exception {
        // Channel.read() or ChannelHandlerContext.read() was called
        final SelectionKey selectionKey = this.selectionKey; //斷點
        if (!selectionKey.isValid()) {
            return;
        }

        readPending = true;

        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }

10.這個地方調試時,請把前面的斷點都去掉,然後啓動服務器就會停止在 doBeginRead(需要先放過該斷點,然後瀏覽器請求,才能看到效果)

11.執行到這裏時,針對於這個客戶端的連接就完成了,接下來就可以監聽讀事件了

在這裏插入圖片描述

register0  ------  beginRead ----- doBeginRead
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章