Netty---ByteBuf实现

一,获取

1.ByteBuf实现分类

 按底层实现分类

    1.HeadByteBuf: 底层实现为java堆内的字节数组,byte[],可以有GC回收。特点是内涵的分配和回收速度块,缺点是如果进行socket的io读写,需要额外做一次内存复制,将堆内存的内容复制到内核内存中,性能会有一定程度的下降。

    2.DirectByteBuf:直接内存字节缓冲区,非堆内存,它在堆外进行内存分配,相比于堆内存,它的分配和回收都相对较慢,但是读写操作,少了一次内存复制,速度比堆内存块。内核空间的字节数组,native堆中,由操作系统申请和管理,所以分配和释放速度较慢,因为调用方法要走更多的流程。

    3.CompositeByteBuf:上面两种方式的组合,也是一种零拷贝技术。内部实现组合两个缓冲区。

按内存回收角度分类:

    1.UnpooledByteBuf,没有使用对象池的普通bytebuf,

    2.PooledByteBuf:使用了对象池的bytebuf,避免了bytebuf的重复分配,回收,减少了GC的负担,但是内存池的管理和维护更加复杂,netty4.1默认使用对象池缓冲区。4.0默认使用非对象池缓冲区。

2.创建ByteBuf

ByteBuf的Allocator分配器:创建缓冲区和分配内存空间,提供了两种实现

        1.PoolByteBufAllocator:将ByteBuf实例放入池对象中,提高性能,将内存碎片减少到最小。

        2.UnpooledByteBufAllocator是普通未池化ByteBuf分配器,不会把ByteBuf放入池中,每次调用返回一个新的ByteBuf实例。

说明:我们先从 非池化的开始介绍,再介绍池化的,从简单到难

2.1 UnpooledHeadByteBuf

它分配的内存就在jvm管理的堆中,可以说是最简单的一个ByteBuf,它的内部就是一个byte[]组。

字段:

private final ByteBufAllocator alloc; //非池化 分配器
byte[] array;
private ByteBuffer tmpNioBuf;

我们先看它是如何通过xxxAllocator分配出来的

1.UnpooledByteBufAllocator.newHeapBuffer()方法

protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
    return PlatformDependent.hasUnsafe() ?
            new InstrumentedUnpooledUnsafeHeapByteBuf(this, initialCapacity, maxCapacity) :
            new InstrumentedUnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
}

 这里我们走第一个new对象,它是xxAllocator中的内部类,也就是本类,它的特点就是继承了我们需要实例化的对象,

private static final class InstrumentedUnpooledUnsafeHeapByteBuf extends UnpooledUnsafeHeapByteBuf {
    InstrumentedUnpooledUnsafeHeapByteBuf(UnpooledByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
        super(alloc, initialCapacity, maxCapacity);
    }

    它的构造方法,就是直接调用父类的构造方法,所以,我们就拿到了UnpooledHeapByteBuf的子类了。

2.UnpooledHeapByteBuf 构造方法

    它有两个构造方法,一个是根据大小自己初始化一个Byte[]数组,另外一个是直接赋值Byte[]数组,不用自己再去初始化。

public UnpooledHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
    super(maxCapacity);
    if (initialCapacity > maxCapacity) { //抛出异常
       ....
    }
    this.alloc = checkNotNull(alloc, "alloc"); //检测是否为空
    setArray(allocateArray(initialCapacity));  //申请 创建一个初始的数组
    setIndex(0, 0);
}

重点方法就在这个SetArray上面,它是嵌套了,里面有一个分配的方法

直接,简单

protected byte[] allocateArray(int initialCapacity) {
    return new byte[initialCapacity];
}
private void setArray(byte[] initialArray) {
    array = initialArray;
    tmpNioBuf = null;
}

ByteBuf初始化之后,我们再来看它的读取

3.get/set方法

随机读取getInt()

public int getInt(int index) {
    ensureAccessible();
    return _getInt(index);
}
protected int _getInt(int index) {
    return HeapByteBufUtil.getInt(array, index); //使用工具类
}

工具类HeapByteBufUtil的具体实现,一个Int占4个byte。

static int getInt(byte[] memory, int index) {
    return  (memory[index]     & 0xff) << 24 |
            (memory[index + 1] & 0xff) << 16 |
            (memory[index + 2] & 0xff) <<  8 |
            memory[index + 3] & 0xff;
}

setInt的具体实现

static void setInt(byte[] memory, int index, int value) {
    memory[index]     = (byte) (value >>> 24);
    memory[index + 1] = (byte) (value >>> 16);
    memory[index + 2] = (byte) (value >>> 8);
    memory[index + 3] = (byte) value;
}

4.ByteBuf转ByteBuffer

public ByteBuffer nioBuffer(int index, int length) {
    ensureAccessible();
    return ByteBuffer.wrap(array, index, length).slice();
}

跳转到ByteBuffer中的wrap打包方法

public static ByteBuffer wrap(byte[] array,int offset, int length){
    try {
        return new HeapByteBuffer(array, offset, length);  //直接new一个出来
...
}

直接就根据内容,new了一个Nio ByteBuffer出来,转换逻辑简单。

2.2 UnpooledDirectByteBuf

它使用直接内存,交给操作系统分配,所以相比于Heap来说,复杂一些

字段:

private final ByteBufAllocator alloc;
ByteBuffer buffer; // accessed by UnpooledUnsafeNoCleanerDirectByteBuf.reallocateDirect()
private ByteBuffer tmpNioBuf;
private int capacity;
private boolean doNotFree; //是否释放

它与HeapByteBuf的直接不同点就是,它使用的是nio ByteBuffer来存在了,而HeapByteBuf使用的是数组。

1.newDirectBuffer()

位于UnpooledByteBufAllocator,非池化的ByteBuf的创建,都在这个分配器里面

protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
    final ByteBuf buf;
    if (PlatformDependent.hasUnsafe()) {
        buf = noCleaner ? new InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf(this, initialCapacity, maxCapacity) :
                new InstrumentedUnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
    } else {
        buf = new InstrumentedUnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
    }
    return disableLeakDetector ? buf : toLeakAwareBuffer(buf);
}

还是一样的套路,内部类,我们看unsafeNoClearDirectByteBuf这个,调用父类构造方法,直接super(),来到我们的目标类中,先检测字段是否合法,最后直接allocateDirect()方法构建

public UnpooledDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
    super(maxCapacity);
    ObjectUtil.checkNotNull(alloc, "alloc");
    checkPositiveOrZero(initialCapacity, "initialCapacity");
    checkPositiveOrZero(maxCapacity, "maxCapacity");
    if (initialCapacity > maxCapacity) {
        throw new IllegalArgumentException(String.format(
                "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
    }

    this.alloc = alloc;
    setByteBuffer(allocateDirect(initialCapacity), false);
}

它调用的是ByteBuffer的allocateDirect()方法

protected ByteBuffer allocateDirect(int initialCapacity) {
    return ByteBuffer.allocateDirect(initialCapacity);
}

这个方法,直接返回一个nio的 DirectByteBuffer,所以在我们的目标类中,字段是nio的ByteBuffer就是这个原因,我们保存的是nio的DirectByteBuffer类

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

它的继承关系

来看DirectByteBuffer类的构造方法,传入的是初始大小

DirectByteBuffer(int cap) {                   // package-private
    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();  //是否使用 页对齐
    int ps = Bits.pageSize();                //页大小,默认是4086b,4kb
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));  //如果使用页对齐,那么在基本的大小cap上,还要+页大小
    Bits.reserveMemory(size, cap);  //尝试申请内存  ★

    long base = 0;
    try {
        base = unsafe.allocateMemory(size);     //分配内存,返回基地址
    } catch (OutOfMemoryError x) { 
        Bits.unreserveMemory(size, cap);//没内存可分配,回滚修改的内存数据 throw x; 
    }
    unsafe.setMemory(base, size, (byte) 0);  //设置内存里的初始值为0
    if (pa && (base % ps != 0)) {        //地址对齐,且基地址不是页大小的整数倍
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));  //将地址改为页大小的整数倍
    } else {
        address = base;   //地址就是基地址
    }
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));  //创建内存回收器,传的是基地址
    att = null;  //没附件
}

    页对齐的,是使用空间换时间,前面一个页没有使用完毕,那么我们新加入的数据,可能会一部分放在前面一个页,一部分放在新页上面,我们增加一个页空间,不管什么情况,我们都可以把自己全部内容换到一个新的页,占用该页的开头位置,(多余的空间就用来填补前一个页余下的空间,这里就是浪费空间的意思),而之前如果一个数据放在两个页上面就会加载两次,现在我们只需要加载一次,就达到了空间换时间的目的。

    具体的分配:Bits.reserveMemory()

static void reserveMemory(long size, int cap) {
    if (!memoryLimitSet && VM.isBooted()) {
        maxMemory = VM.maxDirectMemory();   //获取分配的上限
        memoryLimitSet = true;
    }
    // optimist!  看是否还能申请内存,成功就返回
    if (tryReserveMemory(size, cap)) {
        return;
    }
    final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();

    // retry while helping enqueue pending Reference objects
    // which includes executing pending Cleaner(s) which includes
    // Cleaner(s) that free direct buffer memory
    while (jlra.tryHandlePendingReference()) {
        if (tryReserveMemory(size, cap)) {  //再次尝试申请
            return;
        }
    }
    System.gc();  //如果没有成功,就启动gc

    // a retry loop with exponential back-off delays
    // (this gives VM some time to do it's job)
    boolean interrupted = false;
    try {
        long sleepTime = 1;
        int sleeps = 0;
        while (true) {
            if (tryReserveMemory(size, cap)) {
                return;
            }
            if (sleeps >= MAX_SLEEPS) {  //尝试等待9次睡眠,大概0.5秒,如果还没内存就退出循环
                break;
            }
            if (!jlra.tryHandlePendingReference()) {//如果没有释放内存就sleep
                try {
                    Thread.sleep(sleepTime);
                    sleepTime <<= 1;  //睡眠时间x2
                    sleeps++;
                } catch (InterruptedException e) {
                    interrupted = true;
                }
            }
        }

        // no luck  没有内存可以申请,抛出异常
        throw new OutOfMemoryError("Direct buffer memory");

    } finally {
        if (interrupted) {
            // don't swallow interrupts
            Thread.currentThread().interrupt();
        }
    }
}

这里就要涉及底层的东西了,我们先到这里,详细内容,可以看下方的参考链接,超级详细。

 

2.3 池化ByteBuf

这个内容,就更加复杂了,也可以直接参考下方的链接,我就不当搬运工了!

二,释放

    在流水线的末尾添加TailHandler处理器,进行自动释放。

    但是如果流水线没有走完,而是走到了一半就截断了,这个时候要嘛就是手动释放,

    或者SimpleChannelInbounHandler进行释放。

一共上述三种情况。

(自动释放)https://blog.csdn.net/wangwei19871103/article/details/104679073

参考链接:https://blog.csdn.net/wangwei19871103/article/details/104235590

                https://blog.csdn.net/wangwei19871103/article/details/104264130

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是使用Netty-Mqtt-Client实现Mqtt客户端发布消息和订阅消息的核心Java代码,带注释说明: ```java import io.netty.buffer.Unpooled; import io.netty.handler.codec.mqtt.*; import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttPublishMessage; import io.netty.handler.codec.mqtt.MqttSubscribeMessage; import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; import io.netty.handler.codec.mqtt.MqttMessageBuilders.*; // 创建一个Mqtt客户端 public class MqttClient { private final String clientId; // 客户端ID private final String serverHost; // 服务器主机名 private final int serverPort; // 服务器端口号 private final String username; // 用户名 private final String password; // 密码 private final int keepAlive; // 心跳间隔时间 private EventLoopGroup group; // Netty线程组 private MqttClientInitializer initializer; // Netty客户端初始化器 private Channel channel; // Netty通道 // 构造方法,初始化Mqtt客户端配置 public MqttClient(String clientId, String serverHost, int serverPort, String username, String password, int keepAlive) { this.clientId = clientId; this.serverHost = serverHost; this.serverPort = serverPort; this.username = username; this.password = password; this.keepAlive = keepAlive; } // 连接服务器 public void connect() { group = new NioEventLoopGroup(); // 创建Netty线程组 initializer = new MqttClientInitializer(clientId, username, password, keepAlive); // 创建Netty客户端初始化器 Bootstrap bootstrap = new Bootstrap(); // 创建Netty客户端启动器 bootstrap.group(group) .channel(NioSocketChannel.class) .remoteAddress(serverHost, serverPort) .handler(initializer); try { ChannelFuture future = bootstrap.connect().sync(); // 连接服务器,同步等待连接完成 if (future.isSuccess()) { // 连接成功 channel = future.channel(); // 获取Netty通道 } } catch (InterruptedException e) { e.printStackTrace(); } } // 断开连接 public void disconnect() { if (channel != null && channel.isActive()) { channel.close(); // 关闭Netty通道 } if (group != null) { group.shutdownGracefully(); // 关闭Netty线程组 } } // 发布消息 public void publish(String topic, String message, MqttQoS qos) { MqttFixedHeader header = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, false, 0); MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, 0); ByteBuf payload = Unpooled.buffer(); payload.writeBytes(message.getBytes()); MqttPublishMessage publishMessage = new MqttPublishMessage(header, variableHeader, payload); channel.writeAndFlush(publishMessage); // 发送Mqtt PUBLISH消息 } // 订阅主题 public void subscribe(String topic, MqttQoS qos) { MqttFixedHeader header = new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(1); MqttTopicSubscription topicSubscription = new MqttTopicSubscription(topic, qos); MqttSubscribePayload payload = new MqttSubscribePayload(Arrays.asList(topicSubscription)); MqttSubscribeMessage subscribeMessage = new MqttSubscribeMessage(header, variableHeader, payload); channel.writeAndFlush(subscribeMessage); // 发送Mqtt SUBSCRIBE消息 } // 取消订阅主题 public void unsubscribe(String topic) { MqttFixedHeader header = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(1); MqttUnsubscribePayload payload = new MqttUnsubscribePayload(Arrays.asList(topic)); MqttUnsubscribeMessage unsubscribeMessage = new MqttUnsubscribeMessage(header, variableHeader, payload); channel.writeAndFlush(unsubscribeMessage); // 发送Mqtt UNSUBSCRIBE消息 } } ``` 以上代码中,我们创建了一个MqttClient,该通过Netty-Mqtt-Client实现了Mqtt客户端发布消息和订阅消息的功能。具体实现细节如下: - connect()方法:连接Mqtt服务器,其中我们通过Netty创建了一个NioEventLoopGroup线程组、一个MqttClientInitializer客户端初始化器和一个Bootstrap客户端启动器,并将它们配置好后发起连接请求; - disconnect()方法:断开Mqtt服务器连接,关闭Netty通道和线程组; - publish()方法:发布Mqtt消息,其中我们使用了MqttFixedHeader、MqttPublishVariableHeader、ByteBuf和MqttPublishMessage等Netty-Mqtt-Client提供的来构建Mqtt PUBLISH消息,并通过Netty通道将其发送给服务器; - subscribe()方法:订阅Mqtt主题,其中我们使用了MqttFixedHeader、MqttMessageIdVariableHeader、MqttTopicSubscription、MqttSubscribePayload和MqttSubscribeMessage等Netty-Mqtt-Client提供的来构建Mqtt SUBSCRIBE消息,并通过Netty通道将其发送给服务器; - unsubscribe()方法:取消订阅Mqtt主题,其中我们使用了MqttFixedHeader、MqttMessageIdVariableHeader、MqttUnsubscribePayload和MqttUnsubscribeMessage等Netty-Mqtt-Client提供的来构建Mqtt UNSUBSCRIBE消息,并通过Netty通道将其发送给服务器。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值