Netty(二)

Netty源码分析

先来一段测试的服务端代码方便我们debug跟踪源码(用的是netty-all-4.1.39版本)

Netty-Server

public class TestServer {
    public static void main(String[] args) {
        new ServerBootstrap()
                .group(new NioEventLoopGroup(),new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new LoggingHandler());
                    }
                })
                .bind(9999);
    }
}

首先来看看bind(9999)这个方法,我们启用debug模式方便跟踪源码

private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }

        if (regFuture.isDone()) {
       
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            
            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) {
                        
                        promise.setFailure(cause);
                    } else {
                   
                        promise.registered();

                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }

final ChannelFuture regFuture = initAndRegister();

在这里会进行初始化ServerSocketChannel并给他绑定一个selector选择器,这里其实也是一个异步操作,主线程进去执行这些操作

regFuture.isDone()是判断主线程去执行 initAndRegister();结束没如果结束了则走里面如果没结束则走下面

regFuture.addListener(new ChannelFutureListener()则是主线程在等待回调,等待nioEventLoopGroup中的线程去执行绑定doBind0(regFuture, channel, localAddress, promise);

大家可以看看这两张图

图一是主线程执行到regFuture.addListener

 图二是nioEventLoopGroup中的线程去执行doBind0(regFuture, channel, localAddress, promise);

 接下来我们再展开分析initAndRegister()方法

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            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);
        }
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }

首先看第一句channelFactory.newChannel();很明显这是个Channel的工厂,我们的NioServerSocketChannel应该就是在这里创建的,跟踪进去

可以发现他跳到了ReflectiveChannelFactory类中

@Override
    public T newChannel() {
        try {
            return constructor.newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
        }
    }

 发现他是调用了public io.netty.channel.socket.nio.NioServerSocketChannel()构造器,那我们来验证一下在他的构造方法上打个断点

 会发现果然调用到这里来了,只是返回的是一个ServerSocketChannel,不过这是netty中的,且NioServerSocketChannel实现了它,那我们来看看jdk中的ServerSocketChannel是怎么创建的呢

很显然他也是调用了同样的方法,所以可以得出他两就是一回事,会一同创建

接下来在看init(channel);

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

有些不必要的代码我就没显示出来了,大家可以看到刚刚创建的NioServerSocketChannel在这里立马就往里面添加了一个处理器,但是注意,这里并没有执行,这里我们先在initChannel方法中打一个断点,看看他后续在什么时候调用

接下来就是给NioServerSocketChannel注册一些东西,并且完成线程的切换,也就是主线程在这里会把执行权交给nioEventLoopGroup中的线程

ChannelFuture regFuture = config().group().register(channel);

这里比较长,一直点进去跟踪到io.netty.channel.AbstractChannel类

@Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            ...

            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    logger.warn(
                            "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                            AbstractChannel.this, t);
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }

在这里可以看到,他会首先判断当前的线程是不是nioEventLoopGroup中的线程,这里我们可以想到,他并不是,因为他是主线程,所以会走到下面,会提交一个任务给nioEventLoopGroup的线程来执行,打个断点来验证

 会发现果然交给了nioEventLoopGroup中的线程,在这里也完成了线程的切换,注意nioEventLoopGroup中的线程不会一开始就创建好,而是在首次调用时创建,也就是懒加载模式

下面继续跟进

private void register0(ChannelPromise promise) {
    try {
        //1.绑定选择器
        doRegister();
        neverRegistered = false;
        registered = true;
        pipeline.invokeHandlerAddedIfNeeded();

        safeSetSuccess(promise);
        pipeline.fireChannelRegistered();
        if (isActive()) {
            if (firstRegistration) {
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
                beginRead();
            }
        }
    } catch (Throwable t) {
        closeForcibly();
        closeFuture.setClosed();
        safeSetFailure(promise, t);
    }
}

 会在doRegister();方法中绑定选择器,点进去

selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);

这个javaChannel就是我们之前创建好的原生java中的ServerSocketChannel,也就是和NioServerSocketChannel一起创建的

 第一个参数就是selector,这已经是在eventLoop中管理了所以可以直接调用,第二个参数就是关注的事件,目前是0表示不关注任何事件,第三个就是附件,表示将来发生了事件谁来处理这个事件,

到这里就完成了原生的channel注册到了netty中的channel上

下面就到了pipeline.invokeHandlerAddedIfNeeded();这一行方法上,大家还记不记得我们之前有一个处理器只添加进去了,但是还没有用,那就是在这里会回调到那个方法上

 果然运行到这一行代码上,所以这个initChannel方法是在注册完成之后调用的

接下来会发现他又给我们这个ServerSocketChannel上添加了一个处理器,而这个处理器就是专门处理连接的

然后就是运行到safeSetSuccess(promise)方法上,大家还记不记得我们的主线程执行了final ChannelFuture regFuture = initAndRegister();方法返回了一个regFuture然后添加了一个异步回调方法来执行doBind0(),那什么时候会回调呢,就是当regFuture拿到结果后就会调用,那谁给他这个结果呢,答案就是safeSetSuccess(promise)方法,还有一个疑问就是怎么确定regFuture和这个promise是同一个对象下面我们来验证

在debug模式下给regFuture打上标记如下面两张图

 会发现果然一样,那么就会回调执行 doBind0方法

下面跟进doBind0,因为调用链有点长所以我直接跟进到有意义代码的位置

@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
    try {
        doBind(localAddress);
    } catch (Throwable t) {
        safeSetFailure(promise, t);
        closeIfClosed();
        return;
    }
    if (!wasActive && isActive()) {
        invokeLater(new Runnable() {
            @Override
            public void run() {
                pipeline.fireChannelActive();
            }
        });
    }
    safeSetSuccess(promise);
}

doBind(localAddress);就是绑定我们的端口,我们继续跟进去看

@Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }

这里就是判断版本,然后config.getBacklog()就是读取我们的配置,比如服务器的全连接数量,关于全连接我后续会在另一篇博客中解释

现在端口号绑定完了,接下来就是看看这个通道是不是处于激活状态了,如果是的则会通知到我们的处理器,告诉他们该干活了也就是pipeline.fireChannelActive();这行代码

目前为止我们channel中有的处理器有三个head、acceptor、tail只有中间的是我们添加的,另一个是每个channel中自带的,但是这个方法真正让处理器干活的是head处理器,所以我们看到head处理器中的方法

@Override
        public void channelActive(ChannelHandlerContext ctx) {
            ctx.fireChannelActive();

            readIfIsAutoRead();
        }

我们的channel虽然激活了,但是并没有关注任何事件,所以在这里我们需要给channel绑定一个关注事件也就是在readIfIsAutoRead();方法里,最后就会调用到io.netty.channel.nio.AbstractNioChannel中

@Override
    protected void doBeginRead() throws Exception {
        // Channel.read() or ChannelHandlerContext.read() was called
        final SelectionKey selectionKey = this.selectionKey;
        if (!selectionKey.isValid()) {
            return;
        }

        readPending = true;

        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }

这里可以看到,他给channel关注了一个accept事件,到这里我们的服务器启动就完成了,下一篇来分析NioEventLoopGroup

如果有不对之处还请指出,希望对你有收获

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值