Netty 零拷贝(三)Netty 对零拷贝的改进

Netty 零拷贝(三)Netty 对零拷贝的改进

Netty 系列目录 (https://www.cnblogs.com/binarylei/p/10117436.html)

Netty 的“零拷贝”主要体现以下几个方面:

  1. Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到直接内存中,然后才写入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
  2. Netty 提供了 CompositeByteBuf 类,它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf,避免了传统通过内存拷贝的方式将几个小 Buffer 合并成一个大的 Buffer。
  3. 通过 FileRegion 包装的 FileChannel.tranferTo 方法 实现文件传输,可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统通过循环 write 方式导致的内存拷贝问题。
  4. 通过 wrap 操作,我们可以将 byte[] 数组、ByteBuf、ByteBuffer 等包装成一个 Netty ByteBuf 对象,进而避免了拷贝操作

一、直接缓冲区的应用

Netty 中使用直接缓冲区来读写数据,首先看一下 read 方法中缓冲区的创建方法。

// AbstractNioByteChannel.NioByteUnsafe#read
public final void read() {
    final ChannelConfig config = config();
    final ByteBufAllocator allocator = config.getAllocator();
    final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
    ByteBuf byteBuf = allocHandle.allocate(allocator);
    // 省略...
}

Netty 中接收缓冲区 ByteBuffer 由 ChannelConfig 分配,而 ChannelConfig 创建 ByteBufAllocator 默认使用 Direct Buffer,这就避免了读写数据的二次内存拷贝问题,从而实现了读写 Socket 的零拷贝功能。

// ByteBufAllocator 用于分配缓冲区
private volatile ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;

// RecvByteBufAllocator 可伸缩的缓冲区,根据每次接收的数据大小自动分配缓冲区大小
private volatile RecvByteBufAllocator rcvBufAllocator;
public DefaultChannelConfig(Channel channel) {
    this(channel, new AdaptiveRecvByteBufAllocator());
}

AdaptiveRecvByteBufAllocator 继承自 DefaultMaxMessagesRecvByteBufAllocator,在 allocate 分配缓冲区。

// DefaultMaxMessagesRecvByteBufAllocator.MaxMessageHandle.allocate
public ByteBuf allocate(ByteBufAllocator alloc) {
    return alloc.ioBuffer(guess());
}

// AbstractByteBufAllocator
public ByteBuf ioBuffer(int initialCapacity) {
    if (PlatformDependent.hasUnsafe()) {
        return directBuffer(initialCapacity);
    }
    return heapBuffer(initialCapacity);
}

PlatformDependent.hasUnsafe() 通过判断能否加载到 sun.misc.Unsafe 类就使用直接缓冲区,正常情况下返回 true

private static final boolean HAS_UNSAFE = hasUnsafe0();
public static boolean hasUnsafe() {
    return HAS_UNSAFE;
}

// 默认如果能通过反射获取 sun.misc.Unsafe 实例则使用直接缓冲区,因为直接缓冲区底层就是使用 Unsafe 类
private static boolean hasUnsafe0() {
    // 1. 加载 android.app.Application 类则返回 true
    if (isAndroid()) {
        return false;
    }

    // 2. -Dio.netty.noUnsafe=true
    if (PlatformDependent0.isExplicitNoUnsafe()) {
        return false;
    }

    // 3. 通过反射获取 sun.misc.Unsafe 类。Unsafe.class.getDeclaredField("theUnsafe")
    try {
        boolean hasUnsafe = PlatformDependent0.hasUnsafe();
        logger.debug("sun.misc.Unsafe: {}", hasUnsafe ? "available" : "unavailable");
        return hasUnsafe;
    } catch (Throwable t) {
        logger.trace("Could not determine if Unsafe is available", t);
        return false;
    }
}

我们再分析一直 ByteBufAllocator allocator = ByteBufAllocator.DEFAULT 中的默认 ByteBufAllocator。

ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;

// ByteBufUtil,主要判断是否将内存池化,因为直接缓冲区的分配和销毁开销比较大
static final ByteBufAllocator DEFAULT_ALLOCATOR;
static {
    String allocType = SystemPropertyUtil.get(
            "io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
    allocType = allocType.toLowerCase(Locale.US).trim();

    ByteBufAllocator alloc;
    if ("unpooled".equals(allocType)) {
        alloc = UnpooledByteBufAllocator.DEFAULT;
        logger.debug("-Dio.netty.allocator.type: {}", allocType);
    } else if ("pooled".equals(allocType)) {
        alloc = PooledByteBufAllocator.DEFAULT;
        logger.debug("-Dio.netty.allocator.type: {}", allocType);
    } else {
        alloc = PooledByteBufAllocator.DEFAULT;
        logger.debug("-Dio.netty.allocator.type: pooled (unknown: {})", allocType);
    }

    DEFAULT_ALLOCATOR = alloc;
}

// 除了 HAS_UNSAFE 外还需要判断 io.netty.noPreferDirect 属性
public static final PooledByteBufAllocator DEFAULT =
            new PooledByteBufAllocator(PlatformDependent.directBufferPreferred());
public static final UnpooledByteBufAllocator DEFAULT =
        new UnpooledByteBufAllocator(PlatformDependent.directBufferPreferred());
public static boolean directBufferPreferred() {
    return DIRECT_BUFFER_PREFERRED;
}
private static final boolean DIRECT_BUFFER_PREFERRED =
        HAS_UNSAFE && !SystemPropertyUtil.getBoolean("io.netty.noPreferDirect", false);

二、CompositeByteBuf

//定义两个ByteBuf类型的 body 和 header
CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();
compositeByteBuf.addComponents(true, header, body);

addComponents 方法将 header 与 body 合并为一个逻辑上的 ByteBuf, 这两个 ByteBuf 在 CompositeByteBuf 内部都是单独存在的, CompositeByteBuf 只是逻辑上是一个整体。

三、通过 FileRegion 实现零拷贝

利用 nio 提供的 transferTo 实现零拷贝。

srcFileChannel.transferTo(position, count, destFileChannel);

四、通过 wrap / slice 实现零拷贝

// wrappedBuffer 和 slice 都是共享同一内存,并没有拷贝
ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes);

ByteBuf header = byteBuf.slice(0, 5);
ByteBuf body = byteBuf.slice(5, 10);


每天用心记录一点点。内容也许不重要,但习惯很重要!

原文地址:https://www.cnblogs.com/binarylei/p/10117437.html

时间: 2024-10-11 20:51:31

Netty 零拷贝(三)Netty 对零拷贝的改进的相关文章

Netty构建游戏服务器(三)--netty spring简单整合

一,基本方法 上节实现了netty的基本连接,这节加入spring来管理netty,由spring来开启netty服务. 在netty服务器中,我们建立了三个类:HelloServer(程序主入口) , HelloServerInitializer(传输通道初始化),HelloServerHandler(业务控制器) 这三个类中HelloServer中new了一个HelloServerInitializer,在HelloServerInitializer最后又new了一个HelloServerH

Netty Reator(三)Reactor 模型

Netty Reator(三)Reactor 模型 Netty 系列目录 (https://www.cnblogs.com/binarylei/p/10117436.html) 本文介绍 DC Schmidt 大神的一篇文章<Reactor: an object behavioral pattern for concurrent event demultiplexing and event handler dispatching> 一.What:Reactor 模式是什么? 反应器设计模式(R

【Netty】(9)---Netty编解码器

Netty编解码器 在了解Netty编解码之前,先了解Java的编解码: 编码(Encode)称为序列化, 它将对象序列化为字节数组,用于网络传输.数据持久化或者其它用途. 解码(Decode)称为反序列化,它把从网络.磁盘等读取的字节数组还原成原始对象(通常是原始对象的拷贝),以方便后续的业务逻辑操作. java序列化对象只需要实现java.io.Serializable接口并生成序列化ID,这个类就能够通过java.io.ObjectInput和java.io.ObjectOutput序列化

深入了解Netty【六】Netty工作原理

引言 前面学习了NIO与零拷贝.IO多路复用模型.Reactor主从模型. 服务器基于IO模型管理连接,获取输入数据,又基于线程模型,处理请求. 下面来学习Netty的具体应用. 1.Netty线程模型 Netty线程模型是建立在Reactor主从模式的基础上,主从 Rreactor 多线程模型: 但是在Netty中,bossGroup相当于mainReactor,workerGroup相当于SubReactor与Worker线程池的合体.如: EventLoopGroup bossGroup

拷贝构造,深度拷贝,关于delete和default相关的操作,explicit,类赋初值,构造函数和析构函数,成员函数和内联函数,关于内存存储,默认参数,静态函数和普通函数,const函数,友元

 1.拷贝构造 //拷贝构造的规则,有两种方式实现初始化. //1.一个是通过在后面:a(x),b(y)的方式实现初始化. //2.第二种初始化的方式是直接在构造方法里面实现初始化. 案例如下: #include<iostream> //如果声明已经定义,边不会生成 class classA { private: int a; int b; public: //拷贝构造的规则,有两种方式实现初始化 //1.一个是通过在后面:a(x),b(y)的方式实现初始化 //2.第二种初始化的方式是直

Netty学习总结(1)--Netty入门介绍

1.Netty是什么? Netty是一个基于JAVA NIO类库的异步通信框架,它的架构特点是:异步非阻塞.基于事件驱动.高性能.高可靠性和高可定制性. 2.使用Netty能够做什么? 开发异步.非阻塞的TCP网络应用程序: 开发异步.非阻塞的UDP网络应用程序: 开发异步文件传输应用程序: 开发异步HTTP服务端和客户端应用程序: 提供对多种编解码框架的集成,包括谷歌的Protobuf.Jboss marshalling.Java序列化.压缩编解码.XML解码.字符串编解码等,这些编解码框架可

精通并发与Netty入门一:Netty理论知识介绍

Netty是目前无论是国内还是国外各大互联网公司必备的一个网络应用框架.Netty本身既然是网络框架,处理的基本都是与网络相关的这样的一些作用.由于Netty本身在设计上的一些非常巧妙的方式,是对于NIO的一个很好的实现.Netty在各种应用场景下都会得到很广泛的应用.无论是传统的基于http的这种短连接方式还是基于底层Socket的这样的访问方式.另外还支持H5中规范中新增加的一个特别重要的标准,就是关于长连接的websocket这样一种新的规范.Netty对于其提供了非常好的支撑.那么Net

关于我的网站(八零家园三周年)

三年转瞬即逝,2011年8月6日注册了www80.org这个域名,并给这个网站起名叫“八零家园”,当时计划的是在做成一个怀念八零后的旧时光的网站,还有第二步计划是如果做大了还要开通八零后交流的论坛——“八零家园”.但是,事实上,这三年,这个网站并没有做成,原因有很多,总之是失败了.2011年的8月6日,我还没有到山东大学读研,2014年的8月6日,我已经从山东大学毕业,来到了浪潮集团工作.以后,要工作,还要不断学习,还有很多目标要去实现,所以这个网站估计也就荒废了,但是我会一直留着的.与网站还有

条款十一: 为需要动态分配内存的类声明一个拷贝构造函数和一个拷贝赋值运算符

看下面一个表示string对象的类: // 一个很简单的string类 class string { public: string(const char *value); ~string(); ... // 没有拷贝构造函数和operator= private: char *data; }; string::string(const char *value) { if (value) { data = new char[strlen(value) + 1]; strcpy(data, value