Java后端架构师的成长之路(三)——Java网络编程Netty(3)

Java网络编程Netty

Netty核心源码分析

  • 分析的 Netty 源码版本为4.1.36

Netty启动过程源码分析

  • 用源码分析的方式走一下 Netty (服务器)的启动过程,更好的理解Netty 的整体设计和运行机制。
  • 启动过程源码分析需要到 Netty 调用doBind方法, 追踪到 NioServerSocketChannel 的 doBind 方法。
  • 并且要 Debug 程序到 NioEventLoop类 的run代码 ,无限循环,在服务器端运行。

Echo 程序Demo源码启动类的整体理解

  • 先看启动类:main 方法中,首先创建了关于SSL 的配置类。
  • 重点分析下创建的两个EventLoopGroup 对象:EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup();
    在这里插入图片描述
  • ① 这两个对象是整个 Netty 的核心对象,可以说整个 Netty 的运作都依赖于他们。bossGroup 用于接受 Tcp 请求,他会将请求交给 workerGroup,workerGroup 会获取到真正的连接,然后和连接进行通信,比如读写解码编码等操作。
  • ② NioEventLoopGroup 是事件循环组(线程组) 含有多个 NioEventLoop,可以注册channel,用于在事件循环中去进行选择(和选择器相关)。Debug 一下:
    在这里插入图片描述
  • ③ new NioEventLoopGroup(1); 这个1 表示 bossGroup 事件组有1个线程你可以指定,如果 new NioEventLoopGroup() 会含有默认个线程 cpu核数*2,即可以充分的利用多核的优势,Debug 一下:
    在这里插入图片描述
  • 并且会创建 EventExecutor 数组 children = new EventExecutor[nThreads];每个元素的类型就是 NIOEventLoop, NIOEventLoop 实现了 EventLoop 接口 和 Executor 接口。
    在这里插入图片描述
  • ④ try 块中创建了一个 ServerBootstrap 对象,他是一个引导类,用于启动服务器和引导整个程序的初始化。它和 ServerChannel 关联, 而 ServerChannel 继承了 Channel,有一些方法 remoteAddress等。
  • 随后,变量 b 调用了 group 方法将两个 group 放入了自己的字段中,用于后期引导使用。Debug 一下:
    在这里插入图片描述
    在这里插入图片描述
  • ⑤ 然后添加了一个 channel【.channel(NioServerSocketChannel.class)】,引导类将通过这个 Class 对象反射创建 ChannelFactory。(4)然后添加了一些TCP的参数。【说明:Channel 的创建在bind 方法,可以Debug下bind ,会找到channel = channelFactory.newChannel();】。
  • ⑥ 再添加了一个服务器专属的日志处理器 handler。
  • ⑦ 再添加一个 SocketChannel(不是 ServerSocketChannel)的 handler。
  • ⑧ 然后绑定端口并阻塞至连接成功。
  • ⑨ 最后main线程阻塞等待关闭。
  • ⑩ finally 块中的代码将在服务器关闭时优雅关闭所有资源。

NioEventLoopGroup源码分析

  • ① 分析入口:EventLoopGroup bossGroup = new NioEventLoopGroup(1);
public NioEventLoopGroup(int nThreads) {
    this(nThreads, (Executor) null);
}
	↓↓↓↓↓
public NioEventLoopGroup(int nThreads, Executor executor) {
    this(nThreads, executor, SelectorProvider.provider());
}
	↓↓↓↓↓
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider) {
    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());
}
  • ② 紧接着,点击进入 super() 方法,其父类是 MultithreadEventLoopGroup,接着在继续追踪到源码抽象类 MultithreadEventExecutorGroup ,MultithreadEventExecutorGroup 的构造器方法才是 NioEventLoopGroup 真正的构造方法, 这里看出是使用了抽象模板设计模式。
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
    super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
	↓↓↓↓↓
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
    this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
	↓↓↓↓↓
/**
 * Create a new instance.
 *
 * @param nThreads          使用的线程数,默认为 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));
    }
	// 如果传入的执行器是空的,则采用默认的线程工厂和默认的执行器
    if (executor == null) {
        executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
    }
	// 创建指定线程数的执行器数组
    children = new EventExecutor[nThreads];
	// 初始化线程数组
    for (int i = 0; i < nThreads; i ++) {
        boolean success = false;
        try {
        	// 创建 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);
}

ServerBootstrap 创建和构建过程

  • ① 分析入口:ServerBootstrap b = new ServerBootstrap();,ServerBootstrap 基本使用情况:
ServerBootstrap b = new ServerBootstrap();
// 采用链式调用方式
// group 方法将 bossGroup 和 workerGroup 传入,bossGroup 赋值给 parentGroup 属性,workerGroup 赋值给 childGroup 属性
b.group(bossGroup, workerGroup)
 // channel 方法传入 NioServerSocketChannel class 对象,会根据这个 class 创建 channel 对象
 .channel(NioServerSocketChannel.class)
 // option 方法传入 TCP 参数,放在一个 LinkedHashMap 中
 .option(ChannelOption.SO_BACKLOG, 100)
 // handler 方法传入一个 handler,这个 handler 只专属于 ServerSocketChannel 而不是 SocketChannel
 .handler(new LoggingHandler(LogLevel.INFO))
 // childHandler 方法传入一个 handler,这个 handler 将会在每个客户端连接的时候调用,供 SocketChannel 使用
 .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());
     }
 });
  • ② 进入到 ServerBootstrap 的构造函数看看,是一个空的构造,但是里面初始化了一些重要的成员变量:
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class);

    private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();
    private final Map<AttributeKey<?>, Object> childAttrs = new LinkedHashMap<AttributeKey<?>, Object>();
    // 该配置在后面会有很大作用
    private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);
    private volatile EventLoopGroup childGroup;
    private volatile ChannelHandler childHandler;

    public ServerBootstrap() { }

    private ServerBootstrap(ServerBootstrap bootstrap) {
        super(bootstrap);
        childGroup = bootstrap.childGroup;
        childHandler = bootstrap.childHandler;
        synchronized (bootstrap.childOptions) {
            childOptions.putAll(bootstrap.childOptions);
        }
        synchronized (bootstrap.childAttrs) {
            childAttrs.putAll(bootstrap.childAttrs);
        }
    }
    // 后面代码省略...
}

绑定端口源码分析

  • ① 分析入口:ChannelFuture f = b.bind(PORT).sync();,服务器就是在这个bind方法里启动完成的,bind 方法代码层层往里面追踪,核心代码在 AbstractBootstrap.doBind(…)
// public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable
public ChannelFuture bind(int inetPort) {
	// 创建一个端口对象
    return bind(new InetSocketAddress(inetPort));
}
	↓↓↓↓↓
public ChannelFuture bind(SocketAddress localAddress) {
	// 做一些校验和空判断
    validate();
    if (localAddress == null) {
        throw new NullPointerException("localAddress");
    }
    return doBind(localAddress);
}
private ChannelFuture doBind(final SocketAddress localAddress) {
	// 核心方法一:执行该方法,完成 NioServerSocketChannel 的创建、初始化和注册
    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();
        // init 初始化这个 NioServerSocketChannel
        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);
    }
	// 通过 ServerBootstrap 的 bossGroup 注册 NioServerSocketChannel
    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.
	// 返回这个异步执行的占位符即 regFuture
    return regFuture;
}

在这里插入图片描述

  • 接下来看下 NioServerSocketChannel的创建过程:通过 NIO 的SelectorProvider 的 openServerSocketChannel 方法得到JDK 的 channel,目的是让 Netty 包装 JDK 的 channel。
    在这里插入图片描述
// public class NioServerSocketChannel extends AbstractNioMessageChannel implements io.netty.channel.socket.ServerSocketChannel
public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    // 创建了一个 NioServerSocketChannelConfig 对象,用于对外展示一些配置
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
	↓↓↓↓↓
// public abstract class AbstractNioMessageChannel extends AbstractNioChannel
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent, ch, readInterestOp);
}
	↓↓↓↓↓
// public abstract class AbstractNioChannel extends AbstractChannel
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent);
    this.ch = ch;
    this.readInterestOp = readInterestOp;
    try {
        ch.configureBlocking(false);
    } catch (IOException e) {
        try {
            ch.close();
        } catch (IOException e2) {
            if (logger.isWarnEnabled()) {
                logger.warn(
                        "Failed to close a partially initialized socket.", e2);
            }
        }

        throw new ChannelException("Failed to enter non-blocking mode.", e);
    }
}
// public abstract class AbstractChannel extends DefaultAttributeMap implements Channel
protected AbstractChannel(Channel parent) {
    this.parent = parent;
    // 设置 ChannelId
    id = newId();
    // 设置 Unsafe
    unsafe = newUnsafe();
    // 设置 Pipeline
    pipeline = newChannelPipeline();
}
// 小结一下 NioServerSocketChannel 的创建过程
1、通过 ReflectiveChannelFactory 工厂类,以反射的方式对channel进行创建;
2、创建的过程中,会创建四个重要的对象:ChannelId、ChannelConfig、ChannelPipeline、Unsafe。
  • 接着看下 init方法,这是个抽象方法,由 AbstractBootstrap 的子类 ServerBootstrap 实现:
@Override
void init(Channel channel) throws Exception {
    final Map<ChannelOption<?>, Object> options = options0();
    synchronized (options) {
        setChannelOptions(channel, options, logger);
    }

    final Map<AttributeKey<?>, Object> attrs = attrs0();
    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();

    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    final Entry<ChannelOption<?>, Object>[] currentChildOptions;
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
    synchronized (childOptions) {
        currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0));
    }
    synchronized (childAttrs) {
        currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
    }

    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(final Channel ch) throws Exception {
            final ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = config.handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }

            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    pipeline.addLast(new ServerBootstrapAcceptor(
                            ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
}
// init 初始化过程小结:
1、设置 NioServerSocketChannel 的 TCP 属性,由于 LinkedHashMap 是非线程安全的,所以使用同步(synchronized)进行处理
2、对 NioServerSocketChannel 的 ChannelPipeline 添加 ChannelInitializer 处理器
3、init 的方法的核心作用在和 ChannelPipeline 相关
4、从 NioServerSocketChannel 的初始化过程中,我们知道,pipeline 是一个双向链表,并且,他本身就初始化了 head 和 tail,这里调用了他的 addLast 方法,也就是将整个 handler 插入到 tail 的前面,因为 tail 永远会在后面,需要做一些系统的固定工作
  • init 方法会调用 addList 方法,我们进入 addList 方法查看:addLast方法,实现是在 DefaultChannelPipeline 类中,其是 Pipeline 方法的核心
// public class DefaultChannelPipeline implements ChannelPipeline
@Override
public final ChannelPipeline addLast(ChannelHandler... handlers) {
    return addLast(null, handlers);
}
	↓↓↓↓↓
@Override
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
    if (handlers == null) {
        throw new NullPointerException("handlers");
    }

    for (ChannelHandler h: handlers) {
        if (h == null) {
            break;
        }
        addLast(executor, null, h);
    }

    return this;
}
	↓↓↓↓↓
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
    	// 检查该 handler 是否符合标准
        checkMultiplicity(handler);
		// 创建一个 AbstractChannelHandlerContext 对象【ChannelHandlerContext 对象是 ChannelHandler 和 ChannelPipeline 之间的关联,每当有 ChannelHandler 添加到 Pipeline 中时,都会创建 Context】
		// Context 的主要功能是管理他所关联的 Handler 和同一个 Pipeline 中的其他 Handler 之间的交互
        newCtx = newContext(group, filterName(name, handler), handler);
		// 将 Context 添加到链表中。也就是追加到 tail 节点的前面
        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 方法
    callHandlerAdded0(newCtx);
    return this;
}
	↓↓↓↓↓
private void addLast0(AbstractChannelHandlerContext newCtx) {
    AbstractChannelHandlerContext prev = tail.prev;
    newCtx.prev = prev;
    newCtx.next = tail;
    prev.next = newCtx;
    tail.prev = newCtx;
}

在这里插入图片描述

  • ③ 前面说了 dobind方法有2个重要的步骤,initAndRegister 说完,接下来看 doBind0 方法, 代码如下
// (1) public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable
/*
参数说明:
参数一:initAndRegister方法的返回值 future
参数二:NioServerSocketChannel
参数三:端口地址
参数四:NioServerSocketChannel 的 promise
*/
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()) {
            	// !!! 这里下断点Debug
                channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }
        }
    });
}
	↓↓↓↓↓
