万字长文拆解Netty核心机制:ChannelHandler源码全解析

264 阅读16分钟

万字长文拆解Netty核心机制:ChannelHandler源码全解析

在上一篇文章中,我们深入探讨Netty框架中的NioEventLoop,分析它是如何通过高效的事件循环机制处理网络事件的

了解到NioEventLoop在处理IO事件时,会调用ChannelPipeline中的ChannelHandler来实现具体的业务逻辑

ChannelPipelineChannelHandler都是Netty框架的核心组件之一,为了帮助读者更好地掌握Netty的内部机制

本文将深入分析它们的核心源码,并整理出核心流程图,揭示其背后的实现原理和设计思路

结构体系

ChannelPipeline是处理器链,由多个ChannelHandler组成管道(Pipeline),事件会在管道上依次被处理,并可以将处理结果交给下一个ChannelHandler

ChannelHandler接口主要提供两个方法,用于加入pipeline后(handlerAdded)、退出pipeline后(handlerRemoved)进行调用

 public interface ChannelHandler {
     void handlerAdded(ChannelHandlerContext ctx) throws Exception;
     void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
 }

比较熟悉的实现是ChannelInitializer的handlerAdded:当ChannelInitializer加入pipeline后会进行初始化,将自定义的handler也加入进来,最后将自己(ChannelInitializer)移除

 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
     if (ctx.channel().isRegistered()) {
         //加入自定义Handler
         if (initChannel(ctx)) {
             removeState(ctx);
         }
     }
 }

ChannelHandler下有两个子接口ChannelInboundHandler、ChannelOutboundHandler分别代表入站处理器和出站处理器

ChannelHandler结构体系

ChannelInboundHandler定义大量入站处理的抽象方法,比如处理读事件的channelRead方法

由于定义的抽象方法太多,有时候只关心处理读事件,那么就可以通过实现适配器,避免接口的所有抽象方法都要实现

ChannelInboundHandlerAdapter与ChannelOutboundHandlerAdapter就是入站/出站处理器的适配器

以ChannelInboundHandlerAdapter为例,其实现的接口方法中都会使用@Skip注解

在处理器链路中调用时,如果处理器的对应方法使用@Skip注解则会跳过当前处理器,直到找到下一个处理器

 public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
 ​
     @Skip
     @Override
     public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
         ctx.fireChannelRegistered();
     }
 ​
     @Skip
     @Override
     public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
         ctx.fireChannelUnregistered();
     }
 ​
     @Skip
     @Override
     public void channelActive(ChannelHandlerContext ctx) throws Exception {
         ctx.fireChannelActive();
     }
 ​
     @Skip
     @Override
     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
         ctx.fireChannelInactive();
     }
 ​
     @Skip
     @Override
     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
         ctx.fireChannelRead(msg);
     }
 ​
     @Skip
     @Override
     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
         ctx.fireChannelReadComplete();
     }
 ​
     @Skip
     @Override
     public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
         ctx.fireUserEventTriggered(evt);
     }
 ​
     @Skip
     @Override
     public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
         ctx.fireChannelWritabilityChanged();
     }
 ​
     @Skip
     @Override
     @SuppressWarnings("deprecation")
     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
             throws Exception {
         ctx.fireExceptionCaught(cause);
     }
 }

ChannelOutboundHandlerAdapter实现同理

它们的方法中都有上下文参数ChannelHandlerContext

ChannelHandlerContext是调用链路中的节点,即存储当前节点使用的ChannelHandler也存储Channel、Executor等调用过程中的重要信息

ChannelHandlerContext实现ChannelInboundInvoker、ChannelOutboundInvoker两个接口

这两个接口定义的抽象方法比ChannelInboundHandler、ChannelOutboundHandler接口方法通常多一个前缀fire

它们的接口方法用于在处理器链路中找到下个需要调用的处理器并进行调用,比如fireChannelRead用于调用处理器链中下个节点的ChannelRead

创建Pipeline

初始化Channel时会创建它对应的Pipeline

 protected AbstractChannel(Channel parent) {
     this.parent = parent;
     id = newId();
     unsafe = newUnsafe();
     //创建pipeline
     pipeline = newChannelPipeline();
 }

创建pipeline时,会默认创建两个首尾节点并互相指向

创建pipeline

 protected DefaultChannelPipeline(Channel channel) {
     this.channel = ObjectUtil.checkNotNull(channel, "channel");
     succeededFuture = new SucceededChannelFuture(channel, null);
     voidPromise =  new VoidChannelPromise(channel, true);
 ​
     tail = new TailContext(this);
     head = new HeadContext(this);
 ​
     head.next = tail;
     tail.prev = head;
 }

创建首节点,通常会设置一些信息(尾节点类似)

 HeadContext(DefaultChannelPipeline pipeline) {
     //父类AbstractChannelHandlerContext构造
     super(pipeline, null, HEAD_NAME, HeadContext.class);
     //unsafe复用channel里的组件
     unsafe = pipeline.channel().unsafe();
     //CAS设置状态
     setAddComplete();
 }
 AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor,
                               String name, Class<? extends ChannelHandler> handlerClass) {
     this.name = ObjectUtil.checkNotNull(name, "name");
     this.pipeline = pipeline;
     //执行当前handler时用的线程池 为空就是当前线程执行
     this.executor = executor;
     //执行标记 判断是否需要执行
     this.executionMask = mask(handlerClass);
     //是否有序
     ordered = executor == null || executor instanceof OrderedEventExecutor;
 }

需要注意的是:尾节点只是一个入站处理器,而首节点是入站、出站处理器

 final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler
 final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler,ChannelInboundHandler

添加Handler

pipeline提供四种添加Handler的方式:

  1. addLast 添加到末尾
  2. addFirst 添加到头部
  3. addBefore 添加到某节点前
  4. addAfter 添加到某节点后

无论是使用哪种方法,最终都会调用internalAdd进行通用添加,根据入参策略AddStrategy分区添加的方式:

入参EventExecutorGroup会赋值给AbstractChannelHandlerContext的executor,也就是该handler执行时由哪个线程池来执行

没有设置就是为空,为空的情况会由当前事件循环来进行执行

添加节点主要会检查是否重复添加、构建节点、将节点放到对应位置、最后CAS设置成功状态调用handlerAdded,大致流程如下:

  1. 检查handler是否重复添加(使用@Sharable则可重复添加)checkMultiplicity
  2. 创建节点 newContext
  3. 根据不同添加策略将节点添加到不同位置(改变指向关系)
  4. 根据情况异步/同步调用:CAS成功设置已添加状态后调用接口方法 handlerAdded
 private ChannelPipeline internalAdd(EventExecutorGroup group, String name,
                                     ChannelHandler handler, String baseName,
                                     AddStrategy addStrategy) {
     final AbstractChannelHandlerContext newCtx;
     synchronized (this) {
         //检查handler重复
         checkMultiplicity(handler);
         
         //生成名称 检查重名
         name = filterName(name, handler);
 ​
         //创建节点
         newCtx = newContext(group, name, handler);
 ​
         //根据添加策略添加到对应位置(改变指向关系)
         switch (addStrategy) {
             case ADD_FIRST:
                 addFirst0(newCtx);
                 break;
             case ADD_LAST:
                 addLast0(newCtx);
                 break;
             case ADD_BEFORE:
                 addBefore0(getContextOrDie(baseName), newCtx);
                 break;
             case ADD_AFTER:
                 addAfter0(getContextOrDie(baseName), newCtx);
                 break;
             default:
                 throw new IllegalArgumentException("unknown add strategy: " + addStrategy);
         }
 ​
         //未注册添加回调
         if (!registered) {
             newCtx.setAddPending();
             callHandlerCallbackLater(newCtx, true);
             return this;
         }
 ​
         //传入的线程池 调用HandlerAdded
         EventExecutor executor = newCtx.executor();
         if (!executor.inEventLoop()) {
             callHandlerAddedInEventLoop(newCtx, executor);
             return this;
         }
     }
     //当前事件循环的线程来 调用HandlerAdded
     callHandlerAdded0(newCtx);
     return this;
 }

比较熟悉的初始化ChannelInitializer在handlerAdded实现中:

 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
     //未注册才调用
     if (ctx.channel().isRegistered()) {
         //初始化
         if (initChannel(ctx)) {
             removeState(ctx);
         }
     }
 }
  1. 调用我们实现的initChannel(用来添加Handler)

自定义ChannelInitializer初始化Channel添加Handler

  1. 执行成功后会从管道中移除当前handler(因为ChannelInitializer只用于初始化)
 private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
     if (initMap.add(ctx)) { // Guard against re-entrance.
         try {
             //调用我们实现的initChannel (添加handler)
             initChannel((C) ctx.channel());
         } catch (Throwable cause) {
             exceptionCaught(ctx, cause);
         } finally {
             //初始化完移除当前handler (该handler只用于初始化)
             ChannelPipeline pipeline = ctx.pipeline();
             if (pipeline.context(this) != null) {
                 pipeline.remove(this);
             }
         }
         return true;
     }
     return false;
 }

执行顺序

分析完创建、添加的流程,我们再来看看Pipeline中Handler的执行顺序

我会加入四个handler,两个入站处理器in1、in2,两个出站处理器out1、out2(都是匿名的,名字是我根据输出内容取的)

在入站处理器中通过调用fireChannelRead方法来调用下一个入站处理器处理读事件

在出站处理器中通过调用write方法来调用下一个出站处理器处理进行写回数据

pipeline中节点顺序为: head->in1->in2->out1->out2->tail

服务端Pipeline

服务端代码如下,以服务端读取客户端消息为案例,我们先来看下以下代码会输出什么:

 public static void main(String[] args) {
         NioEventLoopGroup parentGroup = new NioEventLoopGroup(1);
         NioEventLoopGroup childGroup = new NioEventLoopGroup(5);
         ServerBootstrap serverBootstrap = new ServerBootstrap();
         ChannelFuture future = serverBootstrap
                 .group(parentGroup, childGroup)
                 .channel(NioServerSocketChannel.class)
                 .childHandler(new ChannelInitializer<NioSocketChannel>() {
                     @Override
                     protected void initChannel(NioSocketChannel channel) {
                         channel.pipeline()
                                 .addLast(new ChannelInboundHandlerAdapter() {
                                     @Override
                                     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                         System.out.println("in 1 start");
                                         //调用下一个入站处理器的channelRead
                                         ctx.fireChannelRead(msg);
                                         System.out.println("in 1 end");
                                     }
                                 })
                                 .addLast(new ChannelInboundHandlerAdapter() {
                                     @Override
                                     public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                         System.out.println("in 2 start");
                                         //这里的msg是ByteBuf 因为我们没有转换
                                         System.out.println(msg);
                                         
                                         ctx.channel().writeAndFlush("hello ~ my name is cai cai !");
 //                                        ctx.writeAndFlush("hello ~ my name is cai cai !");
                                         
                                         System.out.println("in 2 end");
                                     }
                                 })
                                 .addLast(new ChannelOutboundHandlerAdapter() {
                                     @Override
                                     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                         System.out.println("out 1 start:" + msg);
                                         ByteBuf byteBuf = ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg.toString()), StandardCharsets.UTF_8);
                                         ctx.writeAndFlush(byteBuf);
                                         System.out.println("out 1 end:" + msg);
                                     }
                                 })
                                 .addLast(new ChannelOutboundHandlerAdapter() {
                                     @Override
                                     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                         System.out.println("out 2 start:" + msg);
                                         ctx.write(msg);
                                         System.out.println("out 2 end:" + msg);
                                     }
                                 })
                         ;
                     }
                 })
                 .bind(8888);
         try {
             future.sync().channel().closeFuture().sync();
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
         } finally {
             parentGroup.shutdownGracefully();
             childGroup.shutdownGracefully();
         }
     }

客户端的handler使用字符串编解码器和自定义入站处理器打印客户端收到的数据信息

客户端连接成功后发送一条消息触发服务端初始化handler并处理的流程:

 public static void main(String[] args) {
         Bootstrap bootstrap = new Bootstrap();
         ChannelFuture future = bootstrap
                 .group(new NioEventLoopGroup())
                 .channel(NioSocketChannel.class)
                 .handler(new ChannelInitializer<NioSocketChannel>() {
                     @Override
                     protected void initChannel(NioSocketChannel channel) throws Exception {
                         channel.pipeline()
                                 .addLast(new StringDecoder())
                                 .addLast(new StringEncoder())
                                 .addLast(new ChannelInboundHandlerAdapter() {
                                     @Override
                                     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                         System.out.println("客户端收到:" + msg);
                                     }
                                 })
                         ;
                     }
                 })
                 .connect("127.0.0.1", 8888);
 ​
         try {
             //同步阻塞等待连接
             future.sync();
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
         }
 ​
         //发送消息
         future.channel().writeAndFlush("hello cai cai ~");
     }

服务端最终打印如下:

 in 1 start
 in 2 start
 PooledUnsafeDirectByteBuf(ridx: 0, widx: 15, cap: 2048)
 out 2 start:hello ~ my name is cai cai !
 out 1 start:hello ~ my name is cai cai !
 out 1 end:hello ~ my name is cai cai !
 out 2 end:hello ~ my name is cai cai !
 in 2 end
 in 1 end

通过打印可以发现执行的顺序在入站处理器中与加入顺序有序,而在出站处理器中与加入顺序逆序

触发Handler的入口

接下来通过解析源码看看handler是如何执行的:

pipeline中的handler会在eventloop处理IO事件(processSelectedKey)的过程中触发,处理连接、读事件都会调用unsafe的read方法

 if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
     unsafe.read();
 }

服务端处理接收事件的实现在AbstractNioMessageChannel.NioMessageUnsafe.read中,处理读事件的实现在AbstractNioByteChannel.NioByteUnsafe.read中

我们以AbstractNioByteChannel.NioByteUnsafe.read为例进行查看:

  1. 读取数据前需申请ByteBuf直接内存 byteBuf = allocHandle.allocate(allocator)
  2. 将channel中的数据读取到ByteBuf doReadBytes(byteBuf)
  3. 触发handler处理读事件 pipeline.fireChannelRead(byteBuf)
  4. 触发读取后的方法 pipeline.fireChannelReadComplete

其中多个读事件可能都就绪,1-3步骤循环处理读事件

 public final void read() {
     final ChannelConfig config = config();
     if (shouldBreakReadReady(config)) {
         clearReadPending();
         return;
     }
     final ChannelPipeline pipeline = pipeline();
     final ByteBufAllocator allocator = config.getAllocator();
     final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
     allocHandle.reset(config);
 ​
     ByteBuf byteBuf = null;
     boolean close = false;
     try {
         do {
             //申请缓冲区ByteBuf
             byteBuf = allocHandle.allocate(allocator);
             //将数据读到缓冲区ByteBuf
             allocHandle.lastBytesRead(doReadBytes(byteBuf));
             if (allocHandle.lastBytesRead() <= 0) {
                 //没读到就释放ByteBuf
                 byteBuf.release();
                 byteBuf = null;
                 close = allocHandle.lastBytesRead() < 0;
                 if (close) {
                     // There is nothing left to read as we received an EOF.
                     readPending = false;
                 }
                 break;
             }
 ​
             allocHandle.incMessagesRead(1);
             readPending = false;
             //触发调用pipeline中handler的chanelRead
             pipeline.fireChannelRead(byteBuf);
             byteBuf = null;
         } while (allocHandle.continueReading());
         
         allocHandle.readComplete();
         //触发channelReadComplete
         pipeline.fireChannelReadComplete();
 ​
         if (close) {
             closeOnRead(pipeline);
         }
     } catch (Throwable t) {
         //处理异常
         handleReadException(pipeline, byteBuf, t, close, allocHandle);
     } finally {
         if (!readPending && !config.isAutoRead()) {
             removeReadOp();
         }
     }
 }

read

fireChannelRead就是触发调用channelRead的源头,会先调用头节点的channelRead方法

 public final ChannelPipeline fireChannelRead(Object msg) {
     AbstractChannelHandlerContext.invokeChannelRead(head, msg);
     return this;
 }

如果是调用过程中的fireChannelRead,会使用findContextInbound找到下一个需要调用channelRead的handler

查找的过程中会根据计算过的executionMask+位运算判断是否要跳过Handler 实现该方法并且方法未使用注解@Skip就不会跳过

 @Override
 public ChannelHandlerContext fireChannelRead(final Object msg) {
     //findContextInbound找到下一个要处理读方法的handler (检查mask)
     invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);
     return this;
 }

在调用的过程中需要注意:

在调用过程中会判断当前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 {
         executor.execute(new Runnable() {
             @Override
             public void run() {
                 next.invokeChannelRead(m);
             }
         });
     }
 }

调用前会检查handler是否完全加入,最终调用chanelRead

 private void invokeChannelRead(Object msg) {
     //检查handler是否已经加入(加入的过程长,有几个状态会用CAS设置)
     if (invokeHandler()) {
         try {
             final ChannelHandler handler = handler();
             final DefaultChannelPipeline.HeadContext headContext = pipeline.head;
             if (handler == headContext) {
                 //如果当前节点是头节点
                 headContext.channelRead(this, msg);
             } else if (handler instanceof ChannelDuplexHandler) {
                 //如果当前是ChannelDuplexHandler(即是入站也是出站)
                 ((ChannelDuplexHandler) handler).channelRead(this, msg);
             } else {
                 ((ChannelInboundHandler) handler).channelRead(this, msg);
             }
         } catch (Throwable t) {
             invokeExceptionCaught(t);
         }
     } else {
         fireChannelRead(msg);
     }
 }

其他方法流程类似,只是这里以channelRead举例

根据该流程能判断先从head找到in1打印in 1 start,在in1中调用fireChannelRead继续寻找下一个处理器调用channelRead

执行顺序

因此会在in2中打印in 2 start以及对应ByteBuf的toString:PooledUnsafeDirectByteBuf(ridx: 0, widx: 15, cap: 2048)

然后in2中会调用writeAndFlush写回消息

需要注意的是,同样调用writeAndFlush使用ctx.channel().writeAndFlush()ctx.writeAndFlush()导致的流程可能完全不同

write

write与writeAndFlush的区别就是,writeAndFlush会flush将数据全部刷入缓冲区写回

接下来分析写的流程:

使用 ctx.channel().writeAndFlush() 时,最终会调用尾节点的writeAndFlush

image.png

 @Override
 public final ChannelFuture writeAndFlush(Object msg) {
     return tail.writeAndFlush(msg);
 }

最终会调用通用的write方法:

  1. 查找下一个handler(根据prev前驱指针查找,即向前查找)
  2. 调用write或writeAndFlush(当前事件循环的线程是handler设置的executor则当前线程同步执行,否则executor异步执行)
 private void write(Object msg, boolean flush, ChannelPromise promise) {
     //检查
     ObjectUtil.checkNotNull(msg, "msg");
     try {
         if (isNotValidPromise(promise, true)) {
             ReferenceCountUtil.release(msg);
             // cancelled
             return;
         }
     } catch (RuntimeException e) {
         ReferenceCountUtil.release(msg);
         throw e;
     }
 ​
     //找到下一个handler调用 (findContextOutbound根据prev指针返回,因此是逆序)
     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 WriteTask task = WriteTask.newInstance(next, m, promise, flush);
         if (!safeExecute(executor, task, promise, m, !flush)) {
             task.cancel();
         }
     }
 }

流程与read类似,只需要注意findContextOutbound中是根据prev指针逆序

(尾节点的前一个节点是out2,因此是先调用out2再调用out1)

注意:ctx.channel().writeAndFlush 才会调用尾节点的write,如果使用ctx.writeAndFlush则不会经过尾节点,直接交给前一个节点去执行

 ctx.channel().writeAndFlush("hello ~ my name is cai cai !");

因此使用ctx.writeAndFlush时,out1、out2都不会输出

in2使用ctx.writeAndFlush时

使用ctx.writeAndFlush打印结果如下:(并且这种情况服务端无法将数据写回,后面会分析)

 in 1 start
 in 2 start
 PooledUnsafeDirectByteBuf(ridx: 0, widx: 15, cap: 2048)
 in 2 end
 in 1 end

当out1继续调用write将消息传递时,下一个处理器就是out2

out2将字符串消息转换为ByteBuf继续传递,下一个处理器就是头节点(因为in1、in2都是入站处理,未实现write)

完整调用顺序

 ByteBuf byteBuf = ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg.toString()), StandardCharsets.UTF_8);
 ctx.writeAndFlush(byteBuf);

(不转换成ByteBuf,头节点会不处理,因此发现无法写回消息时记得检查最后是否将数据转换为ByteBuf)

最终会先调用头节点的write方法,将数据加入返回的缓冲池outboundBuffer,调用flush才会真正使用原生NIO写回数据

 void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
     if (invokeHandler()) {
         //检查数据,写入outboundBuffer
         invokeWrite0(msg, promise);
         //flush 写回 
         invokeFlush0();
     } else {
         writeAndFlush(msg, promise);
     }
 }
 public final void write(Object msg, ChannelPromise promise) {
     assertEventLoop();
 ​
     ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
     if (outboundBuffer == null) {
         try {
             ReferenceCountUtil.release(msg);
         } finally {
             safeSetFailure(promise,
                     newClosedChannelException(initialCloseCause, "write(Object, ChannelPromise)"));
         }
         return;
     }
 ​
     int size;
     try {
         //检查消息是否为ByteBuf
         msg = filterOutboundMessage(msg);
         //统计大小
         size = pipeline.estimatorHandle().size(msg);
         if (size < 0) {
             size = 0;
         }
     } catch (Throwable t) {
         try {
             ReferenceCountUtil.release(msg);
         } finally {
             safeSetFailure(promise, t);
         }
         return;
     }
     //加入outboundBuffer
     outboundBuffer.addMessage(msg, size, promise);
 }

flush最终会调用NioSocketChannel.doWrite使用JDK原生Channel与ByteBuffer写回数据

 protected void doWrite(ChannelOutboundBuffer in) throws Exception {
     SocketChannel ch = javaChannel();
     int writeSpinCount = config().getWriteSpinCount();
     //循环写
     do {
         //...
         
         //最大
         int maxBytesPerGatheringWrite = ((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite();
         //数量
         ByteBuffer[] nioBuffers = in.nioBuffers(1024, maxBytesPerGatheringWrite);
         //循环写次数
         int nioBufferCnt = in.nioBufferCount();
 ​
         switch (nioBufferCnt) {
             case 0:
                 writeSpinCount -= doWrite0(in);
                 break;
             case 1: {
                 //只需要写一次
                 ByteBuffer buffer = nioBuffers[0];
                 int attemptedBytes = buffer.remaining();
                 final int localWrittenBytes = ch.write(buffer);
                 if (localWrittenBytes <= 0) {
                     incompleteWrite(true);
                     return;
                 }
                 adjustMaxBytesPerGatheringWrite(attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite);
                 in.removeBytes(localWrittenBytes);
                 --writeSpinCount;
                 break;
             }
             default: {
                 long attemptedBytes = in.nioBufferSize();
                 final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt);
                 if (localWrittenBytes <= 0) {
                     incompleteWrite(true);
                     return;
                 }
                 adjustMaxBytesPerGatheringWrite((int) attemptedBytes, (int) localWrittenBytes,
                         maxBytesPerGatheringWrite);
                 in.removeBytes(localWrittenBytes);
                 --writeSpinCount;
                 break;
             }
         }
     } while (writeSpinCount > 0);
 ​
     incompleteWrite(writeSpinCount < 0);
 }

将NioEventLoop核心处理事件循环的流程相结合,可以得到以下流程图:

调用流程

这并不是Netty完整的运行流程,在其内部组件初始化还涉及到部分重要流程,为了内容的完整性,下篇文章将描述服务端组件启动的流程并整理出相关流程

总结

ChannelPipeline是由多个处理器组成的管道,用于触发事件,让数据在管道内进行调用处理

创建Channel的同时也会创建Channel对应的Pipeline(一一对应),默认创建首尾两个节点,节点间存在prev前驱、next后继指针,调用时会根据指针指向关系进行传递

添加Handler的过程会检查是否重复被添加到Pipeline中,只有使用@Sharable注解的Handler才允许加入到多个Pipeline中,成功添加Handler后会调用handlerAdded

ChannelInitializer的handlerAdded通常会指向我们自定义的实现,即将自定义的Handler也加入Pipeline中,最后在Pipeline中移除自己(ChannelInitializer)

添加Handler时还可以为Handler指定EventExecutorGroup,后续该Handler处理时会使用该EventExecutorGroup,未指定就是当前事件循环线程同步执行;同时还会用位运算计算并记录带有@Skip的方法,后续调用时会进行跳过

ChannelHandler是管道中的处理器,通过实现自定义的处理器能够满足我们的业务需求,处理器分为入站、出站处理器,通常会直接实现它们的适配器类,适配器类中对接口方法用@Skip进行标识,只关心需要实现的方法(如channelRead、write)

当服务端收到客户端请求数据就绪时(读事件就绪),会在缓冲区中申请合适空间的ByteBuf(直接内存),将就绪的数据读入ByteBuf,并调用Pipeline的fireChannelRead进行传递

头节点会找到下一个Handler(next指针,Handler实现该方法并且未使用@Skip),将ByteBuf传递给下一个Handler进行调用,如果Handler调用过程中为继续向后调用则会中断调用过程(即退出)

在往回写的过程中,如果不使用channel().writeXX则会在当前节点向前查找下一个Handler(根据prev指针,Handler实现该方法并且未使用@Skip)进行调用,如果使用channel()则从尾节点开始向前查找

最终由头节点处理写数据,写回数据前会检查数据是否已经转换为ByteBuf(注意写回的数据要是ByteBuf否则不允许),将数据添加到输出缓冲区,最后使用JDK NIO的Channel以及ByteBuffer进行写回数据

最后(点赞、收藏、关注求求啦~)

😁我是菜菜,热爱技术交流、分享与写作,喜欢图文并茂、通俗易懂的输出知识

📚在我的博客中,你可以找到Java技术栈的各个专栏:Java并发编程与JVM原理、Spring和MyBatis等常用框架及Tomcat服务器的源码解析,以及MySQL、Redis数据库的进阶知识,同时还提供关于消息中间件和Netty等主题的系列文章,都以通俗易懂的方式探讨这些复杂的技术点

🏆除此之外,我还是掘金优秀创作者、腾讯云年度影响力作者、华为云年度十佳博主....

👫我对技术交流、知识分享以及写作充满热情,如果你愿意,欢迎加我一起交流(vx:CaiCaiJava666),也可以持续关注我的公众号:菜菜的后端私房菜,我会分享更多技术干货,期待与更多志同道合的朋友携手并进,一同在这条充满挑战与惊喜的技术之旅中不断前行

🤝如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~

📖本篇文章被收入专栏 Java常用框架、网络基石,感兴趣的朋友可以持续关注~

📝本篇文章、笔记以及案例被收入 Gitee-CaiCaiJava、 Github-CaiCaiJava,除此之外还有更多Java进阶相关知识,感兴趣的朋友可以star持续关注~