首先我们来思考一个问题,什么是Channel?
Channel是Netty抽象出来的对网络I/O进行读/写的相关接口,与NIO中的Channel接口类似。
那么Channel有哪些主要功能?
- 网络I/O的读/写
- 客户端发起连接
- 主动关闭连接、关闭链路
- 获取通信双方的地址
说明:
Netty支持除了TCP以外的多种协议。不同协议、不同阻塞类型的连接会有所不同的Channel类型与之对应。
下面我们来看一下常见的几种Channel:
1. AbstractChannel
- 首先他有几个主要属性:
EventLoop:每个Channel对应一条EventLoop线程。
DefaultChannelPipeline:一个Handler的容器,也可以将其理解为一个Handler链。Handler主要处理数据的编/解码和业务逻辑。
Unsafe:实现具体的连接与读/写数据,如网络的读/写、链路关闭、发起连接等。命名为Unsafe表示不对外提供使用,并非不安全。
我们来看一下AbstractChannel的功能图:
接下来我们来看源码:
- 先看一些全局变量:
private final Channel parent;
private final ChannelId id;
// 实现具体的连接读/写数据,如网络的读/写、链路关闭、发起连接等,unsafe表示不对外提供使用,并非不安全
private final Unsafe unsafe;
// handler调用链
private final DefaultChannelPipeline pipeline;
private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false);
private final CloseFuture closeFuture = new CloseFuture(this);
private volatile SocketAddress localAddress;
private volatile SocketAddress remoteAddress;
// 每个channel对应一个eventloop线程
private volatile EventLoop eventLoop;
private volatile boolean registered;
private boolean closeInitiated;
private Throwable initialCloseCause;
- AbstractChannel中有一个子类AbstractUnsafe,这个类大量采用了“模板设计模式”,具体的实现由子类完成,例如其中的bind()方法:
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
assertEventLoop();
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
// See: https://github.com/netty/netty/issues/576
if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
localAddress instanceof InetSocketAddress &&
!((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
!PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
// Warn a user about the fact that a non-root user can't receive a
// broadcast packet on *nix if the socket is bound on non-wildcard address.
logger.warn(
"A non-root user can't receive a broadcast packet if the socket " +
"is not bound to a wildcard address; binding to a non-wildcard " +
"address (" + localAddress + ") anyway as requested.");
}
boolean wasActive = isActive();
try {
// 模板设计模式,调用子类NioServerSocketChannel的doBind()方法
doBind(localAddress);
} catch (Throwable t) {
// 绑定失败回调
safeSetFailure(promise, t);
closeIfClosed();
return;
}
// 从非活跃状态到活跃状态触发了active事件
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
// 绑定成功回调通知
safeSetSuccess(promise);
}
2. AbstractNioChannel
AbstractNioChannel 继承了 AbstractChannel,不过在其基础上又增加了一些属性和方法。
// 真正用到的NIO channel
private final SelectableChannel ch;
// 监听感兴趣的事件
protected final int readInterestOp;
// 注册到Selector后获取Key
volatile SelectionKey selectionKey;
我们来看其中的一个主要方法,doRegister():
/**
* 在AbstractUnsafe的register0()方法中调用
* @throws Exception
*/
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
/**
* 通过javaChannel()方法获取具体的Nio Channel
* 把Channel注册到其EventLoop线程的Selector上
* 对于注册后返回的selectionKey,需要为其设置Channel感兴趣的事件
*/
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.
// 由于尚未调用select.select(...)
// 因此可能仍在缓存而未删除但已取消selectionKey
// 强制调用 selector.selectNow方法
// 将已经取消的selectionKey 从Selector上删除
eventLoop().selectNow();
selected = true;
} else {
// 只有第一次跑出此异常, 才能调用selector.selectorNow进行取消
// 如果调用selector.selectNow 还有取消的缓存,则可能是JDK的一个bug
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
}
在AbstractNioChannel中有个非常重要的类——AbstractNioUnsafe,它继承了AbstractUnsafe,并且实现了其中的connect,flush0()等方法。
我们来探究一下connect方法:
@Override
public final void connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
// 设置任务为不可取消状态,并确定channel已打开
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
try {
// 确保没有正在进行的连接
if (connectPromise != null) {
// Already a connect in process.
throw new ConnectionPendingException();
}
// 获取之前的状态
boolean wasActive = isActive();
/**
* 在远程连接时,会出现以下三种结果。
* 1. 连接成功,返回true
* 2. 暂时没有连接上,服务端没有返回ACK应答,连接结果不确定,返回false
* 3. 连接失败,直接抛出I/O异常