This is the 13th day of my participation in the August More Text Challenge. For details, see:August is more challenging

preface

Last article we carried on the analysis of the zero copy principle, a thorough understanding of the zero copy principle

Before we dive into Netty’s zero copy in this article, let’s take a look at how Java implements zero copy.

Java implements zero copy

The Java implementation of zero copy is based on the underlying operating system. Currently, Java supports two zero-copy technologies: mmap/write and Sendfile.

1. Java provides mmap/write mode

Java NIO provides MappedByteBuffer for mmap/write.

A Channel in Java NlO acts as a kernel Buffer in the operating system, either a read Buffer or a network Buffer, and a Buffer acts as a user Buffer in the operating system.

Here is a use case for MappedByteBuffer:

File file = new File("jw.txt");
try {
    FileChannel fc = new RandomAccessFile(file, "rw").getChannel();
    MappedByteBuffer map = fc.map(FileChannel.MapMode.READ_WRITE, 0, file.length());
    map.put("jiangwang".getBytes());
    fc.position(file.length());
    map.clear();
    fc.write(map);
} catch (IOException e) {
    e.printStackTrace();
}
Copy the code

In the example above, the MappedByteBuffer is created using the filechannel.map () method, which is implemented by calling Linux’s mmap().

This method maps the memory of the kernel buffer and the memory of the user buffer. This is good for reading large files and making changes to the contents of the files, but if the data is to be sent later through a SocketChannel, the CPU still needs to copy the data.

Using MappedByteBuffer is inefficient if the file is small; And the MappedByteBuffer can only be obtained by calling the FileChannel map(), and no other way. Therefore, the MappedByteBuffer in Java is designed for large files.

2. Java provides sendfile

The Java filechannel.transferto () low-level implementation is implemented using Linux’s SendFile. This method transfers the contents of the current channel directly to another channel without any action involving buffers.

Here is an example of how to use filechannel.transferTo () :

// Use sendfile: read the disk file and send it over the network
FileChannel sourceChannel = new RandomAccessFile(source, "rw").getChannel();
SocketChannel socketChannel = SocketChannel.open(sa);
sourceChannel.transferTo(0, sourceChannel.size(), socketChannel);
Copy the code

Netty implements zero copy

The implementation of zero copy in Netty is Java-based; in other words, the underlying implementation is also operating system based. Netty’s zero copy is more biased toward the concept of optimized data manipulation than Java’s zero copy.

Zero copy in Netty is reflected in the following aspects:

  • Netty providesCompositeByteBufClass, which can be multipleByteBufMerge into a logicalByteBuf, to avoid eachByteBufBetween.
  • throughwrapYou can wrap the byte [] array, ByteBuf, and ByteBuffer into a Netty ByteBuf object, avoiding copying operations.
  • ByteBufsupportsliceOperation, so it can beByteBufSplit into multiple units sharing the same storage areaByteBufTo avoid memory replication.
  • throughFileRegionThe packing ofFileChannel.transferTo()To achieve file transfer, you can directly send the data in the file buffer to the targetChannel, to avoid through the loopwhileMemory replication problem caused by mode.

As can be seen from the above methods, the first three methods are generalized zero-copy, which are implemented in order to reduce unnecessary data replication and tend to optimize data operations at the application layer. And the fourth method, FileRegion

The wrapped filechannel.transferto () is the true zero copy (narrow zero copy).

Let’s look at each of these implementations separately.

1. CompositeByteBuf

CompositeByteBuf combines multiple ByteBuFs into a logical ByteBuf, similar to using a linked list to join discrete ByteBuFs by reference. Multiple discrete Bytebufs can be discrete regions of different sizes in memory, linked together by a linked list as one logically large region. When the actual data is read, it will be read from each of the pieces.

The image below shows how CompositeByteBuf works:

Here is an example code for CompositeByteBuf:

ByteBuf header = ...
ByteBuf body = ...
CompositeByteBuf compositeBuffer = Unpooled.compositeBuffer();
compositeBuffer.addComponents(true, header,body);
Copy the code

2. Wrap

Zero copy can be achieved through the WRAP operation.

Wrap a Netty ByteBuf object by wrapping a byte [] array, ByteBuf, ByteBuffer, and so on.

For example, wrap bytes as an UnpooledHeapByteBuf object with the Unpooled. WrappedBuffer method. There is no copying during wrapping. The resulting ByteBuf object shares the same storage space as the bytes array, and changes to bytes are reflected in the ByteBuf object.

Here is an example of code used by Unpooled. WrappedBuffer:

ByteBuf header = ...
ByteBuf body = ...
ByteBuf allByteBuf = Unpooled.wrappedBuffer(header,body);
Copy the code

3, slice method

Zero copy can be achieved through slice. The schematic diagram is as follows:

Through the Slice operation, the ByteBuf is split into multiple ByteBuFs that share the same storage area. Slice (int index,int Length) ¶ Slice (int index,int Length) is used to read data from the original buffer.

Here is sample code for Slice:

ByteBuf bytebuf = ...
ByteBuf header = bytebuf.slice(0.5);
ByteBuf body = bytebuf.slice(5.10);
Copy the code

4. FileRegion mode

Underneath FileRegion is Java’s Filechannel.transferTo () implementation of file transfer, so you can directly send data from the file buffer to the target Channel. This approach is true zero copy at the operating system level.

Here is an example code for FileRegion:

public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
    RandomAccessFile raf = null;
    long length = 0;
    try {
        //1. Open a file with RandomAccessFile
        raf = new RandomAccessFile(msg, "r");
        length = raf.length();
    } catch (Exception e) {
        ctx.writeAndFlush("ERR:" + e.getClass() + ":" + e.getMessage());
        return;
    } finally {
        if (length < 0& raf ! =null) {
            raf.close();
        }
    }

    ctx.write(raf.length());
    if (ctx.pipeline().get(SslHandler.class) == null) {
        //2. Call the raf.getChannel() method to get a FileChannel
        //3. Encapsulate FileChannel into a DefaultFileRegion
        ctx.write(new DefaultFileRegion(raf.getChannel(), 0, length));
    } else {
        ctx.write(new ChunkedFile(raf));
    }
    ctx.write("\n");

}
Copy the code

conclusion

When we write data to the buffer, if the data written exceeds the set capacity, what should we do? In fact, Netty provides dynamic capacity expansion mechanism, interested partners can learn about it.

We will discuss the source analysis of Netty’s bootstrap in the next section.

At the end

I am a code is being hit is still trying to advance. If the article is helpful to you, remember to like, follow yo, thank you!