Netty源码解析
Netty源码解析
示例
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
// 创建EventLoopGroup
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 2.创建ServerBootstrap,并进行配置
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.handler(new LoggingHandler(LogLevel.INFO))
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println("server is ready...");
// 3.绑定端口6666,获取ChannelFuture
ChannelFuture cf = serverBootstrap.bind(6666).sync();
cf.channel().closeFuture().sync();
cf.addListener((future -> {
if (cf.isSuccess()) {
System.out.println("bind successfully");
} else {
System.out.println("bind failed");
}
}));
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Thread---"+Thread.currentThread().getName());
System.out.println("ctx---"+ctx);
Channel channel = ctx.channel();
ChannelPipeline pipeline = ctx.pipeline();
ByteBuf byteBuffer = (ByteBuf)msg;
System.out.println("msg---"+byteBuffer.toString(CharsetUtil.UTF_8));
System.out.println("address---"+channel.remoteAddress());
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("coplete...", CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyClientHandler());
}
});
ChannelFuture future = bootstrap.connect("127.0.0.1", 6668);
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("client---"+ctx);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf)msg;
System.out.println("msg---"+byteBuf);
System.out.println("address---"+ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
NioEventLoopGroup的创建
首先是NioEventLoopGroup的创建
lNioEventLoopGroup是整个Netty的核心,NioEventLoopGroup可以看成是一组线程,里面每个EventLoop就是其中一个线程,每个EventLoop又会有一个Selector,循环监听注册在他上面的Channel的事件
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
构造方法会有很深的调用链,一直调用到父类的MultithreadEventLoopGroup
io.netty.channel.MultithreadEventLoopGroup#MultithreadEventLoopGroup(int, java.util.concurrent.Executor, java.lang.Object…)
static {
// 默认线程一个NioEventLoopGroup启动的线程数为 cup核心数*2,一个线程代表一个EventLoop
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
}
}
/**
* @see MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(int, Executor, Object...)
*/
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
// 如果指定了线程,则使用指定的线程数,否则使用默认线程cup*2
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
// DefaultEventExecutorChooserFactory 默认的 EventExecutorChooserFactory类型
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
io.netty.util.concurrent.MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(int, java.util.concurrent.Executor, io.netty.util.concurrent.EventExecutorChooserFactory, java.lang.Object…)
/**
* 创建一个实例
*
* @param nThreads 此实例将使用的线程数。
* @param executor 要使用的执行器,或{@code null}(如果应该使用默认值)。
* @param chooserFactory 要使用的{@link EventExecutorChooserFactory}。
* @param args 将传递给每个{@link #newChild(Executor, Object…)}调用的参数
*/
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());
}
//创建指定线程数的EventLoop数组
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
// 遍历创建NioEventLoop
// NioEventLoop是顶层接口,NioEventLoop是EventExecutor接口的间接实现类
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 ++) {
// 创建失败,则关闭所有的NioEventLoop
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);
}
}
};
//为每一个单例线程池(NioEventLoop)添加一个关闭监听器
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
//将所有的单例线程池添加到成员变量readonlyChildren (一个 HashSet) 中。
// 所以表明,一个NioEventLoopGroup包含一组NioEventLoop
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
从上面启动函数可以看出,创建NioEventLoopGroup,默认一个NioEventLoopGroup会启动cpu核心数*2个线程(其实是一个单例线程池),每个线程对应一个NioEventLoop。一个NioEventLoop对应一个Selector,一个taskQueue
ServerBootstrap 创建和配置
ServerBootstrap是一个引导类,继承了父类AbstractBootstrap,用于引导Netty的配置和启动。
相关属性
io.netty.bootstrap.ServerBootstrap
// 关于workerGroup的TCP 参数
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);
// workerGroup本身(这里是NioEventLoopGroup)
private volatile EventLoopGroup childGroup;
// 给workerGroup配置的ChannelHandler,用于处理发生在SocketChannel上的事件(读写)
private volatile ChannelHandler childHandler;
io.netty.bootstrap.AbstractBootstrap
// bossGroup本身(这里是NioEventLoopGroup)
volatile EventLoopGroup group;
@SuppressWarnings("deprecation")
// 用于反射创建Channel的过程(这里的channel是NioServerSocketChannel)
private volatile ChannelFactory<? extends C> channelFactory;
// 绑定的端口的Socket地址对象
private volatile SocketAddress localAddress;
// 关于bossGroup的TCP 参数
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();
private final Map<AttributeKey<?>, Object> attrs = new LinkedHashMap<AttributeKey<?>, Object>();
// 给bossGroup配置的ChannelHandler,用于处理发生在ServerSocketChannel的事件(连接)
private volatile ChannelHandler handler;
ServerBootstrap的构造器是个空构造
io.netty.bootstrap.ServerBootstrap#ServerBootstrap()
public ServerBootstrap() { }
配置两个NioEventLoopGroup到
serverBootstrap.group(bossGroup, workerGroup)
io.netty.bootstrap.ServerBootstrap#group(io.netty.channel.EventLoopGroup, io.netty.channel.EventLoopGroup)
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup);
...
// workerGroup保存在ServerBootstrap 中的成员变量childGroup中
this.childGroup = childGroup;
return this;
}
io.netty.bootstrap.AbstractBootstrap#group(io.netty.channel.EventLoopGroup)
public B group(EventLoopGroup group) {
...
// bossgroup配置在父类成员变量group
this.group = group;
return self();
}
配置bossGroup的通道类型,这里是NioServerSocketChannel
io.netty.bootstrap.AbstractBootstrap#channel
/ * *
* 用于创建实例的{@link Channel}的{@link类}。
* 您可以使用this或{@link #channelFactory(io.netty.channel.ChannelFactory)},如果您需要
* {@link Channel}实现没有无参数构造函数。
* /
public B channel(Class<? extends C> channelClass) {
...
// 根据传进来的Channel类型创建channel工厂
// 工厂类型为ReflectiveChannelFactory,代表是基于反射的方式去创建Channel的
return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
io.netty.bootstrap.AbstractBootstrap#channelFactory(io.netty.channel.ChannelFactory<? extends C>)
public B channelFactory(io.netty.channel.ChannelFactory<? extends C> channelFactory) {
return channelFactory((ChannelFactory<C>) channelFactory);
}
io.netty.bootstrap.AbstractBootstrap#channelFactory(io.netty.bootstrap.ChannelFactory<? extends C>)
@Deprecated
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
...
// 根据传进来的Channel类型创建channel工厂,并保存到成员变量中。
this.channelFactory = channelFactory;
return self();
}
为bossGroup配置一些参数
io.netty.bootstrap.AbstractBootstrap#option
/**
* Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they got
* created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
*/
public <T> B option(ChannelOption<T> option, T value) {
if (option == null) {
throw new NullPointerException("option");
}
if (value == null) {
synchronized (options) {
options.remove(option);
}
} else {
synchronized (options) {
// 保存配置到成员遍历options中(一个 LinkedHashMap)
options.put(option, value);
}
}
return self();
}
为WorkerGroup配置一些参数
io.netty.bootstrap.ServerBootstrap#childOption
public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) {
if (childOption == null) {
throw new NullPointerException("childOption");
}
if (value == null) {
synchronized (childOptions) {
childOptions.remove(childOption);
}
} else {
synchronized (childOptions) {
// 保存配置到成员变量childOptions中(一个 LinkedHashMap)
childOptions.put(childOption, value);
}
}
return this;
}
为bossGroup配置Handler(ChannelHandler)
io.netty.bootstrap.AbstractBootstrap#handler(io.netty.channel.ChannelHandler)
public B handler(ChannelHandler handler) {
if (handler == null) {
throw new NullPointerException("handler");
}
this.handler = handler;
return self();
}
为workerGroup配置Handler(ChannelHandler)
io.netty.bootstrap.ServerBootstrap#childHandler(io.netty.channel.ChannelHandler)
public ServerBootstrap childHandler(ChannelHandler childHandler) {
if (childHandler == null) {
throw new NullPointerException("childHandler");
}
// 保存ChannelHandler 到成员变量childHandler 中,这里是一个ChannelInitializer
// ChannelInitializer 也是Channel处理器中的一种,只是作用比较特殊
// 主要用于追加其他的ChannelHandler,自身本身并不做业务处理
this.childHandler = childHandler;
return this;
}
端口绑定,启动服务端
上面只是做了启动需要的配置,还没真正启动。在这里才会创建Channel并且绑定端口,返回ChannelFuture。这里的Channel是NioServerSocketChannel,他间接实现了接口Channel,NioServerSocketChannel的创建依赖于刚刚ServerBootstrap的配置时,通过NioServerSocketChannel的Class对象创建的ChannelFactory,他会反射创建NioServerSocketChannel。而ChannelFuture可以看成是Future,因为他也间接实现了Future,这里Channel的初始化和绑定端口是一个异步的动作,所以要返回一个Future。
serverBootstrap.bind(6666).sync();
io.netty.bootstrap.AbstractBootstrap#bind(int)
/**
* 创建NioServerSocketChannel并绑定端口.
*/
public ChannelFuture bind(int inetPort) {
// 端口号封装成InetSocketAddress
return bind(new InetSocketAddress(inetPort));
}
/**
* 创建NioServerSocketChannel并绑定端口.
*/
public ChannelFuture bind(SocketAddress localAddress) {
...
// 调用dobing方法
return doBind(localAddress);
}
io.netty.bootstrap.AbstractBootstrap#doBind
private ChannelFuture doBind(final SocketAddress localAddress) {
// 初始化并且注册NioServerSocketChannel,然后返回ChannelFuture
// ChannelFuture 可以看成就是 Future,实际上他间接实现了future接口
// 真正类型是 DefaultChannelPromise
final ChannelFuture regFuture = initAndRegister();
// 通过ChannelFuture 获取 Channel(NioServerSocketChannel)
final Channel channel = regFuture.channel();
// 初始化NioServerSocketChannel,不绑定端口,直接返回
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {
// 至此,我们知道注册是完整和成功的。
ChannelPromise promise = channel.newPromise();
// 执行 doBind0 方法,完成对端口的绑定
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// Registration future is almost always fulfilled already, but just in case it's not.
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
// 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;
}
}
NioServerSocketChannel的创建和初始化
io.netty.bootstrap.AbstractBootstrap#initAndRegister
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 利用ReflectiveChannelFactory反射创建NioServerSocketChannel
channel = channelFactory.newChannel();
// 初始化NioServerSocketChannel
init(channel);
} catch (Throwable t) {
if (channel != null) {
channel.unsafe().closeForcibly();
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
// 注册NioServerSocketChannel并返回ChannelFuture 对象
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
// 返回ChannelFuture 对象
return regFuture;
}
NioServerSocketChannel的创建
先看反射实例化的过程channel = channelFactory.newChannel();
ReflectiveChannelFactory是通过NioServerSocketChannel的无参构造反射实例化ReflectiveChannelFactory的,
io.netty.channel.ReflectiveChannelFactory#newChannel
@Override
public T newChannel() {
try {
return clazz.getConstructor().newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}
NioServerSocketChannel的无参构造,无参构造会调用重载的构造。在这之前会先调用newSocket方法,获取jdk自带的ServerSocketChannel实现类ServerSocketChannelImpl。然后再交给重载的构造,对原生的ServerSocketChannel进行包装,变成NioServerSocketChannel
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
io.netty.channel.socket.nio.NioServerSocketChannel#newSocket
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
return provider.openServerSocketChannel();
} catch (IOException e) {
throw new ChannelException("Failed to open a server socket.", e);
}
}
sun.nio.ch.SelectorProviderImpl#openServerSocketChannel
可以看到返回的就是ServerSocketChannelImpl类型的Channel
public ServerSocketChannel openServerSocketChannel() throws IOException {
return new ServerSocketChannelImpl(this);
}
获取到的ServerSocketChannelImpl,通过重载的构造进行包装
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
// 创建了一个 NioServerSocketChannelConfig 对象,用于对外展示一些配置
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
上面的第一行调用父类构造的代码,会一步一步调用到这里
io.netty.channel.AbstractChannel#AbstractChannel(io.netty.channel.Channel)
protected AbstractChannel(Channel parent) {
// 保存ServerSocketChannelImpl对象为当parent属性
this.parent = parent;
// 创建一个唯一的 ChannelId
id = newId();
// 创建一个 NioMessageUnsafe,用于操作消息
unsafe = newUnsafe();
// 创建一个pipeline( DefaultChannelPipeline) ,一个Channel对应一个pipeline
// pipeline 是个双向链表结构,用于过滤所有的进出的消息
pipeline = newChannelPipeline();
}
反射实例化的过程到这里结束了,返回的是一个包装了原生ServerSocketChannelImpl的包装对象NioServerSocketChannel。
接下来看Channel的初始化init(channel);
NioServerSocketChannel的初始化
init 方法是 AbstractBootstrap 类的抽象方法,由子类 ServerBootstrap 实现
io.netty.bootstrap.ServerBootstrap#init
@Override
void init(Channel channel) throws Exception {
final Map<ChannelOption<?>, Object> options = options0();
// 设置 NioServerSocketChannel 的 的 TCP 属性
// 由于 LinkedHashMap 是非线程安全的,使用同步进行处理
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());
}
}
// 获取绑定在NioServerSocketChannel上的pipeline
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(childOptions.size()));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}
// 对 NioServerSocketChannel 的 ChannelPipeline 添加 ChannelInitializer 处理器
// pipeline 是一个双向循环链表,本身有head 和 tail两个节点,
// 这里把ChannelInitializer 添加到 tail 的前面,tail 永远会在后面做一些系统的固定工作
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));
}
});
}
});
}
下面看调用ChannelPipeline 的addLast所做的工作
addLast 方法在 DefaultChannelPipeline 类中 是 pipeline 的核心,
里面会创建 一 个 AbstractChannelHandlerContext 对象 然后添加到pipeline所管理的链表的尾部前一个节点 。ChannelHandlerContext 对 象 是ChannelHandler 和 ChannelPipeline 之间的关联,每当有 ChannelHandler 添加到 Pipeline 中时,都会创建Context。Context 的主要功能是管理他所关联的 Handler 和同一个 Pipeline 中的其他 Handler 之间的交互。
io.netty.channel.DefaultChannelPipeline#addLast(io.netty.util.concurrent.EventExecutorGroup, java.lang.String, io.netty.channel.ChannelHandler)
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
// 这里group 和 name 为空
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler);
// 包装一个ChannelHandlerContext对象
newCtx = newContext(group, filterName(name, handler), handler);
// 插入到当前pipeline的链表中
addLast0(newCtx);
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
// 最后,同步或者异步或者晚点异步的调用 callHandlerAdded0 方法
callHandlerAdded0(newCtx);
return this;
}
可以看到这里就是插入到tail的前面
io.netty.channel.DefaultChannelPipeline#addLast0
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
端口绑定
NioServerSocketChannel的创建和初始化已经看完了(也就是initAndRegister()方法),然后是端口绑定
io.netty.bootstrap.AbstractBootstrap#doBind0
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
// 这里channel就是刚刚额NioServerSocketChannel
// 通过NioServerSocketChannel获取NioEventLoop
// 添加一个任务Runnable到当前EventLoop的taskQueue中
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
// 会调用到NioServerSocketChannel的bind方法
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
最后会调用到NioServerSocketChannel的bind方法
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
// javaChannel返回的是Nio原生的Channel,然后绑定端口
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
最终会注册一个 Accept 事件等待客户端的连接
然后最后就会进入到NioEventLoop里面,进行循环的监听了
@Override
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
// Selector.select
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
// 处理selectedKeys,也就是监听到的事件
processSelectedKeys();
} finally {
// 执行所有的任务.
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);
}
}
}
接收客户端连接请求,注册Channel到workGroup中
上面已经启动了服务端,现在EventLoop 中就可以接收客户端的连接了
EventLoop 的作用是一个死循环,而这个循环中做 3 件事情:
- 有条件的等待 Nio 事件 select(wakenUp.getAndSet(false));
- 处理 Nio 事件。processSelectedKeys();
- 处理消息队列中的任务。runAllTasks();
启动客户端,触发连接事件。当监听到连接事件时,会进入以下代码
io.netty.channel.nio.NioEventLoop#processSelectedKey(java.nio.channels.SelectionKey, io.netty.channel.nio.AbstractNioChannel)
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
...
// readyOps 为16,代表accept事件
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
// 调用unsafe的read方法
// unsafe 是 boss 线程中 NioServerSocketChannel 的 AbstractNioMessageChannel$NioMessageUnsafe 对象
unsafe.read();
}
}
io.netty.channel.nio.AbstractNioMessageChannel.NioMessageUnsafe#read
@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;
}
int size = readBuf.size();
// 遍历容器
// 循环调用 ServerSocket 的 pipeline 的 fireChannelRead 方法,
// 开始执行 管道中的
for (int i = 0; i < size; i ++) {
readPending = false;
// 循环遍历 容器中的所有请求,
// 调用 pipeline 的 fireChannelRead 方法,
// 用于处理这些接受的请求或者其他事件,
handler 的 ChannelRead 方法
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 {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
进入doReadMessages方法,他做的事情就是通过原生的serverSocketChannel 的accept 方法,获取到SocketChannel,然后封装成NioSocketChannel后,放入到容器buf中
io.netty.channel.socket.nio.NioServerSocketChannel#doReadMessages
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
// 通过工具类,调用 NioServerSocketChannel 内部
// 封装的 serverSocketChannel 的 accept 方法
// 获取到一个 JDK 的 SocketChannel。
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
// 然后,使用 NioSocketChannel 进行封装
// 把 NioSocketChannel 添加到buf容器中
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
...
}
return 0;
}
回到 read 方法,继续分析 循环执行 pipeline.fireChannelRead 方法
前面分析 doReadMessages 方法的作用是通过 ServerSocket 的 accept 方法获取到 Tcp 连接,然后封装成Netty 的 NioSocketChannel 对象,最后添加到 容器中。
在 read 方法中,循环调用 ServerSocket 的 pipeline 的 fireChannelRead 方法, 开始执行 管道中的 handler 的 ChannelRead 方法
可以看到会反复执行多个 handler 的 ChannelRead ,我们知道,pipeline 里面又 4 个 handler ,分别是 Head ,LoggingHandler ,ServerBootstrapAcceptor ,Tail
我们重点看看 ServerBootstrapAcceptor
io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(java.lang.Object)
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
// 调用handler的channelRead方法
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRead(msg);
}
}
进入到ServerBootstrapAcceptor的channelRead方法,可以看到调用了wokerGroup的register方法,注册了该Channel
@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 刚刚中获取的NioSocketChannel
final Channel child = (Channel) msg;
// 给当前 NioSocketChannel 的管道添加之前配置的 ChannelInitializer
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 注册到 worker 线程池(最开始创建的workerGroup)
// 并添加一个监听器
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);
}
}
因为workerGroup的类型是NioEventLoopGroup,继承了MultithreadEventLoopGroup,所以workerGroup的register方法首先进入MultithreadEventLoopGroup的register方法
io.netty.channel.MultithreadEventLoopGroup#register(io.netty.channel.Channel)
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
next方法会返回一个NioEventLoop
io.netty.util.concurrent.DefaultEventExecutorChooserFactory.PowerOfTwoEventExecutorChooser#next
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
接着往下…,会到
io.netty.channel.AbstractChannel.AbstractUnsafe#register
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
...
// 直接看这一行,其他全省略
register0(promise);
...
}
然后到beginRead(),然后doBeginRead(),都在AbstractUnsafe类的内部
直到最后会到AbstractNioChannel的doBeginRead
io.netty.channel.nio.AbstractNioChannel#doBeginRead
@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 的内部类,该方法内部由 2 部分组成
- doReadMessages 用于创建 NioSocketChannel 对象,该对象包装 JDK 的 Nio Channel 客户端。该方法会像创建 ServerSocketChanel 类似创建相关的 pipeline , unsafe,config
- 随后执行 执行 pipeline.fireChannelRead 方法,并将自己绑定到一个 chooser 选择器选择的 workerGroup 中的一个 EventLoop。并且注册一个 0,表示注册成功,但并没有注册读(1)事件
ChannelPipeline,ChannelHandler,ChannelHandlerContext的创建
Netty 中的 ChannelPipeline 、 ChannelHandler 和 ChannelHandlerContext 是非常核心的组件
三者关系
- 每当 ServerSocket 创建一个新的连接,就会创建一个 Socket,对应的就是目标客户端。
- 每一个新创建的 Socket 都将会分配一个全新的 ChannelPipeline(以下简称 pipeline)
- 每一个 ChannelPipeline 内部都含有多个 ChannelHandlerContext(以下简称 Context)
- 他们一起组成了双向链表,这些 Context 用于包装我们调用 addLast 方法时添加的 ChannelHandler(以下简称handler)
ChannelSocket 和 ChannelPipeline 是一对一的关联关系,而 pipeline 内部的多个 Context 形成了链
表,Context 对 只是对 Handler 的封装。
当一个请求进来的时候,会进入 Socket 对应的 pipeline,并经过 pipeline 所有的 handler,对,就是设计模式中的过滤器模式。
ChannelPipeline
io.netty.channel.ChannelPipeline
public interface ChannelPipeline
extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable<Entry<String, ChannelHandler>> {
ChannelPipeline接口继承了 inBound,outBound,Iterable 接口,表示他可以调用 数据出站的方法和入站的方法,同时也能遍历内部的链表, 看看他的几个代表性的方法,基本上都是针对 handler 链表的插入,追加,删除,替换操作,类似是一个 LinkedList。同时,也能返回 channel(也就是 socket)
在 pipeline 的接口文档上,提供了一幅图
* <pre>
* I/O Request
* via {@link Channel} or
* {@link ChannelHandlerContext}
* |
* +---------------------------------------------------+---------------+
* | ChannelPipeline | |
* | \|/ |
* | +---------------------+ +-----------+----------+ |
* | | Inbound Handler N | | Outbound Handler 1 | |
* | +----------+----------+ +-----------+----------+ |
* | /|\ | |
* | | \|/ |
* | +----------+----------+ +-----------+----------+ |
* | | Inbound Handler N-1 | | Outbound Handler 2 | |
* | +----------+----------+ +-----------+----------+ |
* | /|\ . |
* | . . |
* | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
* | [ method call] [method call] |
* | . . |
* | . \|/ |
* | +----------+----------+ +-----------+----------+ |
* | | Inbound Handler 2 | | Outbound Handler M-1 | |
* | +----------+----------+ +-----------+----------+ |
* | /|\ | |
* | | \|/ |
* | +----------+----------+ +-----------+----------+ |
* | | Inbound Handler 1 | | Outbound Handler M | |
* | +----------+----------+ +-----------+----------+ |
* | /|\ | |
* +---------------+-----------------------------------+---------------+
* | \|/
* +---------------+-----------------------------------+---------------+
* | | | |
* | [ Socket.read() ] [ Socket.write() ] |
* | |
* | Netty Internal I/O Threads (Transport Implementation) |
* +-------------------------------------------------------------------+
* </pre>
- 这是一个 handler 的 list,handler 用于处理或拦截入站事件和出站事件,pipeline 实现了过滤器的高级形式,以便用户控制事件如何处理以及 handler 在 pipeline 中如何交互。
- 上图描述了一个典型的 handler 在 pipeline 中处理 I/O 事件的方式,IO 事件由 inboundHandler 或者outBoundHandler 处理,并通过调用 ChannelHandlerContext.fireChannelRead 方法转发给其最近的处理程序 。
- 入站事件由入站处理程序以自下而上的方向处理,如图所示。入站处理程序通常处理由图底部的 I / O 线程生成入站数据。入站数据通常从如 SocketChannel.read(ByteBuffer) 获取。
- 个 通常一个 e pipeline 个 有多个 handler,例如,一个典型的服务器在每个通道的管道中都会有以下处理程序
协议解码器 - 将二进制数据转换为 Java 对象。
协议编码器 - 将 Java 对象转换为二进制数据。
业务逻辑处理程序 - 执行实际业务逻辑(例如数据库访问) - 你的业务程序不能将线程阻塞,会影响 IO 的速度,进而影响整个 Netty 程序的性能。如果你的业务程序很快,就可以放在 IO 线程中,反之,你需要异步执行。或者在添加 handler 的时候添加一个线程池,例如:
下面这个任务执行的时候,将不会阻塞 IO 线程,执行的线程来自 group 线程池
pipeline.addLast(group,“handler”,new MyBusinessLogicHandler());
io.netty.channel.ChannelHandlerContext#fireChannelRead
@Override
ChannelHandlerContext fireChannelRead(Object msg);
io.netty.channel.AbstractChannelHandlerContext#fireChannelRead
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
// invokeChannelRead处理Channel的入站事件
// 寻找下一个Inbound类型的ChannelHandlerContext
invokeChannelRead(findContextInbound(), msg);
return this;
}
io.netty.channel.AbstractChannelHandlerContext#findContextInbound
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
// context的next获取下一个context,查看是否是Inbound类型,是则返回
ctx = ctx.next;
} while (!ctx.inbound);
return ctx;
}
出站的时候则相反
io.netty.channel.AbstractChannelHandlerContext#findContextOutbound
private AbstractChannelHandlerContext findContextOutbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.prev;
} while (!ctx.outbound);
return ctx;
}
所以Context是通过inbound和outbond两个boolean值标识他是出站还是入站的Context
ChannelHandler
io.netty.channel.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;
}
ChannelHandler 的作用就是处理 IO 事件或拦截 IO 事件,并将其转发给下一个处理程序ChannelHandler。
Handler 处理事件时分入站和出站的,两个方向的操作都是不同的,因此,Netty 定义了两个子接口继承
ChannelHandler
- ChannelInboundHandler 入站事件接口
- channelActive 用于当 Channel 处于活动状态时被调用;
- channelRead 当从 Channel 读取数据时被调用等等方法。
- 程序员需要重写一些方法,当发生关注的事件,需要在方法中实现我们的业务逻辑,因为当事件发生时,Netty 会回调对应的方法。
- ChannelOutboundHandler 出站事件接口
- bind 方法,当请求将 Channel 绑定到本地地址时调用
- close 方法,当请求关闭 Channel 时调用等等
- 出站操作都是一些连接和写出数据类似的方法。
- ChannelDuplexHandler 处理出站和入站事件
- ChannelDuplexHandler 间接实现了入站接口并直接实现了出站接口。
- 是一个通用的能够同时处理入站事件和出站事件的类。
ChannelHandlerContext
ChannelHandlerContext 继承了出站方法调用接口和入站方法调用接口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 中组成了双向链表。
在上面分析NioServerSocketChannel创建的时候可以看到pipeline的创建
io.netty.channel.AbstractChannel#AbstractChannel(io.netty.channel.Channel)
protected AbstractChannel(Channel parent) {
// 保存ServerSocketChannelImpl对象为当parent属性
this.parent = parent;
// 创建一个唯一的 ChannelId
id = newId();
// 创建一个 NioMessageUnsafe,用于操作消息
unsafe = newUnsafe();
// 创建一个pipeline( DefaultChannelPipeline) ,一个Channel对应一个pipeline
// pipeline 是个双向链表结构,用于过滤所有的进出的消息
pipeline = newChannelPipeline();
}
沿着newChannelPipeline方法追踪到达
io.netty.channel.DefaultChannelPipeline#DefaultChannelPipeline
protected DefaultChannelPipeline(Channel channel) {
// 此时channel还是空的,经过这一步后,channel就被赋值为NioServerSocketChannel类型
this.channel = ObjectUtil.checkNotNull(channel, "channel");
// 创建一个 future 和 promise,用于异步回调使用
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
// ChannelPipeline的尾部节点的ChannelHandlerContext, 一个 inbound 的 tailContext
tail = new TailContext(this);
// ChannelPipeline的头部节点的ChannelHandlerContext
// 一个既是 inbound 类型又是 outbound 类型的 headContext
head = new HeadContext(this);
// hean和tail连接起来形成双向链表
head.next = tail;
tail.prev = head;
}
pipeline添加Handler时创建Context
io.netty.channel.DefaultChannelPipeline#addLast(io.netty.util.concurrent.EventExecutorGroup, java.lang.String, io.netty.channel.ChannelHandler)
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
// 检查这个 handler 实例是否是共享的,如果不是,并且已经被别的 pipeline 使用了,则抛出异常。
checkMultiplicity(handler);
// 调用 newContext(group, filterName(name, handler), handler) 方法 ,个 创建一个 Context。 。 从这里可以看出来了 ,个 每次添加一个 handler 联 都会创建一个关联 Context。
newCtx = newContext(group, filterName(name, handler), handler);
// 调用 addLast 将 方法,将 Context 追加到链表中。
addLast0(newCtx);
// 如果这个通道还没有注册到 selecor 上,
// 就将这个 Context 添加到这个 pipeline 的待办任务中。
// 当注册好了以后,就会调用 callHandlerAdded0 方法(默认是什么都不做,用户可以实现这个方法)。
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
Pipeline Handler HandlerContext 创建过程梳理
到这里,针对三对象创建过程,了解的差不多了,和最初说的一样,每当创建 ChannelSocket 的时候都会创建一个绑定的 pipeline,一对一的关系,创建 pipeline 的时候也会创建 tail 节点和 head 节点,形成最初的链表。tail是入站 inbound 类型的 handler, head 既是 inbound 也是 outbound 类型的 handler。在调用 pipeline 的 addLast方法的时候,会根据给定的 handler 创建一个 Context,然后,将这个 Context 插入到链表的尾端(tail 前面)。到此就 OK 了
- 每当创建 ChannelSocket 的时候都会创建一个绑定的 pipeline,一对一的关系,创建 pipeline 的时候也会创建tail 节点和 head 节点,形成最初的链表。
- 在调用 pipeline 的 addLast 方法的时候,会根据给定的 handler 创建一个 Context,然后,将这个 Context 插入到链表的尾端(tail 前面)。
- Context 包装 handler,多个 Context 在 pipeline 中形成了双向链表
- 入站方向叫 inbound,由 head 节点开始,出站方法叫 outbound ,由 tail 节点开始
ChannelPipeline 调度 handler 的源码剖析
入站相关方法
当一个请求进来的时候,会第一个调用 pipeline 的 相关方法,如果是入站事件,这些方法由 fire 开头,
表示开始管道的流动。让后面的 handler 继续处理
ChannelPipeline的真实类型是DefaultChannelPipeline,当有入站事件发生时,就会首先触发ChannelPipeline中的fireChannelActive方法,然后执行其他的firexxx方法。这些方法都是 inbound 的方法。里面是一个静态方法的调用,会调用 head 的 ChannelInboundInvoker 接口的方法,再然后调用 handler 的真正方法
io.netty.channel.DefaultChannelPipeline#fireChannelActive
@Override
public final ChannelPipeline fireChannelActive() {
AbstractChannelHandlerContext.invokeChannelActive(head);
return this;
}
这里挑一个fireChannelRead方法看一下,启动浏览器,再启动客户端。可以看到调用到了
fireChannelRead。
io.netty.channel.DefaultChannelPipeline#fireChannelRead
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
// 调用AbstractChannelHandlerContext的静态方法
// head是当前pipeline的头结点ChannelHandlerContext
// msg 类型是NioSocketChannel
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
// 获取NioEventLoop,Context -> pipeline -> channel -> NioEventLoop
EventExecutor executor = next.executor();
// 判断当前线程是否是本NioEventLoop
// 是则直接在本线程执行操作
if (executor.inEventLoop()) {
// 执行next的invokeChannelRead,第一次进来是head
next.invokeChannelRead(m);
} else {
// executor 不是本线程,则调用 execute 方法异步执行
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRead(m);
}
});
}
}
next是AbstractChannelHandlerContext,而当前也是通过AbstractChannelHandlerContext的静态方法调用进来的,所以invokeChannelRead方法会进入到当前类的invokeChannelRead方法,而invokeChannelRead就会取到与当前Context关联的Handler,执行对应的方法(这里是channelRead)。但是当前Context是头结点head,head获取handler的操作较特殊,他会返回自身 (return this;)。
io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(java.lang.Object)
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
// 从当前 Context 中取出与之关联的 Handler,并强转成ChannelInboundHandler,
// 然后执行对应handler的相关操作方法,如channelRead。
// 当前 ChannelHandlerContext 是 hear,
// 而 hear 的真实类型是 DefaultChannelPipeline 的内部类 HeadContext,
// HeadContext 的 handler()里面是return this,所以还是当前 hear 对象。
// HeadContext 除了继承了父类AbstractChannelHandlerContext,也实现了
// ChannelOutboundHandler, ChannelInboundHandler两个接口,
// 所以他既是Context,也是handler。
// 调用hear的channelRead方法,进入HeadContext的channelRead方法
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRead(msg);
}
}
io.netty.channel.DefaultChannelPipeline.HeadContext#channelRead
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// ctx 还是 head,msg还是NIOSocketChannel
ctx.fireChannelRead(msg);
}
上面方法中,ctx 还是 head,msg 还是 NIOSocketChannel,所以会调用 hear 的 fireChannelRead 方法,进入到 AbstractChannelHandlerContext 的 fireChannelRead 方法。也就是说现在是从 DefaultChannelPipeline 的 fireChannelRead,到了HeadContext 的 fireChannelRead
io.netty.channel.AbstractChannelHandlerContext#fireChannelRead
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
invokeChannelRead(findContextInbound(), msg);
return this;
}
findContextInbound方法是找到链表中下一个处理入站事件的ChannelHandlerContext
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while (!ctx.inbound);
return ctx;
}
io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
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);
}
});
}
}
所以这里又回到了AbstractChannelHandlerContext的invokeChannelRead方法,只是现在的next不是head节点了,而是下一个inbound的ChannelHandlerContext,并且取出的handler也是真正和context关联的handler(return handler;),就会调用到我们注册进workerGroup的handler的相关方法。以此类推,就循环调用了链表上所有的inbound相关的ChannelHandlerContext
出站相关方法
io.netty.channel.DefaultChannelPipeline#bind(java.net.SocketAddress, io.netty.channel.ChannelPromise)
@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return tail.bind(localAddress, promise);
}
可以看到出站首先会调用tail节点
小结
pipeline 首先会调用 Context 的静态方法 fireXXX,并传入 Context。然后,静态方法调用 Context 的 invoker 方法,而 invoker 方法内部会调用该 Context 所包含的 Handler 的真正的 XXX 方法,调用结束后,如果还需要继续向后传递,就调用 Context 的 fireXXX2 方法,循环往复。
Netty心跳服务
Netty 作为一个网络框架,提供了诸多功能,比如编码解码等,Netty 还提供了非常重要的一个服务-----心跳机制 heartbeat。我们需要在心跳检测Handle(如IdleStateHandler)后添加一个自定义的Handler,重写userEventTiggered方法,用于接收心跳检测Handler的处理结果(如IdleStateEvent),进行相应的处理。通过心跳检查对方是否有效,这是 RPC 框架中是必不可少的功能。
IdleStateHandler的4个重要属性
private final boolean observeOutput; //是否考虑出站时较慢的情况。默认值是 false
private final long readerIdleTimeNanos;//读事件空闲时间,0 则禁用事件
private final long writerIdleTimeNanos;//写事件空闲时间,0 则禁用事件
private final long allIdleTimeNanos;//读或写空闲时间,0 则禁用事件
当该 handler 被添加到 pipeline 中时,则调用handlerAdded方法,进而调用 initialize 方法
io.netty.handler.timeout.IdleStateHandler#handlerAdded
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
initialize(ctx);
} else {
}
}
进入initialize方法
io.netty.handler.timeout.IdleStateHandler#initialize
private void initialize(ChannelHandlerContext ctx) {
switch (state) {
case 1:
case 2:
return;
}
state = 1;
// 调用 initOutputChanged 方法,初始化 “监控出站数据属性”
initOutputChanged(ctx);
lastReadTime = lastWriteTime = ticksInNanos();
// 只要给定的参数大于 0,就创建一个定时任务,
// 每个事件都创建。同时,将 state 状态设置为 1,防止重复初始化。
if (readerIdleTimeNanos > 0) {
// 这里的 schedule 方法会调用 eventLoop 的 schedule 方法,将定时任务添加进队列中
// 然后返回一个ScheduledFuture类型的对象,是一个Future
// 保存到 IdleStateHandler 的成员变量
readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),
readerIdleTimeNanos, TimeUnit.NANOSECONDS);
}
if (writerIdleTimeNanos > 0) {
// writerIdleTimeout 与 readerIdleTimeout 一样,一个ScheduledFuture对象
writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),
writerIdleTimeNanos, TimeUnit.NANOSECONDS);
}
if (allIdleTimeNanos > 0) {
// allIdleTimeout 也是 ScheduledFuture对象
allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),
allIdleTimeNanos, TimeUnit.NANOSECONDS);
}
}
上面调用initOutputChanged方法会初始化上面罗列的IdleStateHandler4个重要属性,然后根据3个超时时间是否大于0,创建定时任务。而ReaderIdleTimeoutTask,WriterIdleTimeoutTask,AllIdleTimeoutTask 均属于IdleStateHandler 的 内部类。这三个任务类共有一个父类(AbstractIdleTask,也是内部类)。这个父类提供了一
个模板方法
io.netty.handler.timeout.IdleStateHandler.AbstractIdleTask
/* AbstractIdleTask 实现了Runnable 接口 */
private abstract static class AbstractIdleTask implements Runnable {
// 与当前handler绑定的Context
private final ChannelHandlerContext ctx;
AbstractIdleTask(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
/* 给子类提供的模板方法 */
@Override
public void run() {
// 通道关闭则不执行后序操作
if (!ctx.channel().isOpen()) {
return;
}
// 否则会执行子类重写的抽象方法run(ctx)
run(ctx);
}
// 要求子类重写的run(ctx)抽象方法
protected abstract void run(ChannelHandlerContext ctx);
}
下面就逐个看看Task子类的重写的run(ChannelHandlerContext ctx) 方法
ReaderIdleTimeoutTask 的 run 方法
io.netty.handler.timeout.IdleStateHandler.ReaderIdleTimeoutTask#run
@Override
protected void run(ChannelHandlerContext ctx) {
// 得到用户设置的超时时间
long nextDelay = readerIdleTimeNanos;
if (!reading) {
// 如果读取操作结束了(执行了 channelReadComplete 方法设置) ,
// 就用用户设置的超时时间 减去
// 系统当前的时间(纳秒)给定时间和最后一次读操作的时间的差值
//(执行了 channelReadComplete 方法设置)
nextDelay -= ticksInNanos() - lastReadTime;
}
// 如果 nextDelay 小于0,就触发事件
if (nextDelay <= 0) {
// 首先将任务再次放到队列,时间是刚开始设置的时间,
// 返回一个 promise 对象,用于做取消操作
readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);
// 设置 first 属性为 false ,表示,下一次读取不再是第一次了,
// 这个属性在 channelRead 方法会被改成 true
boolean first = firstReaderIdleEvent;
firstReaderIdleEvent = false;
try {
// 创建一个 IdleStateEvent 类型的写事件对象,
IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
// 将IdleStateEvent 对象传递给用户的 UserEventTriggered 方法。
// 完成触发事件的操作
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
// 反之,继续放入队列, 间隔时间是新的计算时间
readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
}
}
总的来说,每次读取操作都会记录一个时间,定时任务时间到了,会计算当前时间和最后一次读的时间
的间隔,如果间隔超过了设置的时间,就触发 UserEventTriggered 方法
io.netty.handler.timeout.IdleStateHandler#channelIdle
protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
// 触发 接下来的 Handler 的 UserEventTriggered 方法
// 调用流程与上面分析的 fireChannelRead 一样
ctx.fireUserEventTriggered(evt);
}
WriterIdleTimeoutTask 的 run 方法
写任务的 run 代码逻辑基本和读任务的逻辑一样,唯一不同的就是有一个针对 出站较慢数据的判断
hasOutputChanged
io.netty.handler.timeout.IdleStateHandler.WriterIdleTimeoutTask#run
@Override
protected void run(ChannelHandlerContext ctx) {
long lastWriteTime = IdleStateHandler.this.lastWriteTime;
long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
if (nextDelay <= 0) {
writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);
boolean first = firstWriterIdleEvent;
firstWriterIdleEvent = false;
try {
// 出站较慢数据的判断hasOutputChanged
if (hasOutputChanged(ctx, first)) {
return;
}
IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
}
}
AllIdleTimeoutTask 的 run
表示这个监控着所有的事件。当读写事件发生时,都会记录。代码逻辑和写事件的的基本一致
io.netty.handler.timeout.IdleStateHandler.AllIdleTimeoutTask#run
@Override
protected void run(ChannelHandlerContext ctx) {
long nextDelay = allIdleTimeNanos;
if (!reading) {
// 这里会取走后读时间和最后写时间的最大值
nextDelay -= ticksInNanos() - Math.max(lastReadTime, lastWriteTime);
}
if (nextDelay <= 0) {
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 核心组件 EventLoop
NioEventLoop间接实现了ScheduledExecutorService,EventLoop两个接口,有继承了父类SingleThreadEventExecutor
- ScheduledExecutorService 接口表示是一个定时任务接口,EventLoop 可以接受定时任务。
- EventLoop 接口:Netty 接口文档说明该接口作用:一旦 Channel 注册了,就处理该 Channel 对应的所有I/O 操作。
- SingleThreadEventExecutor 表示这是一个单个线程的线程池
- EventLoop 是一个单例的线程池,里面含有一个死循环的线程不断的做着 3 件事情:监听端口,处理端口事件,处理队列事件。每个 EventLoop 都可以绑定多个 Channel,而每个 Channel 始终只能由一个 EventLoop 来处理
io.netty.util.concurrent.SingleThreadEventExecutor#execute
@Override
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop();
// 首先判断该 EventLoop 的线程是否是当前线程
if (inEventLoop) {
// 如果是,直接添加到任务队列中去
addTask(task);
} else {
// 如果不是,则尝试启动线程(但由于线程是单个的,因此只能启动一次)
startThread();
// 将任务添加到队列中去。
addTask(task);
// 如果线程已经停止,并且删除任务
if (isShutdown() && removeTask(task)) {
// 则执行拒绝策略,默认是抛出异常
reject();
}
}
// 如果 addTaskWakesUp 是 false 并且任务不是 NonWakeupRunnable 类型的
if (!addTaskWakesUp && wakesUpForTask(task)) {
// 就尝试唤醒 selector。这个时候,阻塞在 selecor 的线程就会立即返回
wakeup(inEventLoop);
}
}
先看任务入队的操作
io.netty.util.concurrent.SingleThreadEventExecutor#addTask
protected void addTask(Runnable task) {
...
// 调用offerTask方法添加任务到队列
if (!offerTask(task)) {
// 添加不成功,则执行拒绝策略
reject(task);
}
}
io.netty.util.concurrent.SingleThreadEventExecutor#offerTask
final boolean offerTask(Runnable task) {
// 判断当前线程池是否处于shutdown,shutdown状态不接收新任务
if (isShutdown()) {
// 执行拒绝策略
reject();
}
// 添加任务到队列
return taskQueue.offer(task);
}
还有第一次进来时当前线程不是EventLoop线程,会进行startThread方法启动一个线程,startThread又会调用到doStartThread方法
io.netty.util.concurrent.SingleThreadEventExecutor#doStartThread
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 方法,
// 注意:这个方法是个死循环,是整个 EventLoop 的核心
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
for (;;) {
int oldState = state;
// 在 finally 块中,
// 使用 CAS 不断修改 state 状态,改成ST_SHUTTING_DOWN
// 也就是当线程 Loop 结束的时候,关闭线程。
// 最后还要死循环确认是否关闭,否则不会 break
if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
break;
}
}
if (success && gracefulShutdownStartTime == 0) {
logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must be called " +
"before run() implementation terminates.");
}
try {
for (;;) {
if (confirmShutdown()) {
break;
}
}
} finally {
try {
// 执行 cleanup 操作
cleanup();
} finally {
// 更新状态为 ST_TERMINATED
STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
// 释放当前线程锁
threadLock.release();
// 如果任务队列不是空,则打印队列中还有多少个未完成的任务。
if (!taskQueue.isEmpty()) {
logger.warn(
"An event executor terminated with " +
"non-empty task queue (" + taskQueue.size() + ')');
}
// 并回调 terminationFuture 方法。
terminationFuture.setSuccess(null);
}
}
}
}
});
}
run方法会进入到NioEventLoop的run方法,进行死循环
io.netty.channel.nio.NioEventLoop#run
@Override
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
// 监听注册到Selector上面的所有的channel
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
// fall through
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
// 监听到事件,处理SelectedKeys
processSelectedKeys();
} finally {
// 运行所有的Task
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
select方法里面调用 selector 的 select 方法,默认阻塞一秒钟,如果有定时任务,则在定时任务剩余时间的基础上在加上 0.5秒进行阻塞。当执行 execute 方法的时候,也就是添加任务的时候,唤醒 selecor,防止 selecotr 阻塞时间过长
io.netty.channel.nio.NioEventLoop#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 (hasTasks() && wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
// Nio的操作,Selector的select方法,阻塞给定时间,默认一秒
int selectedKeys = selector.select(timeoutMillis);
selectCnt ++;
//如果 1 秒后返回,有返回值 || select 被用户唤醒 || 任务队列有任务 || 有定时任务即将被执行; 则跳出循环
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
break;
}
if (Thread.interrupted()) {
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) {
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
logger.warn(
"Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",
selectCnt, selector);
rebuildSelector();
selector = this.selector;
selector.selectNow();
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);
}
}
}
小结
- 每次执行 ececute 方法都是向队列中添加任务。当第一次添加时就启动线程,执行 run 方法,而 run 方法是整个 EventLoop 的核心,就像 EventLoop 的名字一样,Loop Loop ,不停的 Loop ,Loop 做什么呢?做 3 件事情。
- 调用 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 中添加线程池
处理耗时业务的第一种方式–handler
修改NettyServerHandler,添加异步处理逻辑
// EventExecutorGroup 是当前的业务线程池,是jdk原生线程池的一种封装
static final EventExecutorGroup GROUP = new DefaultEventExecutorGroup(10);
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
final Object copMsg = msg;
final ChannelHandlerContext copCtx = ctx;
// 提交异步处理任务到业务线程池
GROUP.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
ByteBuf buf = (ByteBuf)copMsg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
Thread.sleep(10 * 1000);
System.out.println(body + " " + Thread.currentThread().getName());
String resStr = "hello i am server";
ByteBuf resp = Unpooled.copiedBuffer(resStr.getBytes());
copCtx.writeAndFlush(resp);
return null;
}
});
System.out.println("go on..");
}
在 channelRead 方法,模拟了一个耗时 10 秒的操作,这里,我们将这个任务提交到了一个自定义的业
务线程池中,这样,就不会阻塞 Netty 的 IO 线程
io.netty.channel.AbstractChannelHandlerContext#write(java.lang.Object, boolean, io.netty.channel.ChannelPromise)
private void write(Object msg, boolean flush, ChannelPromise promise) {
AbstractChannelHandlerContext next = findContextOutbound();
final Object m = pipeline.touch(msg, next);
EventExecutor executor = next.executor();
// 判断当前线程是否是下个 outbound 的 executor 线程不是当前线程
if (executor.inEventLoop()) {
// 如果是,则在当前线程中直接执行操作
if (flush) {
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {
// 不是则封装成一个task
AbstractWriteTask task;
if (flush) {
task = WriteAndFlushTask.newInstance(next, m, promise);
} else {
task = WriteTask.newInstance(next, m, promise);
}
// 然后放入mpsc 队列中,等待 IO 任务执行完毕后执行队列中的任务
safeExecute(executor, task, promise, m);
}
}
处理耗时业务的第二种方式-Context 中添加线程池
修改NettyServer,添加handler的时候指定线程池。然后 handler 中的代码就使用普通的方式来处理耗时业务。
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.handler(new LoggingHandler(LogLevel.INFO))
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// ch.pipeline().addLast(new NettyServerHandler());
// 当我们在调用 addLast 方法添加线程池后,
// handler 将优先使用这个线程池,如果不添加,将使用 IO 线程
ch.pipeline().addLast(new DefaultEventExecutorGroup(16), new NettyServerHandler());
}
});
当走到 AbstractChannelHandlerContext 的 invokeChannelRead 方法的时候,executor.inEventLoop() 是不会通过的,因为当前线程是 IO 线程 Context (也就是 Handler)的 executor 是业务线程, 所以会异步执行
io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
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 {
// 因为当前线程是IO线程,executor的线程是业务线程,所以上面的判断不会通过
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRead(m);
}
});
}
}
两种方式的比较
- 第一种方式在 handler 中添加异步,可能更加的自由,比如如果需要访问数据库,那我就异步,如果不需要,就不异步,异步会拖长接口响应时间。因为需要将任务放进 mpscTask 中。如果 IO 时间很短,task 很多,可能一个循环下来,都没时间执行整个 task,导致响应时间达不到指标。
- 第二种方式是 Netty 标准方式(即加入到队列),但是,这么做会将整个 handler 都交给业务线程池。不论耗时不耗时,都加入到队列里,不够灵活。
- 各有优劣,从灵活性考虑,第一种较好