简介
Netty设计为事件驱动模型,业务逻辑的处理都是通过事件来触发,所以Netty抽象出了事件处理器,并分为“入站”和“出站”处理器,这些处理器又采用抽象出的“管道”来进行连接,“管道”则是一个双向链表,每个处理器都是链表上的一个节点,事件或消息通过管道在各个处理器之间流动。
组件介绍
启动器
Bootstrap
客户端程序的启动器,通过启动器则可以启动一个客户端程序,用来与服务端通信并进行业务处理。
ServerBootstrap
顾名思义:服务端启动器,通过启动器可以启动一个服务端程序,用来与客户端通信并进行业务处理。
处理器
入站处理器
处理器用来对消息进行处理,例如:对消息解密。入站处理器则是专门处理对端发送过来的消息,例如服务端处理客户端发送的消息,同理:客户端也可以用入站处理器处理服务端回复的消息。
出站处理器
和入站处理器正好相反,它用来处理发送给对端的消息,它们处理消息的顺序正好相反,消息在管道中流动的顺序也相反。例如:当服务端回复给客户端消息时,可能也需要加密,这时加密处理由出站处理器进行。
通道
Netty将服务端和客户端通信的链路抽象为“Channel”,也就是“通道”。通过通道,我们可以从中读取数据也可以写入数据到通道。
事件循环和事件循环组
Netty采用Reactor模型,其中事件循环是指有一个线程在无限循环,等待IO事件到来,然后进行处理。事件循环组包含多个事件循环线程,用来并行处理IO事件。
原理和验证
-
事件循环由一个永远不变的线程驱动
验证:
我们在启动时会设置一个事件循环组,它的作用是注册通道和返回下一个事件循环以供使用,通过继承关系,EventLoopGroup继承Executor的execute方法,用来处理具体的IO事件,EventLoopGroup的子类有一个是:MultithreadEventLoopGroup,它是我们常用的NioEventLoopGroup的父类,它继承了AbstractEventExecutorGroup的execute方法,该方法源码如下:@Override public void execute(Runnable command) { next().execute(command); }
可见,执行时会选择下一个EventLoop事件循环并调用execute方法执行具体逻辑,SingleThreadEventLoop是EventLoop的抽象子类,它的execute方法继承自SingleThreadEventExecutor父类,查看该方法源码:
@Override public void execute(Runnable task) { if (task == null) { throw new NullPointerException("task"); } boolean inEventLoop = inEventLoop(); addTask(task); if (!inEventLoop) { startThread(); if (isShutdown() && removeTask(task)) { reject(); } } if (!addTaskWakesUp && wakesUpForTask(task)) { wakeup(inEventLoop); } }
其中inEventLoop方法判断了是否在事件循环线程中,如果没有,则调用startThread新启动一个线程,startThread方法最终执行doStartThread方法,它的核心代码如下:
assert thread == null; thread = Thread.currentThread(); SingleThreadEventExecutor.this.run();
其中thread是SingleThreadEventExecutor的一个实例变量,保存当前线程为任务执行线程,run方法则启动一个事件循环,不同类型的事件循环实现该方法的逻辑不一样,对于常用的NioEventLoop来说,它的实现如下:
@Override protected void run() { for (;;) { try { switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) { case SelectStrategy.CONTINUE: continue; case SelectStrategy.SELECT: 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 { 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); } } }
可以看到本质上就是一个死循环,不停地处理IO事件和执行任务队列的任务,核心代码如下:
processSelectedKeys(); runAllTasks();
有很多地方都在调用EventLoop的execute方法,例如在启动器执行bind方法时。
-
一个EventLoop处理多个Channel的IO事件
验证:
在Reactor模型中,对于主从Reactor来说,主Reactor作为接受连接的作用,并将连接分配给从Reactor进行后续事件处理。在Netty服务端启动时,我们可以设置父子事件循环组,和主从Reactor中作用一样。查看启动时bind的源码,核心代码如下:private ChannelFuture doBind(final SocketAddress localAddress) { final ChannelFuture regFuture = initAndRegister(); ... } final ChannelFuture initAndRegister() { Channel channel = null; try { channel = channelFactory.newChannel(); init(channel); } catch (Throwable t) { ... } }
其中init方法核心代码如下:
@Override void init(Channel channel) throws Exception { ... ChannelPipeline p = channel.pipeline(); ... 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)); } }); } }); }
其中ServerBootstrapAcceptor用来接受连接并将连接分配给事件循环,ServerBootstrapAcceptor的核心代码如下:
@Override @SuppressWarnings("unchecked") public void channelRead(ChannelHandlerContext ctx, Object msg) { final Channel child = (Channel) msg; ... try { 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就是在将Channel和EventLoop绑定,代码如下:
@Override public final void register(EventLoop eventLoop, final ChannelPromise promise) { ... AbstractChannel.this.eventLoop = eventLoop; }
当调用Channel的eventLoop方法时,会通过AbstractChannel来实现,代码如下:
@Override public EventLoop eventLoop() { EventLoop eventLoop = this.eventLoop; if (eventLoop == null) { throw new IllegalStateException("channel not registered to an event loop"); } return eventLoop; }
这里返回的则是刚才绑定的EventLoop。
-
每个Channel绑定了一个Pipeline
验证:
通过查看Channel的pipeline方法的实现,我们知道返回的是AbstractChannel的pipeline成员变量,它是在该类的构造函数中初始化的,追踪该类什么时候被创建,可以发现当有NIO有读取或接受事件时,会执行读取消息,这时会初始化一个NioSocketChannel,核心代码如下:@Override protected int doReadMessages(List<Object> buf) throws Exception { SocketChannel ch = SocketUtils.accept(javaChannel()); try { if (ch != null) { buf.add(new NioSocketChannel(this, ch)); 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; }
doReadMessages方法在read方法中被调用,核心代码如下:
@Override public void read() { ... int localRead = doReadMessages(readBuf); ... int size = readBuf.size(); for (int i = 0; i < size; i ++) { readPending = false; pipeline.fireChannelRead(readBuf.get(i)); } }
还可以看到,上面执行了pipeline.fireChannelRead(readBuf.get(i));,这也解释了为什么ServerBootstrapAcceptor的channelRead方法会执行:final Channel child = (Channel) msg;的转换,因为这里确实传递的消息是新实例化的NioSocketChannel。
-
Handler可以将消息拦截不传递下去
验证:
在服务端创建两个Inbound handler,第一个拦截消息,并且不传递消息到第二个handler,代码如下:public class MessageInterceptionHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("message interception"); // 释放消息,以免内存泄漏 ByteBuf byteBuf = (ByteBuf) msg; byteBuf.release(); } }
public class UsuallyHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("usually handler"); super.channelRead(ctx, msg); } }
public class Server { public static void main(String[] args) throws InterruptedException { EventLoopGroup boss = new NioEventLoopGroup(); EventLoopGroup work = new NioEventLoopGroup(); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(boss, work) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline() .addLast(new MessageInterceptionHandler()) .addLast(new UsuallyHandler()); } }); ChannelFuture channelFuture = serverBootstrap.bind(8090).sync(); channelFuture.channel().closeFuture().sync(); } }
执行结果如下:
message interception
将MessageInterceptionHandler修改为下面逻辑:
public class MessageInterceptionHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("message interception"); super.channelRead(ctx, msg); // // 释放消息,以免内存泄漏 // ByteBuf byteBuf = (ByteBuf) msg; // byteBuf.release(); } }
执行结果如下:
message interception
usually handler这里验证了handler可以拦截消息。
-
当前Handler异常没有被处理则会传递到下一个Handler处理
验证:
创建两个handler,一个没有异常处理方法,另一个有异常处理方法,代码如下:public class ExceptionCaughtHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { throw new RuntimeException("Man-made exception"); } // @Override // public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // System.out.println("exception handler caught exception: " + cause.getMessage()); // } }
public class UsuallyHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("usually handler"); super.channelRead(ctx, msg); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("usually handler caught exception: " + cause.getMessage()); } }
服务端启动时,ExceptionCaughtHandler在UsuallyHandler的前面,所以先执行,执行结果如下:
usually handler caught exception: Man-made exception
当ExceptionCaughtHandler注释放开后,执行结果如下:
exception handler caught exception: Man-made exception
证明了异常会传递到下一个Handler
-
ChannelHandlerContext类似于链表中的Node节点,里面保存了前后指针
验证:
ChannelHandlerContext包含了Channel和对应的ChannelHandler,它还可以通知下一个Handler处理消息,我们知道ChannelPipeline是双向链表,我们会添加ChannleHandler作为其中的节点,但其实通过源码可以知道:其中的真正的节点就是ChannelHandlerContext,源码如下:@Override public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) { final AbstractChannelHandlerContext newCtx; synchronized (this) { checkMultiplicity(handler); newCtx = newContext(group, filterName(name, handler), handler); addLast0(newCtx); ... }
新创建了一个AbstractChannelHandlerContext,并且被添加到尾部,继续看源码:
private void addLast0(AbstractChannelHandlerContext newCtx) { AbstractChannelHandlerContext prev = tail.prev; newCtx.prev = prev; newCtx.next = tail; prev.next = newCtx; tail.prev = newCtx; }
很明显,这里AbstractChannelHandlerContext则是作为双向链表的节点。
-
ChannelHandlerContext的write操作是从当前节点向前寻找第一个出站处理器,Channel的write操作是从管道的尾部向前寻找第一个出站处理器
验证:
ChannelHandlerContext和Channel的write操作都是继承自ChannelOutboundInvoker,实现类分别是AbstractChannelHandlerContext和AbstractChannel,查看源码:
AbstractChannelHandlerContext源码如下:private void write(Object msg, boolean flush, ChannelPromise promise) { AbstractChannelHandlerContext next = findContextOutbound(); 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 { AbstractWriteTask task; if (flush) { task = WriteAndFlushTask.newInstance(next, m, promise); } else { task = WriteTask.newInstance(next, m, promise); } safeExecute(executor, task, promise, m); } } private AbstractChannelHandlerContext findContextOutbound() { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.prev; } while (!ctx.outbound); return ctx; }
可以看出,是从当前节点向前寻找第一个出站处理器。
AbstractChannel源码如下:
@Override public ChannelFuture write(Object msg) { return pipeline.write(msg); }
可以验证上面的结论。
-
ChannelHandlerContext既可以触发入站事件,也可以执行出站操作
先看继承类图:
它继承了ChannelInboundInvoker和ChannelOutboundInvoker,这两个类的名字中都有Invoker,代表是用来调用的,ChannelInboundInvoker中的方法供用户触发入站事件,例如有如下方法:ChannelInboundInvoker fireChannelRegistered(); ChannelInboundInvoker fireChannelUnregistered();
当调用时,会将入站事件传递到下一个inboundHandler,核心源码如下:
private void invokeChannelRegistered() { if (invokeHandler()) { try { ((ChannelInboundHandler) handler()).channelRegistered(this); } catch (Throwable t) { notifyHandlerException(t); } } else { fireChannelRegistered(); } }
可以看出,最终会调用ChannelInboundHandler的channelRegistered方法,该方法则是捕获入站事件并进行处理的方法。
-
当消息传递到ChannelPipeline的头或者尾节点,会自动释放内存
验证:
ChannelPipeline中的节点是ChannelHandlerContext,它的尾部和头部节点定义如下:final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler { TailContext(DefaultChannelPipeline pipeline) { super(pipeline, null, TAIL_NAME, true, false); setAddComplete(); } ... }
final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler, ChannelInboundHandler { private final Unsafe unsafe; HeadContext(DefaultChannelPipeline pipeline) { super(pipeline, null, HEAD_NAME, false, true); unsafe = pipeline.channel().unsafe(); setAddComplete(); } ... }
假如:当其中ChannelHandlerContext执行fireChannelRead时,如果当前已经没有inbound处理器,则消息会传递到TailContext ,因为它是一个inbound处理器,它对于该方法的处理是:
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { onUnhandledInboundMessage(msg); } protected void onUnhandledInboundMessage(Object msg) { try { logger.debug( "Discarded inbound message {} that reached at the tail of the pipeline. " + "Please check your pipeline configuration.", msg); } finally { ReferenceCountUtil.release(msg); } }
可以看到在finally中,自动释放了内存。
当调用ChannelHandlerContext的writeAndFlush方法,最终会调用HeadContext的flush方法,接着会调用NioSocketChannel的doWrite方法,当ChannelOutboundBuffer内容写入SocketChannel后,会调用它的removeBytes方法,源码如下:@Override protected void doWrite(ChannelOutboundBuffer in) throws Exception { SocketChannel ch = javaChannel(); int writeSpinCount = config().getWriteSpinCount(); do { ... // Always us nioBuffers() to workaround data-corruption. // See https://github.com/netty/netty/issues/2761 switch (nioBufferCnt) { case 0: // We have something else beside ByteBuffers to write so fallback to normal writes. writeSpinCount -= doWrite0(in); break; case 1: { ... in.removeBytes(localWrittenBytes); --writeSpinCount; break; } default: { ... in.removeBytes(localWrittenBytes); --writeSpinCount; break; } } } while (writeSpinCount > 0); incompleteWrite(writeSpinCount < 0); }
public void removeBytes(long writtenBytes) { for (;;) { ... if (readableBytes <= writtenBytes) { if (writtenBytes != 0) { progress(readableBytes); writtenBytes -= readableBytes; } remove(); } else { // readableBytes > writtenBytes ... } } clearNioBuffers(); }
最终会调用它的remove方法,源码如下:
public boolean remove() { ... if (!e.cancelled) { // only release message, notify and decrement if it was not canceled before. ReferenceCountUtil.safeRelease(msg); safeSuccess(promise); decrementPendingOutboundBytes(size, false, true); } // recycle the entry e.recycle(); return true; }
可以看出,最终会调用ReferenceCountUtil.safeRelease(msg);来释放内存。