上一篇分析了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,我们下一篇来详细说一下这个方法。