前言
上一篇文章写了Netty中的各个核心组件,这些核心组件是如何协作的,下面我们来看看
服务端启动代码
先来看一段服务端启动的代码
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
// 1.创建两个线程组 bossGroup 和 workerGroup
// 2.bossGroup只是处理连接请求,真正的和客户端业务处理,会交给workerGroup完成
// 3.两个都是无限循环
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap = new ServerBootstrap();
// 使用链式编程来进行设置
// 设置两个线程组
bootstrap.group(bossGroup, workerGroup)
// 使用NioServerSocketChannel作为服务器的通道实现
.channel(NioServerSocketChannel.class)
// 设置线程队列得到连接个数
.option(ChannelOption.SO_BACKLOG, 128)
// 设置保持活动连接状态
.childOption(ChannelOption.SO_KEEPALIVE, true)
// 创建一个通道测试对象
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println("server is ready");
// 绑定一个端口并且同步,生成一个ChannelFuture对象
// 启动服务器(并绑定端口)
ChannelFuture cf = bootstrap.bind(6668).sync();
// 对关闭通道进行监听
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
服务端参数设置
bootstrap.group(bossGroup, workerGroup)
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
// 对父类AbstractBootstrap中的group进行赋值
super.group(parentGroup);
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");
return this;
}
bootstrap.group方法主要是对属性进行了赋值,在AbstractBootstrap中有group(bossGroup)的成员变量,ServerBootstrap中有childGroup(workerGroup)的成员变量,这两个group有点像我们平时的工作,bossGroup是老板,拼命从外面接活(不断接收新的连接),childGroup是工人,拼命加班干活(处理所连接客户端的业务操作)
bootstrap.channel(NioServerSocketChannel.class)
设置完老板和工人后,为了跟外界客户有比较良好的沟通,我们需要客服专员,bootstrap.channel就是设置客服专员,每当有新的连接请求,就会新建Channel,客户端和服务端通过Channel进行通信
public B channel(Class<? extends C> channelClass) {
return channelFactory(new ReflectiveChannelFactory<C>(
ObjectUtil.checkNotNull(channelClass, "channelClass")
));
}
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Constructor<? extends T> constructor;
// 此构造器是对成员变量进行赋值
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);
}
}
@Override
public String toString() {
return StringUtil.simpleClassName(ReflectiveChannelFactory.class) +
'(' + StringUtil.simpleClassName(constructor.getDeclaringClass()) + ".class)";
}
}
查看源码,会发现bootstrap.channel(NioServerSocketChannel.class)使用工厂模式,后续使用newChannel方法即可通过反射方式构造出NioServerSocketChannel实例
bootstrap.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
option()方法就是设置变量参数,option是设置父类AbstractBootstrap中的参数,childOption是设置ServerBootstrap中的参数,下方是设置option的代码,也是非常简单
public <T> B option(ChannelOption<T> option, T value) {
ObjectUtil.checkNotNull(option, "option");
synchronized (options) {
if (value == null) {
options.remove(option);
} else {
options.put(option, value);
}
}
return self();
}
public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) {
ObjectUtil.checkNotNull(childOption, "childOption");
synchronized (childOptions) {
if (value == null) {
childOptions.remove(childOption);
} else {
childOptions.put(childOption, value);
}
}
return this;
}
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});
此处也是设置ServerBootstrap中的参数
doBind
我们都知道NIO有三大组件,Buffer、Channel、Selector,Netty是对NIO做了一层包装,让NIO更简单易用,那么NIO的操作流程跟Netty理论上应该是类似的,在NIO中,Channel需要注册到Selector中,在上一篇文章中,我写了NioEventLoopGroup在初始化的过程中,每一个NioEventLoop都会创建一个Selector,在NioServerSocketChannel初始化的过程,会创建Channel和ChannelPipeline,那么Channel和Selector是怎么关联起来的,玄机就在bind方法中,对bind方法一路追踪,真正干活的是doBind方法
private ChannelFuture doBind(final SocketAddress localAddress) {
// 新建一个Channel对象
// 注意,此处是一个异步操作
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
// 判断该对象是否抛出异常
if (regFuture.cause() != null) {
return regFuture;
}
// 判断initAndRegister方法是否执行完毕
if (regFuture.isDone()) {
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPromise();
// 进行doBind操作
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// 如果initAndRegister方法没有执行完毕,则增加一个监听器,等待执行完毕回调下面的方法,思路跟上面代码是一致的
// 判断是否产生异常,如果没有异常则进行doBind操作
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;
}
}
在这里需要特别说明的是initAndRegister()
是一个异步方法,一个EventLoop在整个生命周期中只会与一个Thread进行绑定,Channel实际上就是注册到该EventLoop的Selector中,注册这个动作只能由与EventLoop绑定的Thread执行(为什么要这样设计呢?假设每个线程都可以执行这个操作,多线程的各种问题自然就出来了,Netty需要额外考虑各种加锁,解决多线程的问题),那Netty是怎么做到只能由绑定线程执行注册操作呢?我们在Netty的代码中可以看到比较多的ch.eventLoop().execute()这种类型的代码,如果执行代码的线程是绑定线程,那就直接执行即可,如果不是,则使用提交任务的方式,异步使该线程执行,既然是异步,就会有该任务是否执行完成的问题,所以Netty自定义了ChannelFuture来解决这个异步的问题(详情可见我上一篇文章),在doBind方法中,initAndRegister()
是一个异步方法,后面会判断该方法的异步执行是否完成,如果完成,则直接执行doBind0方法,如果未执行完成,则等待监听器回调
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 上面bootstrap.channel(NioServerSocketChannel.class)设置参数的时候会设置ReflectiveChannelFactory
// 专门生产什么样的对象,这里就是具体使用到的地方,使用反射的方式实例一个NioServerSocketChannel对象
channel = channelFactory.newChannel();
// 对Channel做初始化参数设置
init(channel);
} catch (Throwable t) {
......
}
// 将Channel注册到Selector上
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
再具体看看NioServerSocketChannel的初始化过程,里面有比较关键的设置
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置为非阻塞
serverSocketChannel.configureBlocking(false);
// channel注册到selector中,关心事件为 OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
我们平时使用NIO的时候一般都会写如上两行代码进行Channel的设置,在Netty中,设置的地方在NioServerSocketChannel的父类中,在NioServerSocketChannel初始化的过程中,会层层初始化其父类,我们先观察一下该类的继承图
// 设置的地方在这两个父类中
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
// 设置SelectionKey.OP_ACCEPT
this.readInterestOp = readInterestOp;
try {
// 设置为非阻塞的
ch.configureBlocking(false);
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
// ChannelPipeline和Channel是一一搭配的,Channel创建的时候ChannelPipeline也会随之创建
pipeline = newChannelPipeline();
}
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);
}
回到initAndRegister()方法,看看init()方法具体干了什么
@Override
void init(Channel channel) {
// 设置配置好的option参数
setChannelOptions(channel, newOptionsArray(), logger);
setAttributes(channel, newAttributesArray());
// NioServerSocketChannel初始化过程中,AbstractChannel这个抽象类会创建ChannelPipeline
// 此处获取到ChannelPipeline
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);
// 为ChannelPipeline添加一个初始化的Handler
// 当NioServerSocketChannel在EventLoop注册成功时,这个方法会被调用
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
// 如果用户自己设置handler不为空,添加进去
if (handler != null) {
pipeline.addLast(handler);
}
// 为NioServerSocketChannel当pipeline添加ServerBootstrapAcceptor
// 为了保证这个添加操作一定是在EventLoop线程中执行,添加操作被包装成一个任务提交到EventLoop线程中
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
回到initAndRegister()方法,继续看注册操作
@Override
public ChannelFuture register(Channel channel) {
// 将Channel和EventLoop封装成DefaultChannelPromise对象,DefaultChannelPromise实现了ChannelPromise,拥有
// ChannelFuture和Promise这两个接口的特性,详情可见我上一篇文章
return register(new DefaultChannelPromise(channel, this));
}
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
ObjectUtil.checkNotNull(eventLoop, "eventLoop");
// 如果该Channel已注册过,不重复注册
if (isRegistered()) {
promise.setFailure(new IllegalStateException("registered to an event loop already"));
return;
}
if (!isCompatible(eventLoop)) {
promise.setFailure(
new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
return;
}
AbstractChannel.this.eventLoop = eventLoop;
// 一个EventLoop在整个生命周期都只会与一个Thread进行绑定,这个判断实际上就是判断当前线程是否是与EventLoop绑定的线程
// 以此来辨别是不是该EventLoop,因为与该EventLoop绑定的线程此时可能没有执行权
if (eventLoop.inEventLoop()) {
// 进行注册
register0(promise);
} else {
try {
// 如果当前线程并不是跟EventLoop绑定的线程,则把注册包装成一个任务提交到EventLoop线程中
// 程序会切换线程执行注册任务
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);
}
}
}
private void register0(ChannelPromise promise) {
try {
// 检查该channel目前是否处于open状态
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
// 终于到了真正的注册操作
doRegister();
// 设置标识位,防止重复注册
neverRegistered = false;
registered = true;
// 此处会执行之前init()方法中添加进Channel的初始化Handler
pipeline.invokeHandlerAddedIfNeeded();
// 设置注册结果为成功
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
// Close the channel directly to avoid FD leak.
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
// 将Channel注册到Selector上
// 注意:此处关注的事件是0,后续会变更关注的事件
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
eventLoop().selectNow();
selected = true;
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
}
到此处,Channel终于注册到了Selector上,但是此处设置的关注的事件是0,NIO的代码中,关注的事件是SelectionKey.OP_ACCEPT
,这个地方是在哪里设置的呢?我们接着往下看
看完initAndRegister()
,我们要走出来,回到doBind方法中,其中有一个关键的方法doBind0
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
assertEventLoop();
.....
boolean wasActive = isActive();
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);
}
因为代码的调用链非常长,我们直接看关键代码
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
// 判断java版本是否大于7
if (PlatformDependent.javaVersion() >= 7) {
// 绑定端口号
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.fireChannelActive();
// 此处进入
readIfIsAutoRead();
}
@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);
}
}
至此,我们在NIO中常写的几行代码,在Netty中全部展示出来了,不得不说,Netty的代码封装得很好(写得很绕)
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
Selector selector = Selector.open();
// 监听端口6666
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
serverSocketChannel.configureBlocking(false);
// channel注册到selector中,关心事件为 OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);