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,输入测试数据: