Netty Channel组件
什么是Channel?
Channel是Netty的核心概念之一,它代表客户端和服务端建立的一个连接通道,Netty抽象出来的网络I/O读写相关的接口,即可以用于写入数据到对端,也可以从对端读取数据;客户端有一个Channel(SocketChannel),服务端也有一个Channel(NioSocketChannel),当客户端和服务端建立连接后,客户端的Channel会跟服务端的Channel进行联通;
抽象类AbstractChannel的构造方法
Netty通道的抽象类AbstractChannel的构造方法如下:
AbstractChannel#AbstractChannel(io.netty.channel.Channel)
查看代码
protected AbstractChannel(Channel parent) {
// 父通道
this.parent = parent;
id = newId();
// 新建一个底层的NIO通道,完成实际的I/O操作
unsafe = newUnsafe();
// 新建一个通道流水线
pipeline = newChannelPipeline();
}
AbstractChannel内部有一个pipeline属性,表示管理handler的流水线;Netty在对通道进行初始化的时候,将pipeline属性初始化为DefaultChannelPipeline的实例,如下;
AbstractChannel#newChannelPipeline
从上面可以看出AbstractChannel内部维护了一个父通道parent属性,每个通道都有一条ChannelPipeline;
接受客户端连接的通道NioServerSocketChannel
对于接受客户端连接的通道NioServerSocketChannel来说,其Channel中的parent属性值为null;
NioServerSocketChannel#NioServerSocketChannel(java.nio.channels.ServerSocketChannel)
传输通道NioSocketChannel
而对于传输通道NioSocketChannel来说,其Channel中的parent属性值为接受该客户端连接的通道;
具体的调用如下:
当NioEventLoop有可接受连接的事件发生时,则执行如下逻辑;
NioServerSocketChannel#doReadMessages
几乎所有的Netty通道实现类都继承了AbstractChannel抽象类,它们都拥有的parent和pipeline两个属性成员;
AbstractChannel中的主要方法
-
AbstractChannel#connect(java.net.SocketAddress)
此方法的作用为:连接远程服务器;方法的参数为远程服务器的地址,调用后会立即返回,其返回值为执行连接操作的异步任务ChannelFuture;此方法在客户端的传输通道使用;
在JDK中预置了java.util.concurrent.Future,但是其所提供的实现,只允许手动检查对应的操作是否已经完成,或者一直阻塞直到它完成,这是非常繁琐的,所以Netty提供了它自己的实现ChannelFuture,用于在执行异步操作的时候使用;
-
AbstractChannel#bind(java.net.SocketAddress)
此方法的作用为:绑定监听地址,开始监听新的客户端连接;此方法在服务器的新连接监听和接收通道使用;
-
AbstractChannel#close()
此方法的作用为:关闭通道连接,返回连接关闭的ChannelFuture异步任务;如果需要在连接正式关闭后 执行其他操作,则需要为异步任务设置回调方法,或者调用ChannelFuture异步任务的sync方法来阻塞当前线程,一直等到通道关闭的异步任务执行完毕;
-
AbstractChannel#read
此方法的作用为:读取通道数据,并且开启入站处理;即从内部的Java NIO Channel通道读取数据,然后启动内部的Pipeline流水线,开启数据读取的入站处理;此方法的返回通道自身用于链式调用;
-
AbstractChannel#write(java.lang.Object)
此方法的作用为:开启出站流水处理,把处理后的最终数据写到底层通道(如Java NIO通道);此方法的返回值为出站处理的异步处理任务;
-
AbstractChannel#flush
此方法的作用为:将缓冲区中的数据立即写出到对端;调用AbstractChannel#write(java.lang.Object)方法进行出站处理时,并不能直接将数据写到对端, write操作的在大部分情况下仅仅是写入到操作系统的缓冲区,操作系统会将根据缓冲区的情况,决定什么时候把数据写到对端,而执行flush方法立即将缓冲区的数据写到对端;
EmbeddedChannel嵌入式通道
Netty中提供了一个仅仅是模拟入站和出站操作的专用通道EmbeddedChannel,底层不进行实际的传输,不需要启动Netty服务器和客户端;服务器和客户端。除了不进行传输之外,EmbeddedChannel的其他的事件机制和处理流程和真正的传输通道是一模一样的;因此,使用EmbeddedChannel,开发人员可以在单元测试用例中方便、快速地进行ChannelHandler业务处理器的单元测试;
为了模拟数据的发送和接收,EmbeddedChannel提供了一组专门用于测试的方法,如下:
名称 | 说明 |
writeInbound |
使用场景是:用于测试入站处理器;在测试入站处理器时(如测试一个解码器),需要读取入站(Inbound)数据;可以调用 writeInbound方法,向 EmbeddedChannel写入一个入站数据(如二进制 ByteBuf数据包),模拟底层的入站包,从而被入站处理器处理到,达到测试的目的;
向通道写入入站数据,模拟真实通道收到数据的场景;即这些写入的数据会被流水线上的入站处理器所处理到; |
readInbound | 从EmbeddedChannel中读取入站数据,返回经过流水线最后一个入站处理器处理完成之后的入站数据;如果没有数据,则返回 null; |
writeOutbound |
使用场景是:用于测试出站处理器;在测试出站处理器时(如测试一个编码器),需要有出站的(Outbound)数据进入到流水线,则可以调用writeOutbound方法,向模拟通道写入一个出站数据(如二进制 ByteBuf数据包),该包将进入处理器流水线,被待测试的出站处理器所处理;
向通道写入出站数据,模拟真实通道发送数据,即这些写入的数据会被流水线上的出站处理器处理;
|
readOutbound | 从EmbeddedChannel中读取出站数据,返回经过流水线最后一个出站处理器处理之后的出站数据;如果没有数据,则返回null; |
finish | 结束EmbeddedChannel,它会调用通道的 close方法; |
什么是ChannelHandler?
ChannelHandler是负责Channel的逻辑处理;
什么是ChannelPipeline?
ChannelPipeline负责管理ChannelHandler的容器,它的存储结构为双向链表;
一个Channel包含一个ChannelPipeline,所有ChannelHandler都会顺序加入到ChannelPipeline中,创建Channel时会自动创建一个ChannelPipeline;每个Channel都有一个管理它的ChannelPipeline,这个关联关系是永久性的;
抽象类AbstractChannel
在AbstractChannel中维护了一个DefaultChannelPipeline类型的成员属性;
ChannelPipeline 接口都有往ChannelPipline添加ChannelHandler的addXXX方法;
DefaultChannelPipeline维护了一个ChannelHandlerContext组成的双向链表
而DefaultChannelPipeline中又维护了一个由ChannelHandlerContext组成的双向链表,并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler;
head为ChannelHandlerContext组成的双向链表的前驱指针,tail为ChannelHandlerContext组成的双向链表的后继指针;
在DefaultChannelPipeline的有参构造方法中有对head,tail属性的赋值;
DefaultChannelPipeline#DefaultChannelPipeline
AbstractChannelHandlerContext为抽象类,最终它的实现类为DefaultChannelHandlerContext;
DefaultChannelHandlerContext
在DefaultChannelHandlerContext中维护了一个ChannelHandler的成员属性;
Channel,ChannelHandler,ChannelPipline的关系图
Channel通道拥有一条ChannelPipeline通道流水线,并且ChannelPipeline中Channel属性与其绑定,每一个流水线节点为一个 ChannelHandlerContext上下文对象,每一个上下文中包裹了一个ChannelHandler通道处理器,但通道流水线在没有加入任何通道处理器之前,装配了两个默认的处理器上下文:一个是位于头部的HeadContext,另一个是位于尾部的TailContext;在ChannelHandler通道处理器的入站/出站处理方法中,Netty都会传递一个Context上下文实例作为实际参数;
Channel生命周期
Channel生命周期如下(此生命周期即每次进行网络连接都要经过的流程):
状态 | 描述 |
ChannelUnregistered | Channel已经被创建,但还未注册到EventLoop; |
ChannelRegistered | Channel已经被注册到EventLoop; |
ChannelActive | Channel处于活动状态(已经连接到它的远程节点),它现在可以接收和发送数据; |
ChannelInactive | Channel没有连接到远程节点; |
当这些状态发生改变时,将会生成对应的事件;这些事件将会被转发给 ChannelPipeline 中的 ChannelHandler,其可以随后对它们做出响应;
测试Demo如下:
查看代码
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
private final static Logger logger = LoggerFactory.getLogger(EchoServerHandler. class );
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf data = (ByteBuf) msg;
logger.info( "服务端收到数据: " + data.toString(CharsetUtil.UTF_8));
ctx.writeAndFlush(data);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
logger.info( "EchoServerHandle channelReadComplete..." );
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
/**
* Channel已创建,但未注册到EventLoop
* @param ctx
* @throws Exception
*/
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
logger.info( "EchoServerHandle channelRegistered..." );
}
/**
* Channel注册到EventLoop
* @param ctx
* @throws Exception
*/
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
logger.info( "EchoServerHandle channelUnregistered..." );
}
/**
* 当客户端连接服务器完成就会触发该方法
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.info( "EchoServerHandle channelActive..." );
}
/**
* 客户端下线
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.info( "EchoServerHandle channelInactive..." );
}
}
执行结果如下: