Java网络编程Netty
- Netty核心源码分析
- Netty启动过程源码分析
- Netty接收请求过程源码分析
- Pipeline、Handler、HandlerContext创建源码分析
- ChannelPipeline调度handler的源码分析
- Netty心跳服务源码分析
- Netty核心组件NioEventLoop源码分析
- handler中加入线程池和Context中添加线程池的源码分析
- Netty实现 dubbo RPC
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 NioMessageUnsafe对象。我们进入到AbstractNioMessageChannelNioMessageUnsafe 的 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 提供了 IdleStateHandler ,ReadTimeoutHandler,WriteTimeoutHandler 三个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);
}
}
- 测试结果: