Netty组件Future、Promise、Handler、Pipline、ByteBuf

Future&Promise

Netty中的Future与jdk中的Future同名,但是是两个接口,netty的Future继承自jdk的Future,而Promise又对netty Future进行了扩展

  • jdk Future只能同步等待任务结束(或成功、或失败)才能得到结果
  • netty Future可以同步等待任务结束得到结果,也可以异步方式得到结果,但是要等到任务结束
  • netty Promise不仅有netty Future的功能,而且脱离了任务独立存在,只作为两个线程间传递结果的容器
功能/名称 jdk Future netty Future Promise
cancel 取消任务 - -
isCanceled 任务是否取消 - -
isDone 任务是否完成,不能区分成功失败 - -
get 获取任务结果,阻塞等待 - -
getNow - 获取任务结果,非阻塞,还未产生结果时返回 null -
await - 等待任务结束,如果任务失败,不会抛异常,而是通过 isSuccess 判断 -
sync - 等待任务结束,如果任务失败,抛出异常 -
isSuccess - 判断任务是否成功 -
cause - 获取失败信息,非阻塞,如果没有失败,返回null -
addLinstener - 添加回调,异步接收结果 -
setSuccess - - 设置成功结果
setFailure - - 设置失败结果

JDK Future

@Slf4j
public class Fuature1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(5);
        Future<? extends Integer> future = pool.submit((Callable<? extends Integer>) () -> {
            log.info("waiting ...");
            TimeUnit.SECONDS.sleep(3);
            return 60;
        });
        log.info("---");
        Integer result = future.get();
        log.info("result:{}",result);
    }
}

Netty Future

@Slf4j
public class Future2 {
    public static void main(String[] args) {
        NioEventLoopGroup loopGroup = new NioEventLoopGroup();
        EventLoop eventLoop = loopGroup.next();
        Future<? extends Integer> future = eventLoop.submit((Callable<? extends Integer>) () -> {
            log.info("task runing ...");
            TimeUnit.SECONDS.sleep(3);
            return 80;
        });
        future.addListener(new FutureListener<Integer>(){
            @Override
            public void operationComplete(Future<Integer> integerFuture) throws Exception {
                Integer result = integerFuture.getNow();
                log.info("result:{}",result);
            }
        });
        log.info("waiting ...");
    }
}

Promise

Promise相当于一个容器,可以用于存放各个线程中的结果,然后让其他线程去获取该结果

@Slf4j
public class NettyPromise {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        NioEventLoopGroup group=new NioEventLoopGroup();
        Promise<Integer> promise=new DefaultPromise<>(group.next());
        new Thread(()->{
            try {
                log.info("calc ...");
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                promise.setFailure(e);
            }
            promise.setSuccess(100);
        }).start();
        log.info("result:{}",promise.get());
   }

Handler&Pipline

ChannelHandler用来处理Channel上的各种事件,分为入站、出站两种。所有ChannelHandler被连成一串,就是Pipline

  • 入站处理器通常是ChannelInboundHandlerAdapter的子类,主要用来读取客户端数据,写回结果
  • 出站处理器通常是ChannelOutboundHandlerAdapter的子类,主要对写回结果进行加工

打个比喻,每个Channel是一个产品的加工车间,Pipline是车间中的流水线,ChannelHandler就是流水线上的各道工序,而后面的ByteBuf就是原材料,经过很多工序的加工:先经过一道道入站工序,再经过一道道出站工序最终变为产品

package com.vmware.netty.utils.s5;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.StandardCharsets;

@Slf4j
public class PiplineServer {
    public static void main(String[] args) {
        new ServerBootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast("handler1",new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                log.info("1");
                                ctx.fireChannelRead(msg);
                            }
                        });
                        socketChannel.pipeline().addLast("handler2",new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                log.info("2");
                                ctx.fireChannelRead(msg);
                            }
                        });
                        socketChannel.pipeline().addLast("handler3",new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                log.info("3");
                                socketChannel.writeAndFlush(ctx.alloc().buffer().writeBytes("Server...".getBytes(StandardCharsets.UTF_8)));
                                super.channelRead(ctx,msg);
                            }
                        });
                        socketChannel.pipeline().addLast("handler4",new ChannelOutboundHandlerAdapter(){
                            @Override
                            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                log.info("4");
                                super.write(ctx, msg, promise);
                            }
                        });
                        socketChannel.pipeline().addLast("handler5",new ChannelOutboundHandlerAdapter(){
                            @Override
                            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                log.info("5");
                                super.write(ctx, msg, promise);
                            }
                        });
                        socketChannel.pipeline().addLast("handler6",new ChannelOutboundHandlerAdapter(){
                            @Override
                            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                log.info("6");
                                super.write(ctx, msg, promise);
                            }
                        });
                    }
                }).bind(8888);
    }
}

输出结果

2023-04-06 00:15:25.721 [nioEventLoopGroup-2-2] INFO  com.vmware.netty.utils.s5.PiplineServer - 1
2023-04-06 00:15:25.722 [nioEventLoopGroup-2-2] INFO  com.vmware.netty.utils.s5.PiplineServer - 2
2023-04-06 00:15:25.722 [nioEventLoopGroup-2-2] INFO  com.vmware.netty.utils.s5.PiplineServer - 3
2023-04-06 00:15:25.722 [nioEventLoopGroup-2-2] INFO  com.vmware.netty.utils.s5.PiplineServer - 6
2023-04-06 00:15:25.723 [nioEventLoopGroup-2-2] INFO  com.vmware.netty.utils.s5.PiplineServer - 5
2023-04-06 00:15:25.723 [nioEventLoopGroup-2-2] INFO  com.vmware.netty.utils.s5.PiplineServer - 4

可以看到,ChannelInboundHandlerAdapter是按照addLast的执行顺序执行的,而ChannelOutboundHandlerAdapter是按照addLast的逆序执行的。ChannelPipline的实现是一个ChannelHandlerContext(包装了ChannelHandler)组成的双向链表
在这里插入图片描述

在通过channel.pipline().addLast(name,handler)添加handler时,可以为handler取名字。这样可以调用pipline的addAfter、addBefore等方法更灵活的向pipline中添加handler

  • pipline是一个结构带有head与tail指针的双向链表,其中的节点为handler
    • 通过ctx.fireChannelRead(msg)等方法,将当前handler的处理结果传递给下一个handler
  • 当有入站(Inbound)操作时,会从head开始向后调用handler,直到handler不是处理Inbound操作为止
  • 当有出站(Outbound)操作时,会从tail开始向前调用handler,直到handler不是处理Outbound操作为止

Netty组件Future、Promise、Handler、Pipline、ByteBuf_第1张图片

OutboundHandler

  • socketChannel.writeAndFlush()

当handler中调用该方法进行写操作时,会触发Outbound操作,此时是从tail向前寻找OutboundHandler

  • ctx.writeAndFlush()

当handler中调用该方法进行写操作时,会触发Outbound操作,此时是从当前handler向前寻找OutboundHandler

修改服务器端代码

socketChannel.pipeline().addLast("handler4",new ChannelOutboundHandlerAdapter(){
   @Override
   public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        log.info("4");
        super.write(ctx, msg, promise);
    }
});
socketChannel.pipeline().addLast("handler3",new ChannelInboundHandlerAdapter(){
   @Override
   public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("3");
        ctx.writeAndFlush(ctx.alloc().buffer().writeBytes("Server...".getBytes(StandardCharsets.UTF_8)));
        ctx.fireChannelRead(msg);
   }
});

输出

2023-04-06 00:29:16.381 [nioEventLoopGroup-2-2] INFO  com.vmware.netty.utils.s5.PiplineServer2 - 1
2023-04-06 00:29:16.381 [nioEventLoopGroup-2-2] INFO  com.vmware.netty.utils.s5.PiplineServer2 - 2
2023-04-06 00:29:16.381 [nioEventLoopGroup-2-2] INFO  com.vmware.netty.utils.s5.PiplineServer2 - 3
2023-04-06 00:29:16.382 [nioEventLoopGroup-2-2] INFO  com.vmware.netty.utils.s5.PiplineServer2 - 4

EmbeddedChannel

EmbeddedChannel可以用于测试各种handler,通过其构造函数按顺序传入需要测试的handler,然后调用对应的Inbound和Outbound方法即可

@Slf4j
public class EmbededChannel {
    public static void main(String[] args) {
        ChannelInboundHandlerAdapter c1 = new ChannelInboundHandlerAdapter() {
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                log.info("1");
                ctx.fireChannelRead(msg);
            }
        };
        ChannelInboundHandlerAdapter c2 = new ChannelInboundHandlerAdapter() {
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                log.info("2");
                ctx.fireChannelRead(msg);
            }
        };
        ChannelInboundHandlerAdapter c3 = new ChannelInboundHandlerAdapter() {
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                log.info("3");
                ctx.fireChannelRead(msg);
            }
        };

        ChannelOutboundHandlerAdapter o1 = new ChannelOutboundHandlerAdapter(){
            @Override
            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                log.info("4");
                super.write(ctx,msg,promise);
            }
        };
        ChannelOutboundHandlerAdapter o2 = new ChannelOutboundHandlerAdapter(){
            @Override
            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                log.info("5");
                super.write(ctx,msg,promise);
            }
        };
        EmbeddedChannel channel = new EmbeddedChannel(c1, c2, c3, o1, o2);
        //执行Inbound
        channel.writeInbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("hello".getBytes(StandardCharsets.UTF_8)));
        //执行Outbound
        channel.writeOutbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("hello".getBytes(StandardCharsets.UTF_8)));
    }
}

ByteBuf

创建调试工具

public class BufUtil {
    public static void log(ByteBuf buffer) {
        int length = buffer.readableBytes();
        int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
        StringBuilder buf = new StringBuilder(rows * 80 * 2)
                .append("read index:").append(buffer.readerIndex())
                .append(" write index:").append(buffer.writerIndex())
                .append(" capacity:").append(buffer.capacity())
                .append(NEWLINE);
        appendPrettyHexDump(buf, buffer);
        System.out.println(buf.toString());
    }
}

该方法可以帮助我们更为详细地查看ByteBuf中的内容

创建ByteBuf

@Slf4j
public class ByteBufStudy {
    public static void main(String[] args) {
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(16);
        BufUtil.log(buffer);
        StringBuilder builder = new StringBuilder();
        for (int index = 0; index < 20; index++) {
            builder.append("a");
        }
        buffer.writeBytes(builder.toString().getBytes(StandardCharsets.UTF_8));
        BufUtil.log(buffer);
    }
}

执行结果

read index:0 write index:0 capacity:16

read index:0 write index:20 capacity:64
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 |aaaaaaaaaaaaaaaa|
|00000010| 61 61 61 61                                     |aaaa            |
+--------+-------------------------------------------------+----------------+

创建方式

ByteBuf可以通过ByteBufAllocator选择allocator并调用对应的buffer方法来创建,默认使用直接内存作为ByteBuf,容量为256个字节,可以指定初始容量大小

当ByteBuf的容量无法容纳所有数据时,ByteBuf会自动进行扩容操作

当在handler中创建ByteBuf,建议使用ChannelHandlerContext ctx.alloc().buffer()来创建

直接内存与堆内存

声明直接内存类型的ByteBuf

//方式1
ByteBufAllocator.DEFAULT.buffer();
//方式2
ByteBufAllocator.DEFAULT.directBuffer();

声明堆内存类型的ByteBuf

ByteBufAllocator.DEFAULT.heapBuffer(16);
  • 直接内存创建和销毁的代价昂贵,但读写性能高(少一次内存复制),适合配合池化功能一起用
  • 直接内存对 GC 压力小,因为这部分内存不受 JVM 垃圾回收的管理,但也要注意及时主动释放

示例

@Slf4j
public class ByteBufStudy2 {
    public static void main(String[] args) {
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
        log.info("type:{}",buffer.getClass());
        ByteBuf buffer1 = ByteBufAllocator.DEFAULT.heapBuffer();
        log.info("type:{}",buffer1.getClass());
        ByteBuf buffer2 = ByteBufAllocator.DEFAULT.directBuffer();
        log.info("type:{}",buffer2.getClass());
    }
}

输出

2023-04-06 01:35:13.138 [main] INFO  com.vmware.netty.utils.buffer.ByteBufStudy2 - type:class io.netty.buffer.PooledUnsafeDirectByteBuf
2023-04-06 01:35:13.145 [main] INFO  com.vmware.netty.utils.buffer.ByteBufStudy2 - type:class io.netty.buffer.PooledUnsafeHeapByteBuf
2023-04-06 01:35:13.145 [main] INFO  com.vmware.netty.utils.buffer.ByteBufStudy2 - type:class io.netty.buffer.PooledUnsafeDirectByteBuf

池化与非池化

池化的最大意义在于可以重用 ByteBuf,优点有

  • 没有池化,则每次都得创建新的 ByteBuf 实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加 GC 压力
  • 有了池化,则可以重用池中 ByteBuf 实例,并且采用了与 jemalloc 类似的内存分配算法提升分配效率
  • 高并发时,池化功能更节约内存,减少内存溢出的可能

池化功能是否开启,可以通过下面的系统环境变量来设置

-Dio.netty.allocator.type={unpooled|pooled}Copy
  • 4.1 以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现
  • 4.1 之前,池化功能还不成熟,默认是非池化实现

组成

ByteBuf主要由一下几个组成部分

最大容量与当前容量

  • 在构造ByteBuf时,可以传入两个参数,分别代表初始容量和最大容量,若未传入第二个参数(最大容量),最大容量默认为Integer.MAX_VALUE
  • 当ByteBuf容量无法容纳所有数据时,会进行扩容操作,若超出最大容量,会抛出IndexOutOfBoundsException

读写操作不同于ByteBuffer只用position进行控制,ByteBuf分别由读指针和写指针两个指针控制。进行读写操作时,无需进行模式的切换

  • 读指针前的部分被称为废弃部分,是已经读过的内容
  • 读指针与写指针之间的空间称为可读部分
  • 写指针与当前容量之间的空间称为可写部分

Netty组件Future、Promise、Handler、Pipline、ByteBuf_第2张图片

最开始读写指针都在 0 位置

你可能感兴趣的:(Netty,java,网络,netty)