Netty入门,原理,源码解析与验证

简介

Netty设计为事件驱动模型,业务逻辑的处理都是通过事件来触发,所以Netty抽象出了事件处理器,并分为“入站”和“出站”处理器,这些处理器又采用抽象出的“管道”来进行连接,“管道”则是一个双向链表,每个处理器都是链表上的一个节点,事件或消息通过管道在各个处理器之间流动。

组件介绍

启动器

Bootstrap

客户端程序的启动器,通过启动器则可以启动一个客户端程序,用来与服务端通信并进行业务处理。

ServerBootstrap

顾名思义:服务端启动器,通过启动器可以启动一个服务端程序,用来与客户端通信并进行业务处理。

处理器

入站处理器

处理器用来对消息进行处理,例如:对消息解密。入站处理器则是专门处理对端发送过来的消息,例如服务端处理客户端发送的消息,同理:客户端也可以用入站处理器处理服务端回复的消息。

出站处理器

和入站处理器正好相反,它用来处理发送给对端的消息,它们处理消息的顺序正好相反,消息在管道中流动的顺序也相反。例如:当服务端回复给客户端消息时,可能也需要加密,这时加密处理由出站处理器进行。

通道

Netty将服务端和客户端通信的链路抽象为“Channel”,也就是“通道”。通过通道,我们可以从中读取数据也可以写入数据到通道。

事件循环和事件循环组

Netty采用Reactor模型,其中事件循环是指有一个线程在无限循环,等待IO事件到来,然后进行处理。事件循环组包含多个事件循环线程,用来并行处理IO事件。

原理和验证

  1. 事件循环由一个永远不变的线程驱动
    验证:
    我们在启动时会设置一个事件循环组,它的作用是注册通道和返回下一个事件循环以供使用,通过继承关系,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方法时。

  2. 一个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。

  3. 每个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。

  4. 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可以拦截消息。

  5. 当前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

  6. 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则是作为双向链表的节点。

  7. 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);
    }
    

    可以验证上面的结论。

  8. 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方法,该方法则是捕获入站事件并进行处理的方法。

  9. 当消息传递到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);来释放内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值