Netty

Posted on Mon, Oct 17, 2022 learning

netty是由三个核心组件构成:缓冲区、通道和事件模型

阻塞和非阻塞:

阻塞:调用后就要等到IO结束才返回。

非阻塞:IO没有结束就直接返回,IO结束再通知。

Netty架构图:

Netty的基础是ByteBuf类,其他的Buffer类基本都是该类的衍生类。

Netty有一些内置的复合缓冲区类型,可以实现透明的零拷贝(一种 I/O 操作优化技术,可以快速高效地将数据从文件系统移动到网络接口,而不需要将其从内核空间复制到用户空间)。

我们知道数据在使用底层协议进行传输的过程中是会被封装成为一个个的包进行传输。当传输的数据过大,一个包放不下的时候,还需要对数据进行拆分,目的方在接收到数据之后,需要对收到的数据进行组装,一般情况下这个组装的操作是对数据的拷贝,将拆分过后的对象拷贝到一个长的数据空间中。

Netty中有很多这种零拷贝工具类方法:

//封装Byte数组
public static ByteBuf wrappedBuffer(byte[]... arrays) {
        return wrappedBuffer(arrays.length, arrays);
    }
//封装ByteBuf
public static ByteBuf wrappedBuffer(ByteBuf... buffers) {
        return wrappedBuffer(buffers.length, buffers);
    }
//封装ByteBuffer
public static ByteBuf wrappedBuffer(ByteBuffer... buffers) {
        return wrappedBuffer(buffers.length, buffers);
    }

Netty组件:

Netty提供了ChannelInboundHandlerAdapter类,来处理服务端的各种请求。

public interface ChannelInboundHandler extends ChannelHandler {

    /**
     当通道注册完成后,Netty会调用fireChannelRegistered()方法,触发通道注册事件,
     而在通道流水线注册过的入站处理器的channelRegistered()回调方法会被调用。
     */
    void channelRegistered(ChannelHandlerContext ctx) throws Exception;

    /**
     * 当通道激活完成后,Netty会调用fireChannelActive()方法,触发通道激活事件,
     而在通道流水线注册过的入站处理器的channelActive()回调方法会被调用。
     */
    void channelUnregistered(ChannelHandlerContext ctx) throws Exception;

     /**
     * 当通道激活完成后,Netty会调用fireChannelActive()方法,触发通道激活事件,
     而在通道流水线注册过的入站处理器的channelActive()回调方法会被调用。
     */
    void channelActive(ChannelHandlerContext ctx) throws Exception;

    /**
     * 当连接被断开或者不可用时,Netty会调用fireChannelInactive()方法
     ,触发连接不可用事件,
     而在通道流水线注册过的入站处理器的channelInactive()回调方法会被调用。
     */
    void channelInactive(ChannelHandlerContext ctx) throws Exception;

    /**
     当通道缓冲区可读时,Netty会调用fireChannelRead()方法,触发通道可读事件,
     而在通道流水线注册过的入站处理器的channelRead()回调方法会被调用,
     以便完成入站数据的读取和处理。
     */
    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;

    /**
     * 当通道缓冲区读完时,Netty会调用fireChannelReadComplete()方法,
     触发通道缓冲区读完事件,
     而在通道流水线注册过的入站处理器的channelReadComplete()回调方法会被调用。
     */
    void channelReadComplete(ChannelHandlerContext ctx) throws Exception;

    /**
     * 如果触发了用户事件,则被调用。
     */
    void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;

    /**
     * 一旦Channel的可写状态改变,就会被调用。 
     可以使用Channel.isWritable()检查状态。
     */
    void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;

    /**
     * 异常回调
     */
    @Override
    @SuppressWarnings("deprecation")
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}

在netty中,入站处理器的默认实现ChannelInboundHandlerAdapter,可以继承该类,按需实现对应方法。

出站Handler:

public interface ChannelOutboundHandler extends ChannelHandler {
    /**
    监听地址(IP+端口)绑定:完成底层Java IO通道的IP地址绑定。
    如果使用TCP传输协议,这个方法用于服务端。
     */
    void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;

    /**
      连接服务端:完成底层Java IO通道的服务端的连接操作。
      如果使用TCP传输协议,那么这个方法将用于客户端。
     */
    void connect(
            ChannelHandlerContext ctx, SocketAddress remoteAddress,
            SocketAddress localAddress, ChannelPromise promise) throws Exception;

    /**
     * 断开服务器连接:断开底层Java IO通道的socket连接。
     如果使用TCP传输协议,此方法主要用于客户端。
     *
     */
    void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    /**
     * 主动关闭通道:关闭底层的通道,例如服务端的新连接监听通道。
     */
    void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    /**
   从当前注册的EventLoop进行取消注册操作后调用。
     */
    void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    /**
     * 从底层读数据:完成Netty通道从Java IO通道的数据读取。
     */
    void read(ChannelHandlerContext ctx) throws Exception;

    /**
    * 写数据到底层:完成Netty通道向底层Java IO通道的数据写入操作。此方法仅仅是触发一下操作,并不是完成实际的数据写入操作。
     */
    void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;

    /**
     * 将底层缓存区的数据腾空,立即写出到对端。
     */
    void flush(ChannelHandlerContext ctx) throws Exception;
}

同理,使用时继承该类,并重写自己需要的方法。

ChannelInitialiazer通道初始化Handler:

一条Netty通道拥有一条Handler业务处理器流水线,负责装配自己的Handler业务处理器。装配Handler的工作发生在通道开始工作之前。

图中,ChannelInitializer,属于入站处理器的类型,继承了ChannelInboundHandlerAdapter。其中,InitChannel()为ChannelInitializer定义的抽象方法,需要开发人员自己实现.

/**
一旦Channel被注册,这个方法就会被调用。 
方法返回后,此实例将从Channel的ChannelPipeline中删除。
**/
protected abstract void initChannel(C ch) throws Exception;

在通道初始化时,会调用提前注册的初始化处理器的initChannel()方法。比如,在父通道接收到新连接并且要初始化其子通道时,会调用初始化器的initChannel()方法,并且会将新接收的通道作为参数,传递给此方法(父通道初始化子通道)。

一般来说,initChannel()方法的大致业务代码是:拿到新连接通道作为实际参数,往它的流水线中装配Handler业务处理器。

暂时看晕了,一会儿再来看。