Loading

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..." );
    }
}

 

执行结果如下:  

posted @ 2021-02-12 23:44  街头卖艺的肖邦  阅读(510)  评论(0编辑  收藏  举报