基本说明

1、只有看过Netty源码,才能说是真正的掌握了Netty框架;

2、在io.netty.example包下,有很多netty源码案例,可以用来分析;

3、源码分析,是针对有Java项目经验,并且玩过框架源码的人员来讲的,否则会有相当的难度;

源码剖析目的

用源码分析的方式走一下Netty(服务器)的启动过程,更好地理解Netty的整体设计和运行机制;

1、源码需要剖析到Netty调用doBind方法,追踪到NioServerSocketChannel的doBind;

2、并且要Debug程序到NioEventLoop类的run代码,无限循环,在服务端运行。

@Overrideprotected void run() {    int selectCnt = 0;    for (;;) {        try {            int strategy;            try {                strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());                switch (strategy) {                case SelectStrategy.CONTINUE:                    continue;                case SelectStrategy.BUSY_WAIT:                    // fall-through to SELECT since the busy-wait is not supported with NIO                case SelectStrategy.SELECT:                    long curDeadlineNanos = nextScheduledTaskDeadlineNanos();                    if (curDeadlineNanos == -1L) {                        curDeadlineNanos = NONE; // nothing on the calendar                    }                    nextWakeupNanos.set(curDeadlineNanos);                    try {                        if (!hasTasks()) {                            strategy = select(curDeadlineNanos);                        }                    } finally {                        // This update is just to help block unnecessary selector wakeups                        // so use of lazySet is ok (no race condition)                        nextWakeupNanos.lazySet(AWAKE);                    }                    // fall through                default:                }            } catch (IOException e) {                // If we receive an IOException here its because the Selector is messed up. Let's rebuild                // the selector and retry. https://github.com/netty/netty/issues/8566                rebuildSelector0();                selectCnt = 0;                handleLoopException(e);                continue;            }            selectCnt++;            cancelledKeys = 0;            needsToSelectAgain = false;            final int ioRatio = this.ioRatio;            boolean ranTasks;            if (ioRatio == 100) {                try {                    if (strategy > 0) {                        processSelectedKeys();                    }                } finally {                    // Ensure we always run tasks.                    ranTasks = runAllTasks();                }            } else if (strategy > 0) {                final long ioStartTime = System.nanoTime();                try {                    processSelectedKeys();                } finally {                    // Ensure we always run tasks.                    final long ioTime = System.nanoTime() - ioStartTime;                    ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);                }            } else {                ranTasks = runAllTasks(0); // This will run the minimum number of tasks            }            if (ranTasks || strategy > 0) {                if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {                    logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",                            selectCnt - 1, selector);                }                selectCnt = 0;            } else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)                selectCnt = 0;            }        } catch (CancelledKeyException e) {            // Harmless exception - log anyway            if (logger.isDebugEnabled()) {                logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",                        selector, e);            }        } catch (Error e) {            throw (Error) e;        } catch (Throwable t) {            handleLoopException(t);        } finally {            // Always handle shutdown even if the loop processing threw an exception.            try {                if (isShuttingDown()) {                    closeAll();                    if (confirmShutdown()) {                        return;                    }                }            } catch (Error e) {                throw (Error) e;            } catch (Throwable t) {                handleLoopException(t);            }        }    }}

这里我们直接用官网提供的源码进行分析,这里先贴出官网的源码,然后再对源码进行分析,服务端代码EchoServer

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.        EventLoopGroup bossGroup = new NioEventLoopGroup(1);        EventLoopGroup workerGroup = new NioEventLoopGroup();        final EchoServerHandler serverHandler = new EchoServerHandler();        try {            ServerBootstrap b = new ServerBootstrap();            b.group(bossGroup, workerGroup)             .channel(NioServerSocketChannel.class)             .option(ChannelOption.SO_BACKLOG, 100)             .handler(new LoggingHandler(LogLevel.INFO))             .childHandler(new ChannelInitializer() {                 @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(serverHandler);                 }             });            // Start the server.            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();        }    }}

服务端业务端逻辑代码EchoServerHandler

/** * Handler implementation for the echo server. */@Sharablepublic class EchoServerHandler extends ChannelInboundHandlerAdapter {    @Override    public void channelRead(ChannelHandlerContext ctx, Object msg) {        ctx.write(msg);    }    @Override    public void channelReadComplete(ChannelHandlerContext ctx) {        ctx.flush();    }    @Override    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {        // Close the connection when an exception is raised.        cause.printStackTrace();        ctx.close();    }}

客户端代码EchoClient

/** * Sends one message when a connection is open and echoes back any received * data to the server.  Simply put, the echo client initiates the ping-pong * traffic between the echo client and server by sending the first message to * the server. */public final class EchoClient {    static final boolean SSL = System.getProperty("ssl") != null;    static final String HOST = System.getProperty("host", "127.0.0.1");    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));    static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));    public static void main(String[] args) throws Exception {        // Configure SSL.git        final SslContext sslCtx;        if (SSL) {            sslCtx = SslContextBuilder.forClient()                .trustManager(InsecureTrustManagerFactory.INSTANCE).build();        } else {            sslCtx = null;        }        // Configure the client.        EventLoopGroup group = new NioEventLoopGroup();        try {            Bootstrap b = new Bootstrap();            b.group(group)             .channel(NioSocketChannel.class)             .option(ChannelOption.TCP_NODELAY, true)             .handler(new ChannelInitializer() {                 @Override                 public void initChannel(SocketChannel ch) throws Exception {                     ChannelPipeline p = ch.pipeline();                     if (sslCtx != null) {                         p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));                     }                     //p.addLast(new LoggingHandler(LogLevel.INFO));                     p.addLast(new EchoClientHandler());                 }             });            // Start the client.            ChannelFuture f = b.connect(HOST, PORT).sync();            // Wait until the connection is closed.            f.channel().closeFuture().sync();        } finally {            // Shut down the event loop to terminate all threads.            group.shutdownGracefully();        }    }}

客户端业务代码EchoClientHandler

/** * Handler implementation for the echo client.  It initiates the ping-pong * traffic between the echo client and server by sending the first message to * the server. */public class EchoClientHandler extends ChannelInboundHandlerAdapter {    private final ByteBuf firstMessage;    /**     * Creates a client-side handler.     */    public EchoClientHandler() {        firstMessage = Unpooled.buffer(EchoClient.SIZE);        for (int i = 0; i < firstMessage.capacity(); i ++) {            firstMessage.writeByte((byte) i);        }    }    @Override    public void channelActive(ChannelHandlerContext ctx) {        ctx.writeAndFlush(firstMessage);    }    @Override    public void channelRead(ChannelHandlerContext ctx, Object msg) {        ctx.write(msg);    }    @Override    public void channelReadComplete(ChannelHandlerContext ctx) {       ctx.flush();    }    @Override    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {        // Close the connection when an exception is raised.        cause.printStackTrace();        ctx.close();    }}

下面我们就来针对这个官方提供的源代码例子来分析Netty服务器启动的过程,先来看服务端EchoServer 的代码

服务端说明

1、先看启动类:main方法中,首先创建了关于SSL的配置类;

2、重点分析下创建的两个NioEventLoopGroup对象

// Configure the server.EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();

① 这两个对象是整个Netty的核心对象,可以说,整个Netty的运作都依赖于他们,bossGroup用于接收TCP请求,他会将请求交给workerGroup ,workerGroup 会获取真正的连接,然后和连接进行通讯,比如读写编码解码等操作。

② EventLoopGroup是事件循环组(线程组)含有多个EventLoop,可以注册channel,用于在事件循环中去进行选择(和选择器相关);

EventLoopGroup

③ new NioEventLoopGroup(1) 这个1表示bossGroup 事件组有1个线程你可以指定,如果new NioEventLoopGroup()会默认创建线程=cpu核数*2,这里可以充分利用多核的优势;

#第一步:EventLoopGroup workerGroup = new NioEventLoopGroup();#第二步,点击上面的 new NioEventLoopGroup()public NioEventLoopGroup() {    this(0);}#第三步,点击上面的this(0);public NioEventLoopGroup(int nThreads) {    this(nThreads, (Executor) null);}#第四步,点击上面的this(nThreads, (Executor) null);public NioEventLoopGroup(int nThreads, Executor executor) {    this(nThreads, executor, SelectorProvider.provider());}#第五步,点击上面的this(nThreads, executor, SelectorProvider.provider());public NioEventLoopGroup(        int nThreads, Executor executor, final SelectorProvider selectorProvider) {    this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);}#第六步,点击上面的 this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,                         final SelectStrategyFactory selectStrategyFactory) {    super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());}#第七步,点击上面的superprotected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {    super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);}#第八步:点击 上面的DEFAULT_EVENT_LOOP_THREADS 定位到下面private static final int DEFAULT_EVENT_LOOP_THREADS;static {    DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(            "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));    if (logger.isDebugEnabled()) {        logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);    }}

默认会创建存储cpu核数*2的NioEventLoop的EventExecutor数组,this.children = new EventExecutor[nThreads];

EventExecutor

每个元素的类型都是NioEventLoop,NioEventLoop实现了EventLoop接口和Executor接口,

NioEventLoop继承关系

try块中创建了一个ServerBootstrap对象,他是一个引导类,用于启动服务器和引导整个程序的初始化,它和ServerChannel关联,而ServerChannel继承了Channel,有一些方法remoteAddress等,随后变量b调用了group方法,将两个group放入了自己的字段中,用于后期引导使用,

可以看到group方法的代码如下

/** * Set the {@link EventLoopGroup} for the parent (acceptor) and the child (client). These * {@link EventLoopGroup}'s are used to handle all the events and IO for {@link ServerChannel} and * {@link Channel}'s. */public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {    super.group(parentGroup);    if (this.childGroup != null) {        throw new IllegalStateException("childGroup set already");    }    this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");    return this;}

④ 然后添加了一个channel,它的参数是一个Class对象,引导类将通过这个Class对象反射创建ChannelFactory,然后添加一些TCP的参数。【说明:Channel的创建在bind方法,可以Debug下bind,会找到channel=channelFactory.newChannel();】

⑤ 再添加了一个服务器专属的日志处理器handler;

⑥ 再添加一个SocketChannel(不是ServerSocketChannel)的handler;

⑦ 然后绑定端口并阻塞至连接成功;

⑧ 最后main线程阻塞等待关闭;

分析EventLoopGroup的过程

1、分析MultithreadEventExecutorGroup类的MultithreadEventExecutorGroup方法

/** * Create a new instance. * @param nThreads 使用的线程数,默认为cpu core*2 * @param executor 执行器,如果传入null,则采用Netty默认的线程工厂和默认的执行器ThreadPerTaskExecutor  * @param chooserFactory    单例new DefaultEventExecutorChooserFactory() * @param args   在擦黄金执行器的时候传入固定参数   * /protected MultithreadEventExecutorGroup(int nThreads, Executor executor,                                        EventExecutorChooserFactory chooserFactory, Object... args) {    if (nThreads <= 0) {        throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));    }    //如果传入的执行器是null,则采用默认的线程工厂和默认的执行器    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 terminationListener = new FutureListener() {        @Override        public void operationComplete(Future future) throws Exception {            if (terminatedChildren.incrementAndGet() == children.length) {                terminationFuture.setSuccess(null);            }        }    };    //为每一个单例线程池添加一个关闭监听器    for (EventExecutor e: children) {        e.terminationFuture().addListener(terminationListener);    }    //将所有的单例线程池添加到一个HashSet中    Set childrenSet = new LinkedHashSet(children.length);    Collections.addAll(childrenSet, children);    readonlyChildren = Collections.unmodifiableSet(childrenSet);}

说明:

① 如果executor是null,则创建一个默认的ThreadPerTaskExecutor,使用Netty默认的线程工厂;

② 根据传入的线程数(CPU*2)创建一个线程池(单例线程池)数组;

③ 循环填充数组中的元素,如果异常,则关闭所有的单例线程池;

④ 根据线程选择工厂创建一个线程选择器;

⑤ 为每一个单例线程池添加一个关闭监听器;

⑥ 将所有的单例线程池添加到一个HashSet中;

ServerBootstrap创建和构造过程

1、ServerBootstrap是一个空构造,但是有默认的成员变量

// The order in which child ChannelOptions are applied is important they may depend on each other for validation// purposes.private final Map, Object> childOptions = new LinkedHashMap, Object>();private final Map, Object> childAttrs = new ConcurrentHashMap, Object>();private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);private volatile EventLoopGroup childGroup;private volatile ChannelHandler childHandler;

2、分析下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() {     @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(serverHandler);     } });

说明:

① 链式调用group方法,将boss和worker传入,boss赋值给parentGroup属性,worker赋值给childGroup属性;

② channel方法传入NioServerSocketChannel.class对象,会根据这个class创建channel对象;

③ option方法传入TCP参数,放在一个LinkedHashMap中;

/** * Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they got * created. Use a value of {@code null} to remove a previous set {@link ChannelOption}. */public  B option(ChannelOption option, T value) {    ObjectUtil.checkNotNull(option, "option");    synchronized (options) {        if (value == null) {            options.remove(option);        } else {            options.put(option, value);        }    }    return self();}#其中options是private final Map, Object> options = new LinkedHashMap, Object>();

④ handler方法传入一个handler中,这个handler只专属于ServerSocketChannel而不是SocketChannel;

⑤ childHandler传入一个handler,这个handler将会在每个客户端连接的时候调用,供SocketChannel使用

绑定端口的分析

1、服务器就是在这个bind方法里启动完成的;

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

2、bind方法代码,追踪到创建了一个端口对象,并做了一些空判断,核心代码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(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) {                    // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an                    // IllegalStateException once we try to access the EventLoop of the Channel.                    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;    }}

然后我们来看下它的initAndRegister方法。

final ChannelFuture initAndRegister() {    Channel channel = null;    try {        /**        说明:channelFactory.newChannel()方法的作用,通过ServerBootstrap的        通道工厂反射创建一个NioServerSocketChannel,具体追踪源码可以得到下面结论        ① 通过NIO的SelectorProvider的openServerSocketChannel方法得到JDK的channel.        目的是让Netty包装JDK的channel;        ② 创建了一个唯一的ChannelId,创建了一个NioMessageUnsafe,用于操作消息,创建了一个        DefaultChannelPipeline管道,是个双向链表结构,用于过滤所有的进出消息。        ③ 创建了一个NioServerSocketChannelConfig对象,用于对外展示一些配置;                **/        channel = channelFactory.newChannel();//NioServerSocketChannel        /**        说明:init初始化这个NioServerSocketChannel,具体追踪源码可以得到如下结论        ① init方法,这是个抽象方法(AbstractBootstrap类的),由ServerBootstrap实现(可以追踪一下源码)         setChannelOptions(channel, newOptionsArray(), logger);        ② 设置NioServerSocketChannel的TCP属性;        ③ 由于LinkedHash.Map 是非线程安全的,使用同步进行处理;        ④ 对NioServerSocketChannel的ChannelPipeline添加ChannelInitializer处理器;        ⑤ 可以看出,init的方法的核心作用在和ChannelPipeline相关;        ⑥ 从NioServerSocketChannel的初始化过程中,我们知道,pipeline是一个双向链表,并且,它本身就初始化了        head和tail,这里调用了它的addLast方法,也就是将整个handler插入到tail的前面,因为tail永远会在后面,        需要做一些系统的固定工作;        **/        init(channel);    } catch (Throwable t) {        if (channel != null) {            // channel can be null if newChannel crashed (eg SocketException("too many open files"))            channel.unsafe().closeForcibly();            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor            return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);        }        // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor        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();        }    }    // 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 because 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;}

说明:

1、基本说明:initAndRegister()初始化NioServerSocketChannel通道并注册各个handler,返回一个future;

2、通过ServerBootstrap的通道工厂反射创建一个NioServerSocketChannel;

3、init初始化这个NioServerSocketChannel;

4、config().group().register(channel)通过ServerBootstrap的bossGroup注册NioServerSocketChannel;

5、最后,返回这个异步执行的占位符即regFuture;

6、然后我们再来看看上面的init方法,会调用addLast,选择进入到addLast方法内看看

@Overridepublic final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {    final AbstractChannelHandlerContext newCtx;    synchronized (this) {        checkMultiplicity(handler);        newCtx = newContext(group, filterName(name, handler), handler);        addLast0(newCtx);        // If the registered is false it means that the channel was not registered on an eventLoop yet.        // In this case we add the context to the pipeline and add a task that will call        // ChannelHandler.handlerAdded(...) once the channel is registered.        if (!registered) {            newCtx.setAddPending();            callHandlerCallbackLater(newCtx, true);            return this;        }        EventExecutor executor = newCtx.executor();        if (!executor.inEventLoop()) {            callHandlerAddedInEventLoop(newCtx, executor);            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节点的前面;

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

6、最后,同步或者异步或者晚点异步地调用callHandlerAdded0方法;

前面说了doBind方法有2个重要的步骤,initAndRegister说完了,接下来就来说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()) {                //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第二圈的时候才能看到

@Overridepublic void bind(        ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {    unsafe.bind(localAddress, promise);}

继续追踪AbstractChannel的

@Overridepublic final void bind(final SocketAddress localAddress, final ChannelPromise promise) {    assertEventLoop();    if (!promise.setUncancellable() || !ensureOpen(promise)) {        return;    }    // See: https://github.com/netty/netty/issues/576    if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&        localAddress instanceof InetSocketAddress &&        !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&        !PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {        // Warn a user about the fact that a non-root user can't receive a        // broadcast packet on *nix if the socket is bound on non-wildcard address.        logger.warn(                "A non-root user can't receive a broadcast packet if the socket " +                "is not bound to a wildcard address; binding to a non-wildcard " +                "address (" + localAddress + ") anyway as requested.");    }    boolean wasActive = isActive();    try {        //可以看到,这里最终的方法就是doBind方法,执行成功后,执行通道的        //fireChannelActive方法,告诉所有的handler已经成功绑定        doBind(localAddress);    } catch (Throwable t) {        safeSetFailure(promise, t);        closeIfClosed();        return;    }    if (!wasActive && isActive()) {        invokeLater(new Runnable() {            @Override            public void run() {                pipeline.fireChannelActive();            }        });    }    safeSetSuccess(promise);}

3、最终doBind就会追踪到NioServerSocketChannel的doBind,说明Netty底层使用的是NioServerSocketChannel

@SuppressJava6Requirement(reason = "Usage guarded by java version check")@Overrideprotected void doBind(SocketAddress localAddress) throws Exception {    if (PlatformDependent.javaVersion() >= 7) {        javaChannel().bind(localAddress, config.getBacklog());    } else {        javaChannel().socket().bind(localAddress, config.getBacklog());    }}

4、回到bind方法,最后一步:this.safeSetSuccess(promise),告诉promise任务成功了,其可以执行监听器的方法了,到此整个启动过程已经结束;

5、继续往下走就会进入到(NioEventLoop类)的一个循环代码进行监听

protected void run() {    while(true) {        while(true) {            while(true) {

.net webim 源码_Netty服务器启动过程源码带你分析「你能坚持看完吗?」相关推荐

  1. Android系统默认Home应用程序(Launcher)的启动过程源码分析

    在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还须要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应 ...

  2. Tomcat - Tomcat 8.5.55 启动过程源码分析阶段三_start阶段

    文章目录 启动流程分析 Pre Star阶段 start总览 start源码分析 StandardServer Start StandardService Start StandardEngine S ...

  3. Tomcat - Tomcat 8.5.55 启动过程源码分析阶段二_load加载初始化

    文章目录 启动流程分析 Pre load 加载初始化 总体预览 源码解析 load() Server初始化 Service初始化 Engine初始化 Connector 初始化 小结 启动流程分析 P ...

  4. Activity启动流程源码分析-浅析生命周期函数

    源码分析 接着上一篇 Activity启动流程源码分析-setContentView源码阅读 的讲解,本节介绍一下Activity的生命周期函数何时被调用 要看Activity的生命周期函数何时被调用 ...

  5. Doris FE启动流程源码详细解析

    Doris FE启动流程源码详细解析 一.简介 Apache Doris是一个现代化的MPP分析型数据库产品.仅需亚秒级响应时间即可获得查询结果,有效地支持实时数据分析.Apache Doris的分布 ...

  6. Android音频框架之二 用户录音启动流程源码走读

    前言 此篇是对<Android音频框架之一 详解audioPolicy流程及HAL驱动加载>的延续,此系列博文是记录在Android7.1系统即以后版本实现 内录音功能. 当用户使用 Au ...

  7. SpringBoot2 | SpringBoot启动流程源码分析(一)

    首页 博客 专栏·视频 下载 论坛 问答 代码 直播 能力认证 高校 会员中心 收藏 动态 消息 创作中心 SpringBoot2 | SpringBoot启动流程源码分析(一) 置顶 张书康 201 ...

  8. Activity启动流程源码分析(基于Android N)

    Activity启动流程源码分析 一个Activity启动分为两种启动方式,一种是从Launcher界面上的图标点击启动,另一种是从一个Activity中设置按钮点击启动另外一个Activity.这里 ...

  9. Service通过onBind跨进程启动流程源码探究

    根据<Activity跨进程启动流程源码探究>我们可以清楚以下几点: 1)Context的通用实现是在ContextIml这个类中 2)Activity的启动过程需要借助ActivityM ...

最新文章

  1. 東方茸回廊 汉化补丁
  2. 如何将本地jar包上传到maven私服中
  3. Java队列 Deque
  4. Windows中动态磁盘管理
  5. 老男孩IT教育38期面授班 学员邢伟的决心书
  6. MySQL数据库开启root用户远程登录
  7. oracle数组的使用
  8. org.springframework.dao.IncorrectResultSizeDataAccessException: query did not return a unique result
  9. Angular URL地址参数改变,视图不更新的解决办法(监听URL变化,重新加载数据方法)
  10. cocos2d0基础篇笔记二
  11. 数据结构与算法之优先队列
  12. Android——Binder机制
  13. 普通显示器玩立体游戏|红蓝立体游戏|iZ3D设置办法
  14. Apache Flink 进阶教程(八):详解 Metrics 原理与实战
  15. 在VS2010下利用vue开发团队项目
  16. 如何查看MySQL的表空间
  17. GAP:Learning Contextual Representations for Semantic Parsing with Generation-Augmented Pre-Training
  18. word如何首页和目录不编辑页码
  19. MPU6050 DMP 代码完全解析 —— dmp_init
  20. 计算机的过去和现在作文英语怎么说,自己过去和现在英语作文带翻译

热门文章

  1. oracle清理trace、alert、aud、listener等日志文件
  2. vue ---- 工程化概念、webpack概念、webpack的安装配置,以及简单使用
  3. python 如何快速判断列表是否相同_python如何判断两个list是否相等
  4. python 读取excel表数据_5分钟学会用Python 读取Excel
  5. python与matlab哪个更容易学-详解python和matlab的优势与区别
  6. opencv 无法使用 dll 动态链接库 UnsatisfiedLinkError java.library.path Can‘t find dependent libraries
  7. Java发送form-data请求实现文件上传
  8. 查询目标服务器系统,查看目标服务器的操作系统
  9. invokedynamic指令
  10. MySQL 加锁处理分析(二)