// (2) public abstract class AbstractChannel extends DefaultAttributeMap implements Channel
@Override
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
    return pipeline.bind(localAddress, promise);
}
	↓↓↓↓↓
// (3) public class DefaultChannelPipeline implements ChannelPipeline
@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
    return tail.bind(localAddress, promise);
}
	↓↓↓↓↓
@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
    return tail.bind(localAddress, promise);
}
	↓↓↓↓↓
// (4) abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint
@Override
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
    if (localAddress == null) {
        throw new NullPointerException("localAddress");
    }
    if (isNotValidPromise(promise, false)) {
        // cancelled
        return promise;
    }

    final AbstractChannelHandlerContext next = findContextOutbound(MASK_BIND);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeBind(localAddress, promise);
    } else {
        safeExecute(executor, new Runnable() {
            @Override
            public void run() {
                next.invokeBind(localAddress, promise);
            }
        }, promise, null);
    }
    return promise;
}
	↓↓↓↓↓
private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
    if (invokeHandler()) {
        try {
            ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
        } catch (Throwable t) {
            notifyOutboundHandlerException(t, promise);
        }
    } else {
        bind(localAddress, promise);
    }
}
	↓↓↓↓↓
// (5) 进入handler处理器 LoggingHandler 的bind方法
@Override
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
    if (logger.isEnabled(internalLevel)) {
        logger.log(internalLevel, format(ctx, "BIND", localAddress));
    }
    ctx.bind(localAddress, promise);
}
	↓↓↓↓↓
// (6) DefaultChannelPipeline
@Override
public void bind(
        ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
    unsafe.bind(localAddress, promise);
}
	↓↓↓↓↓
// (7) AbstractChannel
@Override
public 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),告诉 promise 任务成功了。其可以执行监听器的方法了。到此整个启动过程已经结束了
    safeSetSuccess(promise);
}
	↓↓↓↓↓
// (8) NioServerSocketChannel:最终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());
    }
}
  • ④ 接下来到最后一步,服务器就会进入到(NioEventLoop类)一个循环代码,进行监听:
    在这里插入图片描述

Netty启动过程梳理

  • 创建2个 EventLoopGroup 线程池数组。数组默认大小CPU*2,方便chooser选择线程池时提高性能。
  • BootStrap 将 boss 设置为 group属性,将 worker 设置为 childer 属性。
  • 通过 bind 方法启动,内部重要方法为 initAndRegister 和 dobind 方法。
  • initAndRegister 方法会反射创建 NioServerSocketChannel 及其相关的 NIO 的对象、pipeline、unsafe,同时也为 pipeline 初始了 head 节点和 tail 节点。
  • 在 register0 方法成功以后,调用在 dobind 方法中调用 doBind0 方法,该方法会 调用 NioServerSocketChannel 的 doBind 方法对 JDK 的 channel 和端口进行绑定,完成 Netty 服务器的所有启动,并开始监听连接事件。

Netty接收请求过程源码分析

说明

  • 服务器启动后肯定是要接受客户端请求并返回客户端想要的信息的,下面源码分析 Netty 在启动之后是如何接受客户端请求的。
  • 从之前服务器启动的源码中,我们得知,服务器最终注册了一个 Accept 事件等待客户端的连接。我们也知道,NioServerSocketChannel 将自己注册到了 boss 单例线程池(reactor 线程)上,也就是 NioEventLoop 。
  • 先简单说下 NioEventLoop 的逻辑:
EventLoop 的作用是一个死循环,而这个循环中做3件事情:
* 有条件的等待 Nio 事件
* 处理 Nio 事件
* 处理消息队列中的任务
  • 仍用前面的项目来分析:进入到 NioEventLoop 源码中后,在private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) 方法开始调试。
// public final class NioEventLoop extends SingleThreadEventLoop
private void processSelectedKeys() {
    if (selectedKeys != null) {
        processSelectedKeysOptimized();
    } else {
        processSelectedKeysPlain(selector.selectedKeys());
    }
}
	↓↓↓↓↓
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();
           }
       }
   }
}
  • 最终我们要分析到AbstractNioChannel 的 doBeginRead 方法, 当到这个方法时,针对于这个客户端的连接就完成了,接下来就可以监听读事件了

源码分析

  • ① 分析入口:private void processSelectedKey(SelectionKey k, AbstractNioChannel ch)==>> unsafe.read() 方法,Debug可以看到 readyOps 是16 ,是一个 Accept 事件,说明请求已经进来了。
    在这里插入图片描述
  • 这个 unsafe 方法是 boss 线程中 NioServerSocketChannel的AbstractNioMessageChannel N i o M e s s a g e U n s a f e 对 象 。 我 们 进 入 到 A b s t r a c t N i o M e s s a g e C h a n n e l NioMessageUnsafe 对象。 我们进入到AbstractNioMessageChannel NioMessageUnsafeAbstractNioMessageChannelNioMessageUnsafe 的 read 方法中
// public abstract class AbstractNioMessageChannel extends AbstractNioChannel
private final class NioMessageUnsafe extends AbstractNioUnsafe {

    private final List<Object> readBuf = new ArrayList<Object>();

    @Override
    public void read() {
    	// 检查该 eventloop 线程是否是当前线程
        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 {
                	// 核心方法一:执行 doReadMessages 方法,并传入一个 readBuf 变量,这个变量是一个 List 容器
                	// doReadMessages 读取 boss 线程中的 NioServerSocketChannel 接受到的请求,并把这些请求放进容器
                    int localRead = doReadMessages(readBuf);
                    if (localRead == 0) {
                        break;
                    }
                    if (localRead < 0) {
                        closed = true;
                        break;
                    }

                    allocHandle.incMessagesRead(localRead);
                } while (allocHandle.continueReading());
            } catch (Throwable t) {
                exception = t;
            }
			// 遍历 readBuf 集合,循环执行 fireChannelRead 方法,用于处理这些接受的请求或者其他事件
            int size = readBuf.size();
            for (int i = 0; i < size; i ++) {
                readPending = false;
                // 核心方法二
                pipeline.fireChannelRead(readBuf.get(i));
            }
            readBuf.clear();
            allocHandle.readComplete();
            pipeline.fireChannelReadComplete();

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

                pipeline.fireExceptionCaught(exception);
            }

            if (closed) {
                inputShutdown = true;
                if (isOpen()) {
                    close(voidPromise());
                }
            }
        } finally {
            // Check if there is a readPending which was not processed yet.
            // This could be for two reasons:
            // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
            // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
            //
            // See https://github.com/netty/netty/issues/2254
            if (!readPending && !config.isAutoRead()) {
                removeReadOp();
            }
        }
    }
}
  • ② 首先看一下 doReadMessages 方法:
// NioServerSocketChannel 
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
	// 通过SocketUtils工具类,调用 NioServerSocketChannel 内部封装的 serverSocketChannel 的 accept 方法,这是 Nio 做法
    SocketChannel ch = SocketUtils.accept(javaChannel());

    try {
        if (ch != null) {
        	// 获取到一个 JDK 的 SocketChannel,然后,使用 NioSocketChannel 进行封装,再添加到容器中,这样容器buf 中就有了NioSocketChannel 
            buf.add(new NioSocketChannel(this, ch));
            // 成功则返回1
            return 1;
        }
    } catch (Throwable t) {
        logger.warn("Failed to create a new channel from an accepted socket.", t);

        try {
            ch.close();
        } catch (Throwable t2) {
            logger.warn("Failed to close a socket.", t2);
        }
    }

    return 0;
}
  • ③ 接着看一下 fireChannelRead 方法:
// 循环调用 ServerSocket 的 pipeline 的 fireChannelRead 方法, 开始执行 管道中的 handler 的 ChannelRead 方法
// (1)DefaultChannelPipeline
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
    AbstractChannelHandlerContext.invokeChannelRead(head, msg);
    return this;
}
	↓↓↓↓↓
// (2) AbstractChannelHandlerContext
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeChannelRead(m);
    } else {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                next.invokeChannelRead(m);
            }
        });
    }
}
	↓↓↓↓↓
private void invokeChannelRead(Object msg) {
    if (invokeHandler()) {
        try {
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    } else {
        fireChannelRead(msg);
    }
}
	↓↓↓↓↓
// (3) public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel>
// 的内部类 private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter
@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
	// msg 强转成 Channel ,实际上就是 NioSocketChannel
    final Channel child = (Channel) msg;
	// 添加 NioSocketChannel 的 handler 到 pipeline,就是我们 main 方法里面设置的 childHandler 方法里的
    child.pipeline().addLast(childHandler);
	// 设置 NioSocketChannel 的各种属性
    setChannelOptions(child, childOptions, logger);

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

    try {
    	// 将该 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);
    }
}
  • ④ 进入 register 方法查看:
// (1)MultithreadEventLoopGroup
@Override
public ChannelFuture register(Channel channel) {
    return next().register(channel);
}
	↓↓↓↓↓
// (2)public abstract class SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop
@Override
public ChannelFuture register(Channel channel) {
    return register(new DefaultChannelPromise(channel, this));
}
@Override
public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    promise.channel().unsafe().register(this, promise);
    return promise;
}
// (3)protected abstract class AbstractUnsafe implements Unsafe
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    if (eventLoop == null) {
        throw new NullPointerException("eventLoop");
    }
    if (isRegistered()) {
        promise.setFailure(new IllegalStateException("registered to an event loop already"));
        return;
    }
    if (!isCompatible(eventLoop)) {
        promise.setFailure(
                new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
        return;
    }

    AbstractChannel.this.eventLoop = eventLoop;

    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();
            safeSetFailure(promise, t);
        }
    }
}
  • next() 方法追踪:
    在这里插入图片描述
  • ⑤ 最终会调用 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);
    }
}

Netty接受请求过程梳理

  • 总体流程:接受连接 ==>> 创建一个新的 NioSocketChannel ==>> 注册到一个 worker EventLoop 上 ==>> 注册selecot Read 事件。
  • ① 服务器轮询 Accept 事件,获取事件后调用 unsafe 的 read 方法,这个 unsafe 是 ServerSocket 的内部类,该方法内部由两部分组成。
  • ② doReadMessages 用于创建 NioSocketChannel 对象,该对象包装 JDK 的 Nio Channel 客户端。该方法会像创建 ServerSocketChanel 类似创建相关的 pipeline , unsafe,config。
  • ③ 随后执行 pipeline.fireChannelRead 方法,并将自己绑定到一个 chooser 选择器选择的 workerGroup 中的一个 EventLoop。并且注册一个0,表示注册成功,但并没有注册读(1)事件

Pipeline、Handler、HandlerContext创建源码分析

  • Netty 中的 ChannelPipeline 、 ChannelHandler 和 ChannelHandlerContext 是非常核心的组件, 我们从源码来分析Netty 是如何设计这三个核心组件的,并分析是如何创建和协调工作的。

三者关系

  • 每当 ServerSocket 创建一个新的连接,就会创建一个 Socket,对应的就是目标客户端。
  • 每一个新创建的 Socket 都将会分配一个全新的 ChannelPipeline(以下简称 pipeline)。
  • 每一个 ChannelPipeline 内部都含有多个 ChannelHandlerContext(以下简称 Context)。
  • 他们一起组成了双向链表,这些 Context 用于包装我们调用 addLast 方法时添加的 ChannelHandler(以下简称 handler)。
    在这里插入图片描述
  • ChannelSocket 和 ChannelPipeline 是一对一的关联关系,而 pipeline 内部的多个 Context 形成了链表,Context 只是对 Handler 的封装。
  • 当一个请求进来的时候,会进入 Socket 对应的 pipeline,并经过 pipeline 所有的 handler,对,就是设计模式中的过滤器模式。

ChannelPipeline 作用及设计

  • pipeline 的接口设计:
    在这里插入图片描述
  • 部分方法:
    在这里插入图片描述
  • 可以看到该接口继承了 inBound,outBound,Iterable 接口,表示他可以调用数据出站的方法和入站的方法,同时也能遍历内部的链表, 看看他的几个代表性的方法,基本上都是针对 handler 链表的插入、追加、删除、替换操作,类似是一个 LinkedList。同时,也能返回 channel(也就是 socket)。
  • 在 pipeline 的接口文档上,提供了一幅图:
    在这里插入图片描述
    对上图的解释说明:
  • 这是一个 handler 的 list,handler 用于处理或拦截入站事件和出站事件,pipeline 实现了过滤器的高级形式,以便用户控制事件如何处理以及 handler 在 pipeline 中如何交互。
  • 上图描述了一个典型的 handler 在 pipeline 中处理 I/O 事件的方式,IO 事件由 inboundHandler 或者 outBoundHandler 处理,并通过调用 ChannelHandlerContext.fireChannelRead 方法转发给其最近的处理程序 。
  • 入站事件由入站处理程序以自下而上的方向处理,如图所示。入站处理程序通常处理由图底部的 IO 线程生成入站数据。入站数据通常从如 SocketChannel.read(ByteBuffer) 获取。
  • 通常一个 pipeline 有多个 handler,例如,一个典型的服务器在每个通道的管道中都会有以下处理程序:协议解码器 - 将二进制数据转换为Java对象协议编码器 - 将Java对象转换为二进制数据业务逻辑处理程序 - 执行实际业务逻辑(例如数据库访问)
  • 你的业务程序不能将线程阻塞,会影响 IO 的速度,进而影响整个 Netty 程序的性能。如果你的业务程序很快,就可以放在 IO 线程中,反之,你需要异步执行。或者在添加 handler 的时候添加一个线程池,例如:
// 下面这个任务执行的时候,将不会阻塞 IO 线程,执行的线程来自 group 线程池
pipeline.addLast(group, "handler", new MyBusinessLogicHandler();

ChannelHandler 作用及设计

  • 源码:
public interface ChannelHandler {
	// 当把 ChannelHandler 添加到 pipeline 时被调用
    void handlerAdded(ChannelHandlerContext ctx) throws Exception;
	// 当从 pipeline 中移除时调用
    void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
	// 处理过程中在 pipeline 发生异常时调用
    @Deprecated
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;

    @Inherited
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Sharable {
        // no value
    }
}
  • ChannelHandler 的作用就是处理 IO 事件或拦截 IO 事件,并将其转发给下一个处理程序 ChannelHandler。Handler 处理事件时分入站和出站的,两个方向的操作都是不同的,因此,Netty 定义了两个子接口继承 ChannelHandler。
ChannelInboundHandler 入站事件接口
  • 程序员需要重写一些方法,当发生关注的事件,需要在方法中实现我们的业务逻辑,因为当事件发生时,Netty 会回调对应的方法
// 当 Channel 处于活动状态时被调用
void channelActive(ChannelHandlerContext ctx) throws Exception;
// 当从Channel 读取数据时被调用等方法
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
ChannelOutboundHandler 出站事件接口
  • 出站操作都是一些连接和写出数据类似的方法
// 当请求将 Channel 绑定到本地地址时调用
void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
// 当请求关闭 Channel 时调用等
void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
ChannelDuplexHandler 处理出站和入站事件
  • ChannelDuplexHandler 间接实现了入站接口并直接实现了出站接口,是一个通用的能够同时处理入站事件和出站事件的类。

ChannelHandlerContext 作用及设计

  • ChannelHandlerContext 继承了出站方法调用接口和入站方法调用接口
public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker

在这里插入图片描述

  • ChannelOutboundInvoker 部分源码:
    在这里插入图片描述
  • ChannelInboundInvoker 部分源码:
    在这里插入图片描述
  • ChannelOutboundInvoker 和 ChannelInboundInvoker 这两个 invoker 就是针对入站或出站方法来的,就是在入站或出站 handler 的外层再包装一层,达到在方法前后拦截并做一些特定操作的目的
  • ChannelHandlerContext 不仅仅继承了他们两个的方法,同时也定义了一些自己的方法,这些方法能够获取 Context 上下文环境中对应的比如 channel、executor、handler、pipeline、内存分配器、关联的 handler 是否被删除。
  • Context 就是包装了 handler 相关的一切,以方便 Context 可以在 pipeline 方便的操作 handler。

ChannelPipeline | ChannelHandler | ChannelHandlerContext 创建过程

分为3个步骤来看创建的过程:

  • 任何一个 ChannelSocket 创建的同时都会创建 一个 pipeline。
  • 当用户或系统内部调用 pipeline 的 add*** 方法添加 handler 时,都会创建一个包装这 handler 的 Context。
  • 这些 Context 在 pipeline 中组成了双向链表。
SocketChannel创建的时候创建Pipeline
protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}
	↓↓↓↓↓
protected DefaultChannelPipeline newChannelPipeline() {
    return new DefaultChannelPipeline(this);
}
	↓↓↓↓↓
// DefaultChannelPipeline
protected DefaultChannelPipeline(Channel channel) {
	// 将 channel 赋值给 channel 字段,用于 pipeline 操作 channel
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    // 创建一个 future 和 promise,用于异步回调使用
    succeededFuture = new SucceededChannelFuture(channel, null);
    voidPromise =  new VoidChannelPromise(channel, true);

	// 创建一个 inbound 的 tailContext,创建一个既是 inbound 类型又是 outbound 类型的 headContext
	// tailContext 和 HeadContext 是两个非常重要的方法,所有 pipeline 中的事件都会流经他们
    tail = new TailContext(this);
    head = new HeadContext(this);
	// 将两个 Context 互相连接,形成双向链表
    head.next = tail;
    tail.prev = head;
}
在 add** 添加处理器的时候创建 Context**
@Override
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
    if (handlers == null) {
        throw new NullPointerException("handlers");
    }

    for (ChannelHandler h: handlers) {
        if (h == null) {
            break;
        }
        addLast(executor, null, h);
    }

    return this;
}
	↓↓↓↓↓
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
	// pipeline 添加 handler,参数是线程池,name 是null, handler 是我们或者系统传入的handler。Netty 为了防止多个线程导致安全问题,同步了这段代码
    synchronized (this) {
    	// 检查这个 handler 实例是否是共享的,如果不是,并且已经被别的 pipeline 使用了,则抛出异常
        checkMultiplicity(handler);
		// 创建一个 Context,每次添加一个 handler 都会创建一个关联 Context,调用 addLast 方法,将 Context 追加到链表中。
        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) {
        	// 如果这个通道还没有注册到 selecor 上,就将这个 Context 添加到这个 pipeline 的待办任务中
            newCtx.setAddPending();
            callHandlerCallbackLater(newCtx, true);
            return this;
        }

        EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
            callHandlerAddedInEventLoop(newCtx, executor);
            return this;
        }
    }
    // 当注册好了以后,就会调用 callHandlerAdded0 方法(默认是什么都不做,用户可以实现这个方法)
    callHandlerAdded0(newCtx);
    return this;
}

Pipeline Handler HandlerContext创建过程梳理

  • 每当创建 ChannelSocket 的时候都会创建一个绑定的 pipeline,一对一的关系,创建 pipeline 的时候也会创建 tail 节点和 head 节点,形成最初的链表。
  • 在调用 pipeline 的 addLast 方法的时候,会根据给定的 handler 创建一个 Context,然后,将这个 Context 插入到链表的尾端(tail 前面)。
  • Context 包装 handler,多个 Context 在 pipeline 中形成了双向链表入站方向叫 inbound,由 head 节点开始,出站方法叫 outbound ,由 tail 节点开始。

ChannelPipeline调度handler的源码分析

  • 当一个请求进来的时候,ChannelPipeline 是如何调用内部的这些 handler 的呢?我们一起来分析下。
  • 首先,当一个请求进来的时候,会第一个调用 pipeline 的 相关方法,如果是入站事件,这些方法由 fire 开头,表示开始管道的流动,让后面的 handler 继续处理。

源码分析

  • 分析入口:DefaultChannelPipeline
    在这里插入图片描述
  • 以 public final ChannelPipeline fireChannelActive() 方法为例,进行源码追踪
    在这里插入图片描述
    在这里插入图片描述
  • 以 public final ChannelFuture bind(SocketAddress localAddress) 方法为例,进行源码追踪
    在这里插入图片描述
  • 出站是 tail 开始,入站从 head 开始。因为出站是从内部外面写,从tail 开始,能够让前面的 handler 进行处理,防止由 handler 被遗漏,比如编码。反之,入站当然是从 head 往内部输入,让后面的 handler 能够处理这些输入的数据。比如解码。因此虽然 head 也实现了 outbound 接口,但不是从 head 开始执行出站任务
  • 关于如何调用,用一张图来表示:
    在这里插入图片描述
  • ① pipeline 首先会调用 Context 的静态方法 fireXXX,并传入 Context。
  • ② 然后,静态方法调用 Context 的 invoker 方法,而 invoker 方法内部会调用该 Context 所包含的 Handler 的真正的 XXX 方法,调用结束后,如果还需要继续向后传递,就调用 Context 的 fireXXX2 方法,循环往复。

ChannelPipeline 调度 handler 梳理

  • Context 包装 handler,多个 Context 在 pipeline 中形成了双向链表,入站方向叫 inbound,由 head 节点开始,出站方法叫 outbound ,由 tail 节点开始。
  • 而节点中间的传递通过 AbstractChannelHandlerContext 类内部的 fire 系列方法,找到当前节点的下一个节点不断的循环传播。是一个过滤器形式完成对handler 的调度。

Netty心跳服务源码分析

  • Netty 作为一个网络框架,提供了诸多功能,比如编码解码等,Netty 还提供了非常重要的一个服务【心跳机制heartbeat】。通过心跳检查对方是否有效,这是 RPC 框架中是必不可少的功能。
  • Netty 提供了 IdleStateHandlerReadTimeoutHandlerWriteTimeoutHandler 三个Handler 检测连接的有效性。ReadTimeout 事件和 WriteTimeout 事件都会自动关闭连接,而且属于异常处理,所以这里只是介绍一下,我们重点看 IdleStateHandler。
    在这里插入图片描述

IdleStateHandler 分析

  • 四个属性
// 是否考虑出站时较慢的情况, 默认值是false
private final boolean observeOutput;
// 读事件空闲时间,0表示禁用事件
private final long readerIdleTimeNanos;
// 写事件空闲时间,0表示禁用事件
private final long writerIdleTimeNanos;
// 读写事件空闲时间,0表示禁用事件
private final long allIdleTimeNanos;
  • handlerAdded 方法:当该 handler 被添加到 pipeline 中时,则调用 initialize 方法
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
        // channelActive() event has been fired already, which means this.channelActive() will
        // not be invoked. We have to initialize here instead.
        initialize(ctx);
    } else {
        // channelActive() event has not been fired yet.  this.channelActive() will be invoked
        // and initialization will occur there.
    }
}
	↓↓↓↓↓
private void initialize(ChannelHandlerContext ctx) {
    // Avoid the case where destroy() is called before scheduling timeouts.
    // See: https://github.com/netty/netty/issues/143
    switch (state) {
    case 1:
    case 2:
        return;
    }
	// 只要给定的参数大于0,就创建一个定时任务,每个事件都创建。
	// 同时,将 state 状态设置为 1,防止重复初始化。调用 initOutputChanged 方法,初始化 “监控出站数据属性”。
    state = 1;
    initOutputChanged(ctx);

    lastReadTime = lastWriteTime = ticksInNanos();
    // 该类内部的 3 个定时任务类,分别对应 读、写、读写 事件,共有一个父类(AbstractIdleTask),这个父类提供了一个模板方法
    if (readerIdleTimeNanos > 0) {
        readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),
                readerIdleTimeNanos, TimeUnit.NANOSECONDS);
    }
    if (writerIdleTimeNanos > 0) {
        writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),
                writerIdleTimeNanos, TimeUnit.NANOSECONDS);
    }
    if (allIdleTimeNanos > 0) {
        allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),
                allIdleTimeNanos, TimeUnit.NANOSECONDS);
    }
}
	↓↓↓↓↓
private abstract static class AbstractIdleTask implements Runnable {

    private final ChannelHandlerContext ctx;

    AbstractIdleTask(ChannelHandlerContext ctx) {
        this.ctx = ctx;
    }
	// 当通道关闭了,就不执行任务了。反之,执行子类的 run 方法。
    @Override
    public void run() {
        if (!ctx.channel().isOpen()) {
            return;
        }

        run(ctx);
    }

    protected abstract void run(ChannelHandlerContext ctx);
}

读事件的 run 方法(即 ReaderIdleTimeoutTask 的run方法)分析

@Override
protected void run(ChannelHandlerContext ctx) {
	// 得到用户设置的超时时间
    long nextDelay = readerIdleTimeNanos;
    // 如果读取操作结束了(执行了 channelReadComplete 方法设置) ,就用当前时间减去给定时间和最后一次读操作的时间(执行了 channelReadComplete 方法设置)
    if (!reading) {
        nextDelay -= ticksInNanos() - lastReadTime;
    }
	// 如果小于0,就触发事件。反之,继续放入队列。间隔时间是新的计算时间。
    if (nextDelay <= 0) {
    	/*
		触发的逻辑是:首先将任务再次放到队列,时间是刚开始设置的时间,返回一个 promise 对象,用于做取消操作。然后,设置 first 属性为 false ,表示下一次读取不再是第一次了,这个属性在 channelRead 方法会被改成 true。
		总的来说,每次读取操作都会记录一个时间,定时任务时间到了,会计算当前时间和最后一次读的时间的间隔,如果间隔超过了设置的时间,就触发 UserEventTriggered 方法
		*/
        // Reader is idle - set a new timeout and notify the callback.
        // 用于取消任务 promise
        readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);

        boolean first = firstReaderIdleEvent;
        firstReaderIdleEvent = false;

        try {
        	// 再次提交任务
        	// 创建一个 IdleStateEvent 类型的读事件对象,将此对象传递给用户的 UserEventTriggered 方法。完成触发事件的操作
            IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
            // 触发用户 handler use
            channelIdle(ctx, event);
        } catch (Throwable t) {
            ctx.fireExceptionCaught(t);
        }
    } else {
        // Read occurred before the timeout - set a new timeout with shorter delay.
        readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
    }
}

写事件的 run 方法(即 WriterIdleTimeoutTask 的run方法)分析

  • 写任务的run代码逻辑基本和读任务的逻辑一样,唯一不同的就是有一个针对出站较慢数据的判断hasOutputChanged
@Override
protected void run(ChannelHandlerContext ctx) {

    long lastWriteTime = IdleStateHandler.this.lastWriteTime;
    long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
    if (nextDelay <= 0) {
        // Writer is idle - set a new timeout and notify the callback.
        writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);

        boolean first = firstWriterIdleEvent;
        firstWriterIdleEvent = false;

        try {
        	// 出站较慢数据的判断
            if (hasOutputChanged(ctx, first)) {
                return;
            }

            IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
            channelIdle(ctx, event);
        } catch (Throwable t) {
            ctx.fireExceptionCaught(t);
        }
    } else {
        // Write occurred before the timeout - set a new timeout with shorter delay.
        writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
    }
}

所有事件的 run 方法(即AllIdleTimeoutTask 的run方法)分析

  • 表示这个监控着所有的事件。当读写事件发生时,都会记录。代码逻辑和写事件的的基本一致。
@Override
protected void run(ChannelHandlerContext ctx) {

    long nextDelay = allIdleTimeNanos;
    // !!!! 注意
    if (!reading) {
    	// 当前时间减去 最后一次 写或读 的时间 ,若大于0,说明超时了
    	// 这里的时间计算是取读写事件中的最大值来的。然后像写事件一样,判断是否发生了写的慢的情况。
        nextDelay -= ticksInNanos() - Math.max(lastReadTime, lastWriteTime);
    }
    if (nextDelay <= 0) {
        // Both reader and writer are idle - set a new timeout and
        // notify the callback.
        allIdleTimeout = schedule(ctx, this, allIdleTimeNanos, TimeUnit.NANOSECONDS);

        boolean first = firstAllIdleEvent;
        firstAllIdleEvent = false;

        try {
            if (hasOutputChanged(ctx, first)) {
                return;
            }

            IdleStateEvent event = newIdleStateEvent(IdleState.ALL_IDLE, first);
            channelIdle(ctx, event);
        } catch (Throwable t) {
            ctx.fireExceptionCaught(t);
        }
    } else {
        // Either read or write occurred before the timeout - set a new
        // timeout with shorter delay.
        allIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
    }
}

小结Netty的心跳机制

  • IdleStateHandler 可以实现心跳功能,当服务器和客户端没有任何读写交互时,并超过了给定的时间,则会触发用户 handler 的 userEventTriggered 方法。用户可以在这个方法中尝试向对方发送信息,如果发送失败,则关闭连接。
  • IdleStateHandler 的实现基于 EventLoop 的定时任务,每次读写都会记录一个值,在定时任务运行的时候,通过计算当前时间和设置时间和上次事件发生时间的结果,来判断是否空闲。
  • 内部有 3 个定时任务,分别对应读事件、写事件、读写事件。通常用户监听读写事件就足够了。
  • 同时,IdleStateHandler 内部也考虑了一些极端情况:客户端接收缓慢,一次接收数据的速度超过了设置的空闲时间。Netty 通过构造方法中的 observeOutput 属性来决定是否对出站缓冲区的情况进行判断。
  • 如果出站缓慢,Netty 不认为这是空闲,也就不触发空闲事件。但第一次无论如何也是要触发的。因为第一次无法判断是出站缓慢还是空闲。当然,出站缓慢的话,可能造成OOM , OOM比空闲的问题更大。
  • 所以,当你的应用出现了内存溢出,OOM之类,并且写空闲极少发生(使用了 observeOutput 为 true),那么就需要注意是不是数据出站速度过慢。
  • 还有一个注意的地方:就是 ReadTimeoutHandler ,它继承自 IdleStateHandler,当触发读空闲事件的时候,就触发 ctx.fireExceptionCaught 方法,并传入一个 ReadTimeoutException,然后关闭 Socket。
  • 而 WriteTimeoutHandler 的实现不是基于 IdleStateHandler 的,他的原理是,当调用 write 方法的时候,会创建一个定时任务,任务内容是根据传入的 promise 的完成情况来判断是否超出了写的时间。当定时任务根据指定时间开始运行,发现 promise 的 isDone 方法返回 false,表明还没有写完,说明超时了,则抛出异常。当 write 方法完成后,会打断定时任务。

Netty核心组件NioEventLoop源码分析

NioEventLoop继承关系

在这里插入图片描述

  • ScheduledExecutorService 接口表示是一个定时任务接口,所以 NioEventLoop 可以接受定时任务。
  • SingleThreadEventExecutor 表示这是一个单个线程的线程池。
  • NioEventLoop 是一个单例的线程池,里面含有一个死循环的线程不断的做着3件事情:监听端口、处理端口事件、处理队列事件。每个 NioEventLoop 都可以绑定多个 Channel,而每个 Channel 始终只能由一个 NioEventLoop 来处理。

NioEventLoop的execute方法源码分析

  • SingleThreadEventExecutor 类中:
@Override
public void execute(Runnable task) {
    if (task == null) {
        throw new NullPointerException("task");
    }
	// 首先判断该 NioEventLoop 的线程是否是当前线程
    boolean inEventLoop = inEventLoop();
    // 核心方法一:添加任务到队列
    addTask(task);
    if (!inEventLoop) {
    	// 核心方法二:启动线程
        startThread();
        if (isShutdown()) {
        	// 果线程已经停止,并且删除任务失败,则执行拒绝策略
            boolean reject = false;
            try {
                if (removeTask(task)) {
                    reject = true;
                }
            } catch (UnsupportedOperationException e) {
                // The task queue does not support removal so the best thing we can do is to just move on and
                // hope we will be able to pick-up the task before its completely terminated.
                // In worst case we will log on termination.
            }
            if (reject) {
                reject();
            }
        }
    }
	// 如果 addTaskWakesUp 是 false,并且任务不是 NonWakeupRunnable 类型的,就尝试唤醒 selector
	// 这个时候,阻塞在 selecor 的线程就会立即返回
    if (!addTaskWakesUp && wakesUpForTask(task)) {
        wakeup(inEventLoop);
    }
}
  • ② 先看一下 addTask 方法:
protected void addTask(Runnable task) {
    if (task == null) {
        throw new NullPointerException("task");
    }
    if (!offerTask(task)) {
        reject(task);
    }
}
	↓↓↓↓↓
final boolean offerTask(Runnable task) {
    if (isShutdown()) {
        reject();
    }
    return taskQueue.offer(task);
}
  • ③ 再看一下 startThread 方法:当执行 execute 方法的时候,如果当前线程不是 NioEventLoop 所属线程,则尝试启动线程,也就是 startThread 方法
private void startThread() {
	// 该方法首先判断是否启动过了,保证 NioEventLoop 只有一个线程
	// 如果没有启动过,则尝试使用 Cas 将 state 状态改为 ST_STARTED,也就是已启动。
	// 然后调用 doStartThread 方法。如果失败,则进行回滚
    if (state == ST_NOT_STARTED) {
        if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
            try {
                doStartThread();
            } catch (Throwable cause) {
                STATE_UPDATER.set(this, ST_NOT_STARTED);
                PlatformDependent.throwException(cause);
            }
        }
    }
}
	↓↓↓↓↓
private void doStartThread() {
    assert thread == null;
    // 首先调用 executor 的 execute 方法,这个 executor 就是在创建 Event LoopGroup 的时候创建的 ThreadPerTaskExecutor 类
    // 该 execute 方法会将 Runnable 包装成 Netty 的 FastThreadLocalThread。
    executor.execute(new Runnable() {
        @Override
        public void run() {
        	// 首先判断线程中断状态
            thread = Thread.currentThread();
            if (interrupted) {
                thread.interrupt();
            }

            boolean success = false;
            // 然后设置最后一次的执行时间
            updateLastExecutionTime();
            try {
            	// !!! 执行当前 NioEventLoop 的 run 方法,注意:这个方法是个死循环,是整个 NioEventLoop 的核心
                SingleThreadEventExecutor.this.run();
                success = true;
            } catch (Throwable t) {
                logger.warn("Unexpected exception from an event executor: ", t);
            } finally {
            	// 使用CAS 不断修改 state 状态,改成 ST_SHUTTING_DOWN
            	
                for (;;) {
                    int oldState = state;
                    if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
                            SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
                        break;
                    }
                }

                // Check if confirmShutdown() was called at the end of the loop.
                if (success && gracefulShutdownStartTime == 0) {
                    if (logger.isErrorEnabled()) {
                        logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
                                SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must " +
                                "be called before run() implementation terminates.");
                    }
                }
				// 当线程 Loop 结束的时候,关闭线程,最后还要死循环确认是否关闭,否则不会 break。
                try {
                    // Run all remaining tasks and shutdown hooks.
                    for (;;) {
                        if (confirmShutdown()) {
                            break;
                        }
                    }
                } finally {
                    try {
                    	// 然后,执行 cleanup 操作,更新状态为 ST_TERMINATED,并释放当前线程锁
                        cleanup();
                    } finally {
                        // Lets remove all FastThreadLocals for the Thread as we are about to terminate and notify
                        // the future. The user may block on the future and once it unblocks the JVM may terminate
                        // and start unloading classes.
                        // See https://github.com/netty/netty/issues/6596.
                        FastThreadLocal.removeAll();

                        STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
                        threadLock.release();
                        // 如果任务队列不是空,则打印队列中还有多少个未完成的任务。并回调 terminationFuture 方法
                        if (!taskQueue.isEmpty()) {
                            if (logger.isWarnEnabled()) {
                                logger.warn("An event executor terminated with " +
                                        "non-empty task queue (" + taskQueue.size() + ')');
                            }
                        }
                        terminationFuture.setSuccess(null);
                    }
                }
            }
        }
    });
}
  • ④ 下面我们来看看最核心的 NioEventLoop 的run方法:该方法在 NioEventLoop
@Override
protected void run() {
    for (;;) {
        try {
            try {
                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                case SelectStrategy.CONTINUE:
                    continue;

                case SelectStrategy.BUSY_WAIT:
                    // fall-through to SELECT since the busy-wait is not supported with NIO

                case SelectStrategy.SELECT:
                    select(wakenUp.getAndSet(false));

                    // 一堆注释省略 ...

                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                    // 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();
                handleLoopException(e);
                continue;
            }

            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            if (ioRatio == 100) {
                try {
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    runAllTasks();
                }
            } else {
                final long ioStartTime = System.nanoTime();
                try {
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    final long ioTime = System.nanoTime() - ioStartTime;
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
        // Always handle shutdown even if the loop processing threw an exception.
        try {
            if (isShuttingDown()) {
                closeAll();
                if (confirmShutdown()) {
                    return;
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
    }
}
// 从上面的步骤可以看出,整个 run 方法做了3件事情:
1、select 获取感兴趣的事件
2、processSelectedKeys 处理事件
3、runAllTasks 执行队列中的任务
  • ⑤ 我们深入代码看一下 select 方法:如何体现非阻塞?
private void select(boolean oldWakenUp) throws IOException {
    Selector selector = this.selector;
    try {
        int selectCnt = 0;
        long currentTimeNanos = System.nanoTime();
        long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);

        for (;;) {
            long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
            if (timeoutMillis <= 0) {
                if (selectCnt == 0) {
                    selector.selectNow();
                    selectCnt = 1;
                }
                break;
            }

            // If a task was submitted when wakenUp value was true, the task didn't get a chance to call
            // Selector#wakeup. So we need to check task queue again before executing select operation.
            // If we don't, the task might be pended until select operation was timed out.
            // It might be pended until idle timeout if IdleStateHandler existed in pipeline.
            if (hasTasks() && wakenUp.compareAndSet(false, true)) {
                selector.selectNow();
                selectCnt = 1;
                break;
            }

            int selectedKeys = selector.select(timeoutMillis); // 默认一秒
            selectCnt ++;
			// 如果1秒后返回,有返回值 或者 select 被用户唤醒 或者 任务队列有任务 或者 有定时任务即将被执行
			// 则跳出循环
            if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
                // - Selected something,
                // - waken up by user, or
                // - the task queue has a pending task.
                // - a scheduled task is ready for processing
                break;
            }
            if (Thread.interrupted()) {
                // Thread was interrupted so reset selected keys and break so we not run into a busy loop.
                // As this is most likely a bug in the handler of the user or it's client library we will
                // also log it.
                //
                // See https://github.com/netty/netty/issues/2426
                if (logger.isDebugEnabled()) {
                    logger.debug("Selector.select() returned prematurely because " +
                            "Thread.currentThread().interrupt() was called. Use " +
                            "NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
                }
                selectCnt = 1;
                break;
            }

            long time = System.nanoTime();
            if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
                // timeoutMillis elapsed without anything selected.
                selectCnt = 1;
            } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
                    selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
                // The code exists in an extra method to ensure the method is not too big to inline as this
                // branch is not very likely to get hit very frequently.
                selector = selectRebuildSelector(selectCnt);
                selectCnt = 1;
                break;
            }

            currentTimeNanos = time;
        }

        if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
            if (logger.isDebugEnabled()) {
                logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                        selectCnt - 1, selector);
            }
        }
    } catch (CancelledKeyException e) {
        if (logger.isDebugEnabled()) {
            logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
                    selector, e);
        }
        // Harmless exception - log anyway
    }
}
// 调用 selector 的 select 方法,默认阻塞一秒钟,如果有定时任务,则在定时任务剩余时间的基础上在加上0.5秒进行阻塞。当执行 execute 方法的时候,也就是添加任务的时候会唤醒 selecor,防止 selecotr 阻塞时间过长

Netty核心组件NioEventLoop的运行机制小结

  • 每次执行 ececute 方法都是向队列中添加任务。当第一次添加时就启动线程,执行 run 方法。而 run 方法是整个 NioEventLoop 的核心,就像 NioEventLoop 的名字一样,Loop、Loop、不停的 Loop。
  • Loop 主要做了三件事:
  • ① 调用 selector 的 select 方法,默认阻塞一秒钟,如果有定时任务,则在定时任务剩余时间的基础上在加上0.5秒进行阻塞。当执行 execute 方法的时候,也就是添加任务的时候,回唤醒 selecor,防止 selecotr 阻塞时间过长。
  • ② 当 selector 返回的时候,回调用 processSelectedKeys 方法对 selectKey 进行处理。
  • ③ 当 processSelectedKeys 方法执行结束后,则按照 ioRatio 的比例执行 runAllTasks 方法,默认是 IO 任务时间和非 IO 任务时间是相同的,你也可以根据你的应用特点进行调优 。比如 非 IO 任务比较多,那么你就将 ioRatio 调小一点,这样非 IO 任务就能执行的长一点,防止队列钟积攒过多的任务。

handler中加入线程池和Context中添加线程池的源码分析

  • 在 Netty 中做耗时的,不可预料的操作,比如数据库,网络请求,会严重影响 Netty 对 Socket 的处理速度。
  • 而解决方法就是将耗时任务添加到异步线程池中。但就添加线程池这步操作来讲,可以有2种方式,而且这2种方式实现的区别也蛮大的。
  • 处理耗时业务的第一种方式—handler 中加入线程池
  • 处理耗时业务的第二种方式—Context 中添加线程池

处理方式一

  • 在EchoServerHandler 的 channelRead 方法进行异步:
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
	static final EventLoopGroup group = new DefaultEventLoopGroup(16);
	
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) {
	    final Object msgCop = msg;
	    final ChannelHandlerContext cxtCop = ctx;
	
	    group.submit(new Callable<Object>() {
	        @Override
	        public Object call() throws Exception {
	            ByteBuf buf = (ByteBuf) msgCop;
	            byte[] req = new byte[buf.readableBytes()];
	            buf.readBytes(req);
	            String body = new String(req, "UTF-8");
	            Thread.sleep(10 * 1000);
	            System.err.println(body + " " + Thread.currentThread().getName());
	            String reqString = "Hello i am server~~~";
	            ByteBuf resp = Unpooled.copiedBuffer(reqString.getBytes());
	            cxtCop.writeAndFlush(resp);
	            return null;
	        }
	    });
	    System.out.println("go  on ..");
	}
}
  • 处理流程:
    在这里插入图片描述
  • 当 IO 线程轮询到一个 socket 事件,然后 IO 线程开始处理,当走到耗时 handler 的时候,将耗时任务交给业务线程池。
  • 当耗时任务执行完毕再执行 pipeline write 方法的时候 ,(代码中使用的是 context 的 write 方法,上图画的是执行 pipeline 方法, 是一个意思)会将这个任务再次交给 IO 线程
  • 看一下 AbstractChannelHandlerContext 的 write 方法:
private void write(Object msg, boolean flush, ChannelPromise promise) {
    // ... 省略
    final AbstractChannelHandlerContext next = findContextOutbound(flush ?
            (MASK_WRITE | MASK_FLUSH) : MASK_WRITE);
    final Object m = pipeline.touch(msg, next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        if (flush) {
            next.invokeWriteAndFlush(m, promise);
        } else {
            next.invokeWrite(m, promise);
        }
    } else {
        final AbstractWriteTask task;
        if (flush) {
            task = WriteAndFlushTask.newInstance(next, m, promise);
        }  else {
            task = WriteTask.newInstance(next, m, promise);
        }
        if (!safeExecute(executor, task, promise, m)) {
            // We failed to submit the AbstractWriteTask. We need to cancel it so we decrement the pending bytes
            // and put it back in the Recycler for re-use later.
            //
            // See https://github.com/netty/netty/issues/8343.
            task.cancel();
        }
    }
}
  • 这里判定 outbound 的 executor 线程不是当前线程的时候,会将当前的工作封装成 task ,然后放入 mpsc 队列中,等待 IO 任务执行完毕后执行队列中的任务。
  • 这里可以Debug 来验证(提醒:Debug时,服务器端Debug ,客户端Run的方式),当我们使用了 group.submit(new Callable(){} 在handler 中加入线程池,就会进入到 safeExecute(executor, task, promise, m); 如果去掉这段代码,而使用普通方式来执行耗时的业务,那么就不会进入到 safeExecute(executor, task, promise, m);

处理方式二

  • 在 pipeline 进行添加 handler 时候,可以添加一个线程池。
static final EventExecutorGroup group = new DefaultEventExecutorGroup(16);

p.addLast(group, new EchoServerHandler());
  • handler 中的代码就使用普通的方式来处理耗时业务。
  • 当我们在调用 addLast 方法添加线程池后,handler 将优先使用这个线程池【即第一个参数 group 对应的线程池】,如果不添加,将使用 IO 线程。
  • 当走到 AbstractChannelHandlerContext 的 invokeChannelRead 方法的时候,executor.inEventLoop() 是不会通过的,因为当前线程是 IO 线程Context(也就是 Handler) 的 executor 是业务线程,所以会异步执行。
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
   final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeChannelRead(m);
    } else {
    	// 会走这里的异步处理逻辑
    	// 改成 p.addLast(new EchoServerHandler()); 则不会进到这里面,而是上面的逻辑
        executor.execute(new Runnable() {
            @Override
            public void run() {
                next.invokeChannelRead(m);
            }
        });
    }
}

两种方式的比较

  • 第一种方式在 handler 中添加异步,可能更加的自由,比如如果需要访问数据库,那就异步;如果不需要,就不异步,异步会拖长接口响应时间。因为需要将任务放进 mpscTask 中。如果IO 时间很短,task 很多,可能一个循环下来,都没时间执行整个 task,导致响应时间达不到指标。
  • 第二种方式是 Netty 标准方式(即加入到队列),但是,这么做会将整个 handler 都交给业务线程池。不论耗时不耗时,都加入到队列里,不够灵活。
  • 各有优劣,从灵活性考虑,第一种较好。

Netty实现 dubbo RPC

RPC基本介绍

  • RPC(Remote Procedure Call)— 远程过程调用,是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。
  • 两个或多个应用程序都分布在不同的服务器上,它们之间的调用都像是本地方法调用一样,如图:
    在这里插入图片描述
  • 常见的 RPC 框架有:比较知名的如阿里的Dubbo、google的gRPC、Go语言的rpcx、Apache的thrift, Spring 旗下的 Spring Cloud。

RPC调用流程图

在这里插入图片描述

  • ① **服务消费方(client)**以本地调用方式调用服务
  • ② client stub 接收到调用后负责将方法、参数等封装成能够进行网络传输的消息体
  • ③ client stub 将消息进行编码并发送到服务端
  • ④ server stub 收到消息后进行解码
  • ⑤ server stub 根据解码结果调用本地的服务
  • ⑥ 本地服务执行并将结果返回给 server stub
  • ⑦ server stub 将返回导入结果进行编码并发送至消费方
  • ⑧ client stub 接收到消息并进行解码
  • 服务消费方(client)得到结果
  • 小结:RPC 的目标就是将 2-8 这些步骤都封装起来,用户无需关心这些细节,可以像调用本地方法一样即可完成远程服务调用。

自己实现 dubbo RPC(基于Netty)

需求

  • dubbo 底层使用了 Netty 作为网络通讯框架,要求用 Netty 实现一个简单的 RPC 框架。
  • 模仿 dubbo,消费者和提供者约定接口和协议,消费者远程调用提供者的服务,提供者返回一个字符串,消费者打印提供者返回的数据,底层网络通信使用 Netty。

设计说明

  • 创建一个接口,定义抽象方法。用于消费者和提供者之间的约定。
  • 创建一个提供者,该类需要监听消费者的请求,并按照约定返回数据。
  • 创建一个消费者,该类需要透明的调用自己不存在的方法,内部需要使用 Netty 请求提供者返回数据。
    在这里插入图片描述

代码实现

  • 定义服务接口:
/**
 * 服务接口 提供者、消费者 都需要的
 */
public interface HelloService {
    String sayHello(String msg);
}
  • 定义服务端接口实现类:
public class HelloServiceImpl implements HelloService {
    private int counter = 0;

    @Override
    public String sayHello(String msg) {
        if (msg != null) {
            System.out.println("服务提供者收到消费者发送的消息:" + msg + " 第 " + (++counter) + " 次");
            return "Hello " + msg;
        } else {
            return "";
        }
    }
}
  • 定义服务端Netty服务、处理器和启动类:
public class NettyServer {
    public static void startServer(String hostname, int port) {
        startServer0(hostname, port);
    }

    private static void startServer0(String hostname, int port) {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new StringEncoder())
                                    .addLast(new StringDecoder())
                                    .addLast(new NettyServerHandler());
                        }
                    });
            System.out.println("服务端启动完成...");
            ChannelFuture channelFuture = serverBootstrap.bind(hostname, port).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    private static final String PROTOCOL_HEADER = "HelloService#hello#";
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 获取客户端发送的消息,并调用服务
        System.out.println("msg = " + msg);
        // 客户端在调用服务器的 api 时,我们需要定义一个协议
        // 比如我们要求 每次发消息是都必须以某个字符串开头 "HelloService#hello#你好"
        String content = msg.toString();
        if (content.startsWith(PROTOCOL_HEADER)) {
            String result = new HelloServiceImpl().sayHello(content.substring(PROTOCOL_HEADER.length()));
            ctx.writeAndFlush(result);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}
public class ProviderBootstrap {
    public static void main(String[] args) {
        NettyServer.startServer("127.0.0.1", 8888);
    }
}
  • 定义客户端Netty服务、处理器和启动类:
public class NettyClient {
    /**
     * 创建一个线程池
     */
    private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    private volatile static NettyClientHandler clientHandler;
    private int counter = 0;

    /**
     * 编写方法使用代理模式,获取一个代理对象
     */
    public Object getProxy(final Class<?> serviceClass, final String protocolHeader) {
        return Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                new Class<?>[]{serviceClass},
                (proxy, method, args) -> {
                    // 客户端每调用一次sayHello,就会进入该方法
                    System.out.println("(proxy, method, args) 进入第 " + (++counter) + " 次");
                    if (clientHandler == null) {
                        initClient();
                    }
                    // 设置要发给服务器端的信息
                    // providerName 就是协议头
                    // args[0]就是客户端调用API时传递的参数
                    clientHandler.setParam(protocolHeader + args[0]);
                    return executor.submit(clientHandler).get();
                }
        );
    }

    /**
     * 初始化客户端
     */
    private static void initClient() {
        clientHandler = new NettyClientHandler();
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new StringEncoder())
                                    .addLast(new StringDecoder())
                                    .addLast(clientHandler);
                        }
                    });
            bootstrap.connect("127.0.0.1", 8888).sync();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        // 这里不能关闭,否则有问题
//        finally {
//            group.shutdownGracefully();
//        }
    }
}
public class NettyClientHandler extends ChannelInboundHandlerAdapter implements Callable {
    /**
     * 上下文
     */
    private ChannelHandlerContext context;
    /**
     * 返回的结果
     */
    private String result;
    /**
     * 客户端调用方法时,传入的参数
     */
    private String param;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("--- Step one ---");
        context = ctx;
    }

    @Override
    public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("--- Step four ---");
        result = msg.toString();
        // 唤醒等待的线程
        notify();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

    /**
     * 被代理对象调用,发送数据给服务器,==>> wait ==>> 等待被唤醒(channelRead) ==>> 返回结果
     */
    @Override
    public synchronized Object call() throws Exception {
        System.out.println("--- Step three ---");
        context.writeAndFlush(param);
        // 进行wait
        wait();
        System.out.println("--- Step five ---");
        return result;
    }

    void setParam(String param) {
        System.out.println("--- Step two ---");
        this.param = param;
    }
}
public class ClientBootstrap {
    /**
     * 这里定义协议头
     */
    public static final String PROTOCOL_HEADER = "HelloService#hello#";

    public static void main(String[] args) throws Exception {
        // 创建一个消费者
        NettyClient consumer = new NettyClient();
        // 创建代理对象
        HelloService helloService = (HelloService) consumer.getProxy(HelloService.class, PROTOCOL_HEADER);
        // 通过代理对象调用提供者的方法
        String result = helloService.sayHello("Custom RPC");
        System.err.println("结果返回:" + result);

        do {
            TimeUnit.SECONDS.sleep(2);
            //通过代理对象调用服务提供者的方法(服务)
            String res = helloService.sayHello("dubbo~");
            System.out.println("调用的结果 res= " + res);
        } while (true);
    }
}
  • 测试结果:
    在这里插入图片描述
    在这里插入图片描述
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值