原文链接:https://wangwei.one/posts/netty-channel-source-analyse.html
前面,我们大致了解了Netty中的几个核心组件。今天我们就来先来介绍Netty的网络通信组件,用于执行网络I/O操作 —— Channel。
Netty版本:4.1.30
概述
数据在网络中总是以字节的形式进行流通。我们在进行网络编程时选用何种传输方式编码(OIO、NIO等)决定了这些字节的传输方式。
在没有Netty之前,为了提升系统的并发能力,从OIO切换到NIO时,需要对代码进行大量的重构,因为相应的Java NIO 与 IO API大不相同。而Netty在这些Java原生API的基础上做了一层封装,对用户提供了高度抽象而又统一的API,从而让传输方式的切换不在变得困难,只需要直接使用即可,而不需要对整个代码进行重构。
Netty Channel UML
netty channel族如下:
整个族群中,AbstractChannel 是最为关键的一个抽象类,从它继承出了AbstractNioChannel、AbstractOioChannel、AbstractEpollChannel、LocalChannel、EmbeddedChannel等类,每个类代表了不同的协议以及相应的IO模型。除了 TCP 协议以外,Netty 还支持很多其他的连接协议,并且每种协议还有 NIO(异步 IO) 和 OIO(Old-IO,即传统的阻塞 IO) 版本的区别. 不同协议不同的阻塞类型的连接都有不同的 Channel 类型与之对应。下面是一些常用的 Channel 类型:
- NioSocketChannel:代表异步的客户端 TCP Socket 连接
- NioServerSocketChannel:异步的服务器端 TCP Socket 连接
- NioDatagramChannel:异步的 UDP 连接
- NioSctpChannel:异步的客户端 Sctp 连接
- NioSctpServerChannel:异步的 Sctp 服务器端连接
- OioSocketChannel:同步的客户端 TCP Socket 连接
- OioServerSocketChannel:同步的服务器端 TCP Socket 连接
- OioDatagramChannel:同步的 UDP 连接
- OioSctpChannel:同步的 Sctp 服务器端连接
- OioSctpServerChannel:同步的客户端 TCP Socket 连接
Channel API
我们先来看下最顶层接口 channel 主要的API,常用的如下:
接口名 | 描述 |
---|---|
eventLoop() | Channel需要注册到EventLoop的多路复用器上,用于处理I/O事件,通过eventLoop()方法可以获取到Channel注册的EventLoop。EventLoop本质上就是处理网络读写事件的Reactor线程。在Netty中,它不仅仅用来处理网络事件,也可以用来执行定时任务和用户自定义NioTask等任务。 |
pipeline() | 返回channel分配的ChannelPipeline |
isActive() | 判断channel是否激活。激活的意义取决于底层的传输类型。例如,一个Socket传输一旦连接到了远程节点便是活动的,而一个Datagram传输一旦被打开便是活动的 |
localAddress() | 返回本地的socket地址 |
remoteAddress() | 返回远程的socket地址 |
flush() | 将之前已写的数据冲刷到底层Channel上去 |
write(Object msg) | 请求将当前的msg通过ChannelPipeline写入到目标Channel中。注意,write操作只是将消息存入到消息发送环形数组中,并没有真正被发送,只有调用flush操作才会被写入到Channel中,发送给对方。 |
writeAndFlush() | 等同于调用write()并接着调用flush() |
metadate() | 熟悉TCP协议的读者可能知道,当创建Socket的时候需要指定TCP参数,例如接收和发送的TCP缓冲区大小,TCP的超时时间。是否重用地址等。在Netty中,每个Channel对应一个物理链接,每个连接都有自己的TCP参数配置。所以,Channel会聚合一个ChannelMetadata用来对TCP参数提供元数据描述信息,通过metadata()方法就可以获取当前Channel的TCP参数配置。 |
read() | 从当前的Channel中读取数据到第一个inbound缓冲区中,如果数据被成功读取,触发ChannelHandler.channelRead(ChannelHandlerContext,Object)事件。读取操作API调用完成后,紧接着会触发ChannelHander.channelReadComplete(ChannelHandlerContext)事件,这样业务的ChannelHandler可以决定是否需要继续读取数据。如果已经有操作请求被挂起,则后续的读操作会被忽略。 |
close(ChannelPromise promise) | 主动关闭当前连接,通过ChannelPromise设置操作结果并进行结果通知,无论操作是否成功,都可以通过ChannelPromise获取操作结果。该操作会级联触发ChannelPipeline中所有ChannelHandler的ChannelHandler.close(ChannelHandlerContext,ChannelPromise)事件。 |
parent() | 对于服务端Channel而言,它的父Channel为空;对于客户端Channel,它的父Channel就是创建它的ServerSocketChannel。 |
id() | 返回ChannelId对象,ChannelId是Channel的唯一标识。 |
Channel创建
对Netty Channel API以及相关的类有了一个初步了解之后,接下来我们来详细了解一下在Netty的启动过程中Channel是如何创建的。服务端Channel的创建过程,主要分为四个步骤:1)Channel创建;2)Channel初始化;3)Channel注册;4)Channel绑定。
我们以下面的代码为例进行解析:
// 创建两个线程组,专门用于网络事件的处理,Reactor线程组
// 用来接收客户端的连接,
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 用来进行SocketChannel的网络读写
EventLoopGroup workGroup = new NioEventLoopGroup();
// 创建辅助启动类ServerBootstrap,并设置相关配置:
ServerBootstrap b = new ServerBootstrap();
// 设置处理Accept事件和读写操作的事件循环组
b.group(bossGroup, workGroup)
// 配置Channel类型
.channel(NioServerSocketChannel.class)
// 配置监听地址
.localAddress(new InetSocketAddress(port))
// 设置服务器通道的选项,设置TCP属性
.option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
// 设置建立连接后的客户端通道的选项
.childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
// channel属性,便于保存用户自定义数据
.attr(AttributeKey.newInstance("UserId"), "60293")
.handler(new LoggingHandler(LogLevel.INFO))
// 设置子处理器,主要是用户的自定义处理器,用于处理IO网络事件
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(serverHandler);
}
});
// 调用bind()方法绑定端口,sync()会阻塞等待处理请求。这是因为bind()方法是一个异步过程,会立即返回一个ChannelFuture对象,调用sync()会等待执行完成
ChannelFuture f = b.bind().sync();
// 获得Channel的closeFuture阻塞等待关闭,服务器Channel关闭时closeFuture会完成
f.channel().closeFuture().sync();
调用channel()接口设置 AbstractBootstrap 的成员变量 channelFactory,该变量顾名思义就是用于创建channel的工厂类。源码如下:
...
public B channel(Class<? extends C> channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
}
// 创建 channelFactory
return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
...
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
if (channelFactory == null) {
throw new NullPointerException("channelFactory");
}
if (this.channelFactory != null) {
throw new IllegalStateException("channelFactory set already");
}
this.channelFactory = channelFactory;
return (B) this;
}
...
channelFactory 设置为 ReflectiveChannelFactory ,在我们这个例子中 clazz 为 NioServerSocketChannel ,我们可以看到其中有个 newChannel() 接口,通过反射的方式来调用,这个接口的调用处我们后面会介绍到。源码如下:
// Channel工厂类
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Class<? extends T> clazz;
public ReflectiveChannelFactory(Class<? extends T> clazz) {
if (clazz == null) {
throw new NullPointerException("clazz");
}
this.clazz = clazz;
}
@Override
public T newChannel() {
try {
// 通过反射来进行常见Channel实例
return clazz.newInstance();
} catch (Throwable t) {
thro