webIM系列之四——ServerBootstrap(1)

284 阅读6分钟

上一篇分析了NioEventLoopGroup的初始化都做了哪些准备,这一篇分析一下Netty给我们提供的贴心的ServerBootstrap启动类,都帮我们做了什么。先回顾一下前面我们写过的Netty服务的代码:

package com.example.nettysourcedemo;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class ServerRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        /**
         * NioEventLoopGroup本身是一个eventLoopExecutor,里面包含一个childGroup=EventExecutor[]
         * 数组中是NioEventLoop,每一个NioEventLoop中都包含一个selector,还包含一个executor
         * 这个executor可以用来执行任务,在使用executor.execute执行任务时,会使用ThreadPerTaskExecutor.execute()方法执行
         * 这个方法又会调用DefaultThreadFactory创建一个线程来启动任务的执行
         * 重点关注NioEventLoopGroup构造器中执行的父类构造器
         */
        NioEventLoopGroup boss = new NioEventLoopGroup(new DefaultThreadFactory("boss"));
        NioEventLoopGroup worker = new NioEventLoopGroup(new DefaultThreadFactory("worker"));
        NioEventLoopGroup business = new NioEventLoopGroup(new DefaultThreadFactory("business"));

        ServerBootstrap b = new ServerBootstrap();
        b.group(boss,worker)
                //调用ReflectiveChannelFactory的构造器,创建NioServerSocketChannel
                .channel(NioServerSocketChannel.class)
                //设置boss线程的属性
                .option(ChannelOption.SO_BACKLOG,1024)
                //设置worker线程的属性,参考Linux tcp参数设置
                .childOption(ChannelOption.TCP_NODELAY,true)
                //ChannelInitializer也是一个ChannelInboundHandler,主要作用是往pipeline中添加handler,添加完handler之后就会从pipeline中移除
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        //使用单独的业务线程池执行业务handler
                        pipeline.addLast(business,"stringDecoder",new StringDecoder());
                        pipeline.addLast(business,"stringEncoder",new StringEncoder());
                        pipeline.addLast(business,"simpleHandler",new SimpleHandler());
                        pipeline.addLast(business,"realHandler",new RealHandler());
                    }
                });
        ChannelFuture future = b.bind(9080).sync();
        log.info("netty server started");
        future.channel().closeFuture().sync();
        boss.shutdownGracefully();
        worker.shutdownGracefully();
        business.shutdownGracefully();
    }
}

首先通过group()方法,将两个线程池交给ServerBootstrap,这两个线程池的作用,可以通过方法的文档有所了解:

Set the EventLoopGroup for the parent (acceptor) and the child (client). These EventLoopGroup's are used to handle all the events and IO for ServerChannel and Channel's.

boss线程池中的线程,在Reactor模型中,充当的是acceptor的角色,它负责监听客户端的连接请求,既Java NIO中SelectionKey定义的OP_ACCEPT事件。boss线程监听到有OP_ACCEPT事件后,将其交由worker线程池中的线程,处理该连接的所有事件和IO操作。这里可以先告诉大家一个结论,即:server端监听几个端口,boss线程池里就只会用到几个线程,其他线程是不用到的,所以boss线程池不需要设置过多的线程数。

channel()方法,用于定义服务端使用的channel类型,我们通常也只会使用NioServerSocketChannel这一种,通过源码我们可以看到,它调用了ReflectiveChannelFactory的构造器,通过反射,创建了NioServerSocketChannel的实例。

public B channel(Class<? extends C> channelClass) {
        return channelFactory(new ReflectiveChannelFactory<C>(
                ObjectUtil.checkNotNull(channelClass, "channelClass")
        ));
    }

public ReflectiveChannelFactory(Class<? extends T> clazz) {
        ObjectUtil.checkNotNull(clazz, "clazz");
        try {
            this.constructor = clazz.getConstructor();
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
                    " does not have a public non-arg constructor", e);
        }
    }

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

通过NioServerSocketChannel的构造器,注册了SelectionKey.OP_ACCEPT事件。

public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }
    
public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

通过调用父类构造器,在AbstractChannel类中,初始化ChannelPipeline:

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

继续跟进newChannelPipeline()方法,在DefaultChannelPipeline通过构造器,创建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;
    }

pipeline是Netty中的大动脉,所有的业务处理,都在这个大动脉中进行,初始化pipeline的过程中,在其中创建了TailContext和HeadContext两个节点,我们所有的自定义handler,都会在这个大动脉的tail和head中间,像汽车制造流水线一样,每个节点处理完成之后,再交给下一个节点执行进一步的操作,另外,pipeline中还存在NioServerSocketChannel的实例。

TailContext是一个channelInboundHandler,并且实现了ChannelInboundInvoker和ChannelOutboundInvoker接口,也就是说,TailContext可以调用channelInbound的相关方法,也能调用channelOutbound的相关方法。 

而HeadContext是一个channelOutboundHandler,并且也实现了ChannelInboundInvoker和ChannelOutboundInvoker接口,所以pipeline才能够执行真个流水线上的节点的相关方法。 

ChannelInboundHandler和ChannelOutboundHandler是我们作为Netty的使用者,打交道最多的两种类,我们的业务逻辑,都是需要通过重写接口定义的方法来实现的。

public interface ChannelInboundHandler extends ChannelHandler {

    /**
     * The {@link Channel} of the {@link ChannelHandlerContext} was registered with its {@link EventLoop}
     */
    void channelRegistered(ChannelHandlerContext ctx) throws Exception;

    /**
     * The {@link Channel} of the {@link ChannelHandlerContext} was unregistered from its {@link EventLoop}
     */
    void channelUnregistered(ChannelHandlerContext ctx) throws Exception;

    /**
     * The {@link Channel} of the {@link ChannelHandlerContext} is now active
     */
    void channelActive(ChannelHandlerContext ctx) throws Exception;

    /**
     * The {@link Channel} of the {@link ChannelHandlerContext} was registered is now inactive and reached its
     * end of lifetime.
     */
    void channelInactive(ChannelHandlerContext ctx) throws Exception;

    /**
     * Invoked when the current {@link Channel} has read a message from the peer.
     */
    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;

    /**
     * Invoked when the last message read by the current read operation has been consumed by
     * {@link #channelRead(ChannelHandlerContext, Object)}.  If {@link ChannelOption#AUTO_READ} is off, no further
     * attempt to read an inbound data from the current {@link Channel} will be made until
     * {@link ChannelHandlerContext#read()} is called.
     */
    void channelReadComplete(ChannelHandlerContext ctx) throws Exception;

    /**
     * Gets called if an user event was triggered.
     */
    void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;

    /**
     * Gets called once the writable state of a {@link Channel} changed. You can check the state with
     * {@link Channel#isWritable()}.
     */
    void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;

    /**
     * Gets called if a {@link Throwable} was thrown.
     */
    @Override
    @SuppressWarnings("deprecation")
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}

public interface ChannelOutboundHandler extends ChannelHandler {
    /**
     * Called once a bind operation is made.
     *
     * @param ctx           the {@link ChannelHandlerContext} for which the bind operation is made
     * @param localAddress  the {@link SocketAddress} to which it should bound
     * @param promise       the {@link ChannelPromise} to notify once the operation completes
     * @throws Exception    thrown if an error occurs
     */
    void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;

    /**
     * Called once a connect operation is made.
     *
     * @param ctx               the {@link ChannelHandlerContext} for which the connect operation is made
     * @param remoteAddress     the {@link SocketAddress} to which it should connect
     * @param localAddress      the {@link SocketAddress} which is used as source on connect
     * @param promise           the {@link ChannelPromise} to notify once the operation completes
     * @throws Exception        thrown if an error occurs
     */
    void connect(
            ChannelHandlerContext ctx, SocketAddress remoteAddress,
            SocketAddress localAddress, ChannelPromise promise) throws Exception;

    /**
     * Called once a disconnect operation is made.
     *
     * @param ctx               the {@link ChannelHandlerContext} for which the disconnect operation is made
     * @param promise           the {@link ChannelPromise} to notify once the operation completes
     * @throws Exception        thrown if an error occurs
     */
    void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    /**
     * Called once a close operation is made.
     *
     * @param ctx               the {@link ChannelHandlerContext} for which the close operation is made
     * @param promise           the {@link ChannelPromise} to notify once the operation completes
     * @throws Exception        thrown if an error occurs
     */
    void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    /**
     * Called once a deregister operation is made from the current registered {@link EventLoop}.
     *
     * @param ctx               the {@link ChannelHandlerContext} for which the close operation is made
     * @param promise           the {@link ChannelPromise} to notify once the operation completes
     * @throws Exception        thrown if an error occurs
     */
    void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    /**
     * Intercepts {@link ChannelHandlerContext#read()}.
     */
    void read(ChannelHandlerContext ctx) throws Exception;

    /**
    * Called once a write operation is made. The write operation will write the messages through the
     * {@link ChannelPipeline}. Those are then ready to be flushed to the actual {@link Channel} once
     * {@link Channel#flush()} is called
     *
     * @param ctx               the {@link ChannelHandlerContext} for which the write operation is made
     * @param msg               the message to write
     * @param promise           the {@link ChannelPromise} to notify once the operation completes
     * @throws Exception        thrown if an error occurs
     */
    void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;

    /**
     * Called once a flush operation is made. The flush operation will try to flush out all previous written messages
     * that are pending.
     *
     * @param ctx               the {@link ChannelHandlerContext} for which the flush operation is made
     * @throws Exception        thrown if an error occurs
     */
    void flush(ChannelHandlerContext ctx) throws Exception;
}

此外,ServerBootstrap还提供了childOption()方法,childAttr()方法,option()方法等,用来设置TCP相关属性、配置等,这里就不展开说了,另外一个非常关键的方法,是childHandler()方法用于设置我们的业务handler,我们下一篇来详细说一下这个方法。