2021SC@SDUSC
1. 核心组件
- EventLoop 和 EventLoopGroup(已分析)
- ChannelPipeline(ChannelPipeline中已分析)
- ChannelHandler(ChannelPipeline中已分析)
- ChannelHandlerContext(已分析)
- Channel(本节分析)
- Channel(本节分析)
2. ChannelHandler责任链模式的过滤链 (已分析)
3. 通信协议和私有协议栈开发
目录
4.Channel选择器工厂与轮询算法及Channel注册过程
这次主要分析的内容是进行剩余的重要组件Channel,Selector的分析,并且结合前面分析的组件对Netty的一个整体结构进行梳理,以求对整体结构有更好的把握。
一.Channel
1. 认识Channel
Channel是客户端和服务端建立的一个连接通道,是Netty抽象出来的网络I/O读写相关的接口;客户端有一个Channel(SocketChannel),服务端也有一个Channel(NioSocketChannel),当客户端和服务端建立连接后,客户端的Channel会跟服务端的Channel进行联通;
2. Channel网络I/O相关的方法定义
我们来看一下对于每个方法的功能:
id():获取该channel的标识
eventloop():获取该channel注册的线程池
parent():获取该channel的父channel,NIO没有父channel,一般为null
config():获取该channel的配置
isOpen():该channel是否打开状态
isRegistered():该channel是否注册到线程池中
isActive():该channel是否可用
metadata():该channel的元数据
localAddress():该channel的本地绑定地址端口
remoteAddress():该channel连接的对端的地址端口
closeFuture():该channel关闭时触发的future
isWritable():该channel当前是否可写,只有IO线程会处理可写状态
bytesBeforeUnwritable():该channel还能写多少字节
unsafe():获取该channel的unsafe操作对象。对于channel的读写,一般不直接操作channel,而是转交给unsafe对象处理,channel本身通常只做查询状态,获取相关字段内容的操作。
alloc():获取分配的缓冲区
read():进行read操作
write():进行write操作
上面的方法我们看见了一个不熟悉的unsafe对象,这个也是一个比较重要的概念,理解这个类在整个结构所处的位置作用,对于我们理解框架有较大的帮助。
Unsafe被直接定义在Channel接口内部,意味着该接口是与Channel绑定的,上述方法的时候也解释过该类作用。channel本身不直接做相应的工作,交给unsafe方法调用。下图是unsafe的接口定义:
我们来看一下对于每个方法的功能:
recvBufAllocHandle():获取处理读取channel数据之后处理的handler
localAddress():本地地址端口
remoteAddress():远程地址端口
register():将channel注册到线程池中
bind():服务端绑定本地端口
connect():客户端连接远程端口
disconnect():断开连接
close():关闭channel
closeForcibly():强制关闭
deregister():移除线程池的注册
beginRead():准备读取数据
write():写入数据
flush():强制刷新
voidPromise():特殊的promise
outboundBuffer():获取输出数据的buffer操作类
从上面的一系列接口我们可以了解到,实际操作channel进行I/O读写操作的是unsafe类。channel本身不直接做相应的工作。
3. Channel生命周期
Channel生命周期如下(此生命周期即每次进行网络连接都要经过的流程):
当这些状态发生改变时,将会生成对应的事件。
这些事件将会被转发给 ChannelPipeline 中的 ChannelHandler,其可以随后对它们做出响应。
4.Channel选择器工厂与轮询算法及Channel注册过程
在分析EventLoop的时候,我们就知道在Netty 中, 每个 Channel 都有且仅有一个 EventLoop 与之关联。它们关联的过程如下所示:
我们观察到注册的入口,即在MultithreadEventLoopGroup.register(Channel channel)方法:
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
注册channel第一步调用了next()方法,next()是MultithreadEventLoopGroup里边的,获取一个可用的EventLoop。
public EventLoop next() {
return (EventLoop) super.next();
}
可以看出来这里只是调用了父类的next()方法。
我们看一下next()方法在父类MultithreadEventExecutorGroup中的实现:
public EventExecutor next() {
return chooser.next();
}
这里出现了一个chooser,我们来看一下它的结构:
private final EventExecutorChooserFactory.EventExecutorChooser chooser;
这里的EventExecutorChooserFactory是一个工厂,生产各种Executor。
我们先来看一下DefaultEventExecutorChooserFactory:
/*
* 默认使用round-robin算法选择下一个实例的EventExecutor实现
* round-robin:主要用在负载均衡方向,比如有5台机器,
第一次分请求到了第一台机器,第二次到了第二台机器,第三次请求到了第三台请求,
以此类推一直到第五台机器,然后第六次又到了第一台机器。
这样一个轮流的调用,处理负载。这里的Executor数组也是使用这种方式,
保证数组里边的EventExecutor被均衡调用。
*/
public final class DefaultEventExecutorChooserFactory implements EventExecutorChooserFactory {
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
PowerOfTwoEventExecutorChooser和GenericEventExecutorChooser都是DefaultEventExecutorChooserFactory 的静态内部类,都有next()方法返回一个EventExecutor。
当有2的指数个executor的时候,使用PowerOfTwoEventExecutorChooser性能会比非指数个的GenericEventExecutorChooser性能高一点。所以这里进行了这样的处理。
以上是对chooser的创建的一个分析, 现在回到MultithreadEventExecutorGroup看一下对chooser的赋值:
public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {
private final EventExecutor[] children;
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
children[i] = newChild(executor, args);
}
chooser = chooserFactory.newChooser(children);
}
}
children的来源是newChild()方法,这个方法我们也在EventLoop中进行过分析。
/*
创建一个EventExecutor ,稍后可以调用next()方法,这个next()方法被每个线程调用,这些线程是服务MultithreadEventExecutorGroup的
*/
protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception;
接下来我们看一下register方法。
/**
* Abstract base class for {@link EventLoop}s that execute all its submitted tasks in a single thread.
* EventLoop的基础抽象类,所有提交的任务都会在一个线程里边执行。
*/
public abstract class SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop {
//关键代码
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
}
这里的DefaultChannelPromise是ChannelFuture的具体实现, 其持有Channel 和当前的EventLoop。
public DefaultChannelPromise(Channel channel, EventExecutor executor) {
super(executor);
this.channel = channel;
}
父类的实现方法如下图:
public class DefaultPromise<V> extends AbstractFuture<V> implements Promise<V> {
public DefaultPromise(EventExecutor executor) {
this.executor = checkNotNull(executor, "executor");
}
}
最后我们来到register方法:
public abstract class SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
}
这里的ChannelPromise 结构,其实是一个异步返回结果的封装,它持有channel和当前的SingleThreadEventLoop 。这个方法是注册逻辑的真正的入口了,出现了unsafe对象。前面我们说过是unsafe是能够真正的操作Channel。我们来看一下这个方法:
protected abstract class AbstractUnsafe implements Unsafe {
private volatile ChannelOutboundBuffer outboundBuffer = new ChannelOutboundBuffer(AbstractChannel.this);
private RecvByteBufAllocator.Handle recvHandle;
private boolean inFlush0;
/** true if the channel has never been registered, false otherwise */
private boolean neverRegistered = true;
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
}
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;
//主要的注册逻辑分支,if和else分支可以看到最后调用的都是register0,但是else里边加了一个外壳---线程
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);
}
}
}
}
这里通过下面的语句对channel和eventLoop进行了绑定:
AbstractChannel.this.eventLoop = eventLoop;
这里的逻辑是判断当前线程是不是SingleThreadEventExecutor里边维护的线程。如果SingleThreadEventExecutor里边的线程不是当前线程,就新建一个Thread去执行register0,下边看一下register0的逻辑:
private void register0(ChannelPromise promise) {
try {
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
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():
public abstract class AbstractNioChannel extends AbstractChannel {
//关键代码
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
eventLoop().selectNow();
selected = true;
} else {
throw e;
}
}
}
}
}
javaChannel() 返回的是SelectableChannel。javaChannel().register是将channel注册到Selector上去,所以eventLoop().unwrappedSelector()返回的是Selector。
到这里我们看到了最终netty的JavaNio的注册实现。
小结:
结合之前对EventLoop的分析和通过上面对整个注册流程的代码分析,有下面这几点,我们要十分注意:
- 所有由EventLoop所处理的各种I/O事件都将在它所关联的那个Thread上进行处理。
- 一个Channel在它的整个生命周期中只会注册在一个EventLoop上。
- 一个EventLoop在运行过程中,会被分配给一个或多个Channel。
二. ChannelFuture的用法
我们下面简单的介绍一下ChannelFuture的用法。
ChannelFuture是Channel异步IO操作的结果。
什么是异步操作呢?
这个在我们上学期学习完操作系统后应该很好理解。异步操作比如说我执行一个IO调用,异步操作是立即返回的,同步操作是等待IO执行完毕后带着结果返回的,可能需要等待很长时间,让程序阻塞在这里。
Netty中的所有IO操作都是异步的。这意味着任何IO调用都将立即返回,而不能保证所请求的IO操作在调用结束时完成。这里将返回一个带有ChannelFuture的实例,该实例将提供有关IO操作的结果或状态的信息。
ChannelFuture要么是未完成状态,要么是已完成状态。IO操作刚开始时,将创建一个新的Future对象。新的Future对象最初处于未完成的状态。如果IO操作因为执行成功、执行失败或者执行取消导致操作完成,则将被标记为已完成的状态,并带有更多特定信息,例如失败原因。
到这里,我们可以大致猜测出netty的思想,既然调用还没有成功就返回了,那应该会有一个机制,在调用成功后来进行提醒。
ChannelFuture提供了各种方法,可以检查IO操作是否已完成,等待完成以及获取IO操作的结果。
在实现中,DefaultChannelFuture里面有一个listener的集合,addListener会添加listener到集合里面,然后再真正的io操作完成之后来触发相应的listener。
addListener(GenericFutureListener):
这个方法是非阻塞的。它只是将指定的ChannelFutureListener添加到ChannelFuture,并且与将来关联的IO操作完成时,IO线程将通知监听器。ChannelFutureListener完全不阻塞,因此可产生最佳的性能和资源利用率。
小结
截止到现在,对于Netty核心组件这些基础内容已经分析完毕。前面花费的时间也比较多,分析的也比较认真,是从最基础的源码一点一点去找的,为了打下一个比较好的基础。现在目前已经对netty的整体运作有了一个比较清楚的认识。还有一部分关于netty的线程模型等,我也进行了学习,如果后面还有时间,会再进行补充。
下面要更多地学习在基础上,netty是如何实现其他功能的。我主要负责Netty通信协议和私有协议栈开发这一部分。又是一个新的起点,希望可以顺利分析下去。