三、ByteBuf组件

1、结构

ByteBuf是一个字节容器,内部是一个字节数组。
结构图:
在这里插入图片描述

2、重要属性

1、readerIndex  读指针
2、writerIndex	写指针
3、maxCapacity	最大容量

readerIndex:指示可读取的起始位置,每读取一个字节,readerIndex加1,当readerIndex等于writerIndex表示不可读了。
writerIndex:表示可写的起始位置,每写一个字节,writerIndex加1,当writerIndex等于capacity(),则表示不可写了。
maxCapacity:可扩容的最大容量,向ByteBuf写数据时,如果容量不足,则进行扩容,最大扩容值是maxCapacity。

简单操作下ByteBuf

public class WriteReadTest {

    public static void main(String[] args) {
        //初始容量是9 最大容量是100
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(9,100);

        //写入4个字节
        buffer.writeBytes(new byte[]{1,2,3,4});

        //读ByteBuf,readInx不变
        getByteBuf(buffer);

        System.out.println("==============");

        //取ByteBuf,readInx改变
        readByteBuf(buffer);

        System.out.println("==============");

        getByteBuf(buffer);


    }

    /**
     *  get不会影响指针属性
     * @param buffer
     */
    private static void getByteBuf(ByteBuf buffer) {

        for(int i = 0 ; i < buffer.readableBytes(); i++){

            System.out.println("读字节:"+ buffer.getByte(i));
        }
    }
    private static void readByteBuf(ByteBuf buffer) {

        while (buffer.isReadable()){

            System.out.println("取字节:"+ buffer.readByte());

        }
    }

}

运行结果:
在这里插入图片描述

说明getByte方法,不会影响readerIndex下标,buffer.readByte()会影响下标。

3、引用计数

采用引用计数来追踪ByteBuf 的生命周期。
1、对Pooled ByteBuf的支持(池化技术)–创建一个Bufffer对象缓冲池,将没有被引用的buffer放到对象缓冲池中。减少频繁的创建和销毁
2、尽快发现回收ByteBuf

引用计数:
每次调用 retain()方法,计数加1
每次调用 release()方法,计数减 1

写个例子看下:

public static void main(String[] args) {

	//创建的时候,引用数量是1
	ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();

	byteBuf.retain();

	System.out.println("引用数量:"+ byteBuf.refCnt());

	byteBuf.release();

	System.out.println("引用数量:"+ byteBuf.refCnt());

	byteBuf.release();

	System.out.println("引用数量:"+ byteBuf.refCnt());

	//报错:引用数量是0,byteBuf已经释放了
	byteBuf.retain();

}

执行结果:
在这里插入图片描述

说明,引用数量是0,byteBuf已经释放了,不能再使用了。最好retain() 和release()成对出现。

引用计数为0,内存释放分为两种情况:
1、Pooled池化的直接放如可重新分配的池中。
2、Unpooled未池化的,如果是堆内存,则jvm内存回收,如果是直接内存,则调用本地方法,释放外部内存。

4、分配器和分配缓冲区

两种:
PooledByteBufAllocator (池化) 和 UnpooledByteBufAllocator(非池化) 分配器

参数指定: io.netty.allocator.type=pooled/unpooled

测试程序:

public class AoolcatorTest {

    public static void main(String[] args) {

        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(9, 100);

        System.out.println(buffer.maxCapacity() +"-----------"+ buffer.capacity());

        ByteBuf buffer1 = ByteBufAllocator.DEFAULT.buffer();

        System.out.println(buffer1.maxCapacity() +"-----------"+ buffer1.capacity());

        //非池化的直接内存
        ByteBuf byteBuf =  UnpooledByteBufAllocator.DEFAULT.directBuffer();

        System.out.println(byteBuf.maxCapacity() +"-----------"+ byteBuf.capacity());

        //池化的堆内存
        ByteBuf byteBuf1 = PooledByteBufAllocator.DEFAULT.heapBuffer();

        System.out.println(byteBuf1.maxCapacity() +"-----------"+ byteBuf1.capacity());

        //非池化的堆内存
        ByteBuf byteBuf2 =  UnpooledByteBufAllocator.DEFAULT.heapBuffer();

        System.out.println(byteBuf2.maxCapacity() +"-----------"+ byteBuf2.capacity());

        //池化的直接内存
        ByteBuf byteBuf3 = PooledByteBufAllocator.DEFAULT.directBuffer();

        System.out.println(byteBuf3.maxCapacity() +"-----------"+ byteBuf3.capacity());


    }
}

堆缓冲区,直接缓冲区和组合缓冲区:

直接缓冲区:由操作系统的malloc()函数分配内存,本地内存堆native堆管理。一般在池化分配器中分配和回收。
测试:

@Test
public  void testDirectBuffer(){

	//直接内存
	ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer();

	byteBuf.writeBytes("Hello World".getBytes(CharsetUtil.UTF_8));
	
	//直接内存 hasArray() 没值,但是没值,不一定是直接内存,可能还是CompositeByteBuf
	if(!byteBuf.hasArray()){

		int length = byteBuf.readableBytes();

		byte[] bytes = new byte[length];

		byteBuf.getBytes(byteBuf.readerIndex(),bytes);

		System.out.println(new String(bytes,CharsetUtil.UTF_8));

	}
	byteBuf.release();

}

@Test
public  void testHeapBuffer(){

	//堆内存
	ByteBuf byteBuf = ByteBufAllocator.DEFAULT.heapBuffer();

	byteBuf.writeBytes("Hello World".getBytes(CharsetUtil.UTF_8));

	//堆内存 hasArray() 有值
	if(byteBuf.hasArray()){

		byte[] array = byteBuf.array();

		int length = byteBuf.readableBytes();

		int offset = byteBuf.arrayOffset() + byteBuf.readerIndex();

		System.out.println(new String(array,offset,length,CharsetUtil.UTF_8));

	}
	byteBuf.release();

}

CompositeByteBuf 组合测试程序:

@Test
public void test(){

	CompositeByteBuf cbuf = ByteBufAllocator.DEFAULT.compositeBuffer();

	ByteBuf headerBuf = Unpooled.copiedBuffer("Hello", CharsetUtil.UTF_8);

	ByteBuf bodyBuf = Unpooled.copiedBuffer("World", CharsetUtil.UTF_8);

	cbuf.addComponents(headerBuf,bodyBuf);

	sendMessage(cbuf);

	headerBuf.retain();

	cbuf.release();

	cbuf = ByteBufAllocator.DEFAULT.compositeBuffer();

	bodyBuf = Unpooled.copiedBuffer("Java",CharsetUtil.UTF_8);

	cbuf.addComponents(headerBuf,bodyBuf);

	sendMessage(cbuf);

	cbuf.release();

}

private void sendMessage(CompositeByteBuf cbuf) {

	for (ByteBuf buf :cbuf){

		int len = buf.readableBytes();

		byte [] array = new byte[len];

		buf.getBytes(buf.readerIndex(), array);

		System.out.print(new String(array,CharsetUtil.UTF_8));

		System.out.println("---");
	}
}

@Test
public void test2(){

	CompositeByteBuf cbuf = Unpooled.compositeBuffer(3);

	cbuf.addComponent(Unpooled.wrappedBuffer(new byte[]{1,2,3}));

	cbuf.addComponent(Unpooled.wrappedBuffer(new byte[]{4}));

	cbuf.addComponent(Unpooled.wrappedBuffer(new byte[]{5,6}));
	
	//合并成新的Nio的ByteBuffer
	ByteBuffer nioBuffer = cbuf.nioBuffer(0,6);

	byte[] array = nioBuffer.array();

	for (byte b : array) {

		System.out.println(b);

	}
	cbuf.release();
}

5、ByteBuf的浅层复制

分为切片浅层复制和整体浅层复制

slice方法获取一个ByteBuf切片,可以多次切片,多此切片后ByteBuf 对象共享一个存储区域。

slice后新的ByteBuf不能写入,因为maxCapacity和writerIndex一样。切片和原来可读的字节数相同。

切片不会复制源ByteBuf的底层数据,和源是一个。

duplicate整体浅层复制,也不会复制源的yteBuf的底层数据。

浅层问题:
在源ByteBuf调用release()后,一旦计数为0,则浅层复制的也不能读写了。
解决方案:
浅层复制时,retain()一次,使用完调用两次release()。

6、回显程序

netty服务端:

public class NettyEchoServer {

    public static void main(String[] args) {
        NettyEchoServer server = new NettyEchoServer();
        server.runServer();
    }
    public void runServer(){

        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        try{

            serverBootstrap.group(bossGroup,workGroup)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(9000)
                    .option(ChannelOption.SO_KEEPALIVE,true)
                    .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyEchoServerHandler());
                        }
                    });
            ChannelFuture channelFuture = serverBootstrap.bind().sync();
            ChannelFuture closeFuture = channelFuture.channel().closeFuture();
            closeFuture.sync();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            workGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

服务端handler

// 多通道共享
@ChannelHandler.Sharable
public class NettyEchoServerHandler extends ChannelInboundHandlerAdapter {
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf in  = (ByteBuf) msg;
        System.out.println(in.hasArray()?"堆内存":"直接内存");

        int len = in.readableBytes();

        byte [] arr = new byte[len];

        in.getBytes(0,arr);

        System.out.println("server received is :"+ new String(arr,"UTF-8"));

        System.out.println("写回前:"+ ((ByteBuf)msg).refCnt());

        ChannelFuture channelFuture = ctx.writeAndFlush(msg);

        channelFuture.addListener((ChannelFutureListener)->{
            System.out.println("写回后:"+ ((ByteBuf)msg).refCnt());
        });

    }
}

客户端:

public class NettyEchoClient {

    private String serverIp;
    private Integer port;

    Bootstrap b = new Bootstrap();

    NettyEchoClient(String serverIp,Integer port){
        this.serverIp = serverIp;
        this.port = port;
    }
    public void runClient(){
        EventLoopGroup workerLoopGroup = new NioEventLoopGroup();

        try{
            b.group(workerLoopGroup)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(serverIp,port)
                    .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(NettyEchoClientHandler.INSTANCE);
                        }

                    });

            ChannelFuture f = b.connect();
            f.addListener((ChannelFuture futureListener)->{
               if( futureListener.isSuccess()){
                   System.out.println("连接成功");
               }else{
                   System.out.println("连接失败");
               }
            });

            f.sync();

            Channel channel = f.channel();
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入内容:");

            while (scanner.hasNext()){
                String next = scanner.next();
                byte[] bytes = (System.currentTimeMillis() + ">>" + next).getBytes();

                ByteBuf buffer = channel.alloc().buffer();
                buffer.writeBytes(bytes);

                channel.writeAndFlush(buffer);
                System.out.println("请输入内容:");
            }

        }catch (Exception e){
            System.out.println(e);
        }finally {
            workerLoopGroup.shutdownGracefully();
        }


    }

    public static void main(String[] args) {
        NettyEchoClient nettyEchoClient = new NettyEchoClient("127.0.0.1", 9000);
        nettyEchoClient.runClient();
    }
}

客户端handler:

public class NettyEchoClientHandler extends ChannelInboundHandlerAdapter {

    public  static final NettyEchoClientHandler INSTANCE = new NettyEchoClientHandler();

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;

        int len = byteBuf.readableBytes();

        byte [] arr = new byte[len];

        byteBuf.getBytes(0,arr);

        System.out.println("客户端收到:"+ new String(arr, CharsetUtil.UTF_8));

        //byteBuf.release();

        super.channelRead(ctx, msg);
    }
}

分别启动server和client,输入测试数据:
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值