1. 概述
在前面的文章中,我们已经不断看到 Netty Channel 的身影,例如:
- 在 《精尽 Netty 源码分析 —— 启动(一)之服务端》 中,我们看了服务端 NioServerSocketChannel 对象创建的过程。
- 在 《精尽 Netty 源码分析 —— 启动(二)之客户端》 中,我们看了客户端 NioSocketChannel 对象创建的过程。
但是,考虑到本小节的后续文章,我们还是需要这样一篇文章,整体性的再看一次 Channel 的面貌。
2. Channel
io.netty.channel.Channel
,实现 AttributeMap、ChannelOutboundInvoker、Comparable 接口,Netty Channel 接口。
在 《精尽 Netty 源码分析 —— Netty 简介(一)之项目结构》 中,我们对 Channel 的组件定义如下:
Channel 是 Netty 网络操作抽象类,它除了包括基本的 I/O 操作,如 bind、connect、read、write 之外,还包括了 Netty 框架相关的一些功能,如获取该 Channel 的 EventLoop 。
在传统的网络编程中,作为核心类的 Socket ,它对程序员来说并不是那么友好,直接使用其成本还是稍微高了点。而 Netty 的 Channel 则提供的一系列的 API ,它大大降低了直接与 Socket 进行操作的复杂性。而相对于原生 NIO 的 Channel,Netty 的 Channel 具有如下优势( 摘自《Netty权威指南( 第二版 )》) :
- 在 Channel 接口层,采用 Facade 模式进行统一封装,将网络 I/O 操作、网络 I/O 相关联的其他操作封装起来,统一对外提供。
- Channel 接口的定义尽量大而全,为 SocketChannel 和 ServerSocketChannel 提供统一的视图,由不同子类实现不同的功能,公共功能在抽象父类中实现,最大程度地实现功能和接口的重用。
- 具体实现采用聚合而非包含的方式,将相关的功能类聚合在 Channel 中,由 Channel 统一负责和调度,功能实现更加灵活。
2.1 基础查询
/** |
- 自身基本信息有
#id()
、#parent()
、#config()
、#localAddress()
、#remoteAddress()
方法。 - 每个 Channel 都有的核心组件有
#eventLoop()
、#unsafe()
、#pipeline()
、#alloc()
方法。
2.2 状态查询
/** |
一个正常结束的 Channel 状态转移有两种情况:
服务端用于绑定( bind )的 Channel 、或者客户端发起连接( connect )的 Channel 。
REGISTERED -> CONNECT/BIND -> ACTIVE -> CLOSE -> INACTIVE -> UNREGISTERED
服务端接受( accept )客户端的 Channel 。
REGISTERED -> ACTIVE -> CLOSE -> INACTIVE -> UNREGISTERED
一个异常关闭的 Channel 状态转移不符合上面的。
2.3 IO 操作
|
这两个方法,继承自 ChannelOutboundInvoker 接口。实际还有如下几个:
ChannelFuture bind(SocketAddress localAddress);
ChannelFuture connect(SocketAddress remoteAddress);
ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress);
ChannelFuture disconnect();
ChannelFuture close();
ChannelFuture deregister();
ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise);
ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise);
ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
ChannelFuture disconnect(ChannelPromise promise);
ChannelFuture close(ChannelPromise promise);
ChannelFuture deregister(ChannelPromise promise);
ChannelOutboundInvoker read();
ChannelFuture write(Object msg);
ChannelFuture write(Object msg, ChannelPromise promise);
ChannelOutboundInvoker flush();
ChannelFuture writeAndFlush(Object msg, ChannelPromise promise);
ChannelFuture writeAndFlush(Object msg);对比下来,我们会发现 Channel 重写 ChannelOutboundInvoker 这两个接口的原因是:将返回值从 ChannelOutboundInvoker 修改成 Channel 。
- 我们看到除了
#read()
和#flush()
方法,其它方法的返回值的类型都是 ChannelFuture ,这表明这些操作是异步 IO 的过程。
2.4 异步结果 Future
/** |
除了自定义的
#closeFuture()
方法,也从 ChannelOutboundInvoker 接口继承了几个接口方法:ChannelPromise newPromise();
ChannelProgressivePromise newProgressivePromise();
ChannelFuture newSucceededFuture();
ChannelFuture newFailedFuture(Throwable cause);
ChannelPromise voidPromise();- 通过这些接口方法,可创建或获得和该 Channel 相关的 Future / Promise 对象。
2.5 类图
Channel 的子接口和实现类如下图:
- 本图包含了 NIO、OIO、Local、Embedded 四种 Channel 实现类。说明如下:
Channel 四种 Channel 实现类的说明
- 本系列仅分享 NIO 部分,所以裁剪类图如下:
NIO Channel 类图
3. Unsafe
Unsafe 接口,定义在在 io.netty.channel.Channel
内部,和 Channel 的操作紧密结合,下文我们将看到。
Unsafe 直译中文为“不安全”,就是告诉我们,无需且不必要在我们使用 Netty 的代码中,不能直接调用 Unsafe 相关的方法。Netty 注释说明如下:
/** |
😈 当然,对于我们想要了解 Netty 内部实现的胖友,那必须开扒它的代码实现落。因为它和 Channel 密切相关,所以我们也对它的接口做下分类。
3.1 基础查询
/** |
3.2 状态查询
无 😈
3.3 IO 操作
void register(EventLoop eventLoop, ChannelPromise promise); |
3.4 异步结果 Future
/** |
3.5 类图
Unsafe 的子接口和实现类如下图:
- 已经经过裁剪,仅保留 NIO Channel 相关的 Unsafe 的子接口和实现类部分。
- 我们会发现,对于 Channel 和 Unsafe 来说,类名中包含 Byte 是属于客户端的,Message 是属于服务端的。
4. ChanelId
io.netty.channel.ChannelId
实现 Serializable、Comparable 接口,Channel 编号接口。代码如下:
public interface ChannelId extends Serializable, Comparable<ChannelId> { |
#asShortText()
方法,返回的编号,短,但是全局非唯一。#asLongText()
方法,返回的编号,长,但是全局唯一。
ChanelId 的默认实现类为 io.netty.channel.DefaultChannelId
,我们主要看看它是如何生成 Channel 的两种编号的。代码如下:
|
- 对于
#asShortText()
方法,仅使用最后 4 字节的随机数字,并转换成 16 进制的数字字符串。也因此,短,但是全局非唯一。 对于
#asLongText()
方法,通过调用#newLongValue()
方法生成。代码如下:private String newLongValue() {
StringBuilder buf = new StringBuilder(2 * data.length + 5); // + 5 的原因是有 5 个 '-'
int i = 0;
i = appendHexDumpField(buf, i, MACHINE_ID.length); // MAC 地址。
i = appendHexDumpField(buf, i, PROCESS_ID_LEN); // 进程 ID 。4 字节。
i = appendHexDumpField(buf, i, SEQUENCE_LEN); // 32 位数字,顺序增长。4 字节。
i = appendHexDumpField(buf, i, TIMESTAMP_LEN); // 时间戳。8 字节。
i = appendHexDumpField(buf, i, RANDOM_LEN); // 32 位数字,随机。4 字节。
assert i == data.length;
return buf.substring(0, buf.length() - 1);
}
private int appendHexDumpField(StringBuilder buf, int i, int length) {
buf.append(ByteBufUtil.hexDump(data, i, length));
buf.append('-');
i += length;
return i;
}- 具体的生成规则,见代码。最终也是 16 进制的数字。也因此,长,但是全局唯一。
5. ChannelConfig
io.netty.channel.ChannelConfig
,Channel 配置接口。代码如下:
Map<ChannelOption<?>, Object> getOptions(); |
调用
#setOption(ChannelOption<T> option, T value)
方法时,会调用相应的#setXXX(...)
方法。代码如下:// DefaultChannelConfig.java
"deprecation") (
public <T> boolean setOption(ChannelOption<T> option, T value) {
validate(option, value);
if (option == CONNECT_TIMEOUT_MILLIS) {
setConnectTimeoutMillis((Integer) value);
} else if (option == MAX_MESSAGES_PER_READ) {
setMaxMessagesPerRead((Integer) value);
} else if (option == WRITE_SPIN_COUNT) {
setWriteSpinCount((Integer) value);
} else if (option == ALLOCATOR) {
setAllocator((ByteBufAllocator) value);
} else if (option == RCVBUF_ALLOCATOR) {
setRecvByteBufAllocator((RecvByteBufAllocator) value);
} else if (option == AUTO_READ) {
setAutoRead((Boolean) value);
} else if (option == AUTO_CLOSE) {
setAutoClose((Boolean) value);
} else if (option == WRITE_BUFFER_HIGH_WATER_MARK) {
setWriteBufferHighWaterMark((Integer) value);
} else if (option == WRITE_BUFFER_LOW_WATER_MARK) {
setWriteBufferLowWaterMark((Integer) value);
} else if (option == WRITE_BUFFER_WATER_MARK) {
setWriteBufferWaterMark((WriteBufferWaterMark) value);
} else if (option == MESSAGE_SIZE_ESTIMATOR) {
setMessageSizeEstimator((MessageSizeEstimator) value);
} else if (option == SINGLE_EVENTEXECUTOR_PER_GROUP) {
setPinEventExecutorPerGroup((Boolean) value);
} else {
return false;
}
}ChannelConfig 的配置项
io.netty.channel.ChannelOption
很多,胖友可以看下 《Netty:option 和 childOption 参数设置说明》 ,了解感兴趣的配置项。
5.1 类图
ChannelConfig 的子接口和实现类如下图:
- 已经经过裁剪,仅保留 NIO Channel 相关的 ChannelConfig 的子接口和实现类部分。
666. 彩蛋
正如文头所说,在前面的文章中,我们已经不断看到 Netty Channel 的身影,例如:
- 在 《精尽 Netty 源码分析 —— 启动(一)之服务端》 中,我们看了服务端 NioServerSocketChannel bind 的过程。
- 在 《精尽 Netty 源码分析 —— 启动(二)之客户端》 中,我们看了客户端 NioSocketChannel connect 的过程。
在后续的文章中,我们会分享 Netty NIO Channel 的其他操作,😈 一篇一个操作。
推荐阅读文章:
- Hypercube 《自顶向下深入分析 Netty(六)–Channel总述》