preface

Understand zero copy, zero copy is one of the important features of Netty, and what exactly is zero copy? The WIKI defines it as follows:

“Zero-copy” describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.

From the WIKI definition, we see ** “zero copy” refers to a computer operation in which the CPU does not need to consume resources for data to be copied between memory. ** When a computer sends a file over the network, it is transferred directly from Kernel Space to the network without copying the contents of the file into User Space. 2021Java core knowledge interview true question share.

Non-zero Copy mode:

Zero Copy mode:

As you can see from the figure above, the **Zero Copy mode avoids copying data between user space and memory space, thus improving the overall performance of the system. ** The SendFile () method in Linux and the Filechannel.transferto () method in Java NIO both implement zero copy. Netty also implements zero copy by wrapping NIO’s filechannel.transferto () method in FileRegion.

There is another form of zero copy in Netty, that is, Netty allows us to merge multiple pieces of data into a whole virtual data for users to use, and the process does not need to copy the data operation, which is the focus of today’s talk. We all know that during a stream-based transport (such as TCP/IP), packets may be repackaged in different packets, such as when you send the following data:

It is possible that the actual data received is as follows:

So in practice, it’s very likely that a whole message is split up into multiple packets, and individual packets are meaningless to you, and you can only do the right thing when those packets make up a whole message. Netty can use zero-copy mode to combine these packets into a complete message for you to use. In this case, the zero copy scope is only in user space.

Netty has formulated a unified ChannelBuffer interface for the data that needs to be transmitted. The main design ideas of this interface are as follows:

1. Use the getByte(int index) method to implement random access

2. Sequential access is implemented using double Pointers

Each Buffer has a readIndex and a writeIndex.

The read pointer moves back when data is read and the write pointer moves back when data is written

Once you’ve defined a unified interface, it’s time to do the various implementations. Netty mainly implements HeapChannelBuffer ByteBufferBackedChannelBuffer, etc., let’s talk about is directly related to the Zero Copy CompositeChannelBuffer classes. The CompositeChannelBuffer class CompositeChannelBuffer is used to compose multiple channelBuffers into a virtual ChannelBuffer for operation.

The reason for the CompositeChannelBuffer is that it does not actually combine multiple channelBuffers, but only saves their references. This avoids copying data and implements a Zero Copy. Let’s look at the actual code implementation, starting with member variables

private int readerIndex; private int writerIndex; private ChannelBuffer[] components; private int[] indices; private int lastAccessedComponentId;

Some of the more important member variables are listed here. The readerIndex and writerIndex Pointers are inherited from AbstractChannelBuffer. Then components is an array of the ChannelBuffer that holds all the subbuffers that make up the virtual Buffer. Indices is an int array that holds the indexes of each Buffer. The last lastAccessedComponentId is an int that records the child Buffer ID at the last access.

From this data structure, we can see that the so-called CompositeChannelBuffer is actually a series of buffers stored in an array, and then implements the ChannelBuffer interface, so that from the upper level, These buffers are operated on as if they were a single Buffer.

create . Next, let’s look at the CompositeChannelBuffer setComponents method, it will be initialized

CompositeChannelBuffer is called. /** * Setup this ChannelBuffer from the list */ private void setComponents(List<ChannelBuffer> newComponents) { assert ! newComponents.isEmpty(); // Clear the cache. lastAccessedComponentId = 0; // Build the component array. components = new ChannelBuffer[newComponents.size()]; for (int i = 0; i < components.length; i ++) { ChannelBuffer c = newComponents.get(i); if (c.order() ! = order()) { throw new IllegalArgumentException( "All buffers must have the same endianness."); } assert c.readerIndex() == 0; assert c.writerIndex() == c.capacity(); components[i] = c; } // Build the component lookup table. indices = new int[components.length + 1]; indices[0] = 0; for (int i = 1; i <= components.length; i ++) { indices[i] = indices[i - 1] + components[i - 1].capacity(); } // Reset the indexes. setIndex(0, capacity()); }Copy the code

The function of this method is to combine a List of channelBuffers. It first puts the List elements into the Components array, then creates indices to find the data, and finally uses setIndex to reset the pointer. Note that setIndex(0, capacity()) sets the read pointer to 0 and the write pointer to the current Buffer length. Assert c. readerindex () == 0 and assert c. readindex () == c.capacity(). Otherwise, data may be read and written repeatedly.

So Netty recommended we use ChannelBuffers. WrappedBuffer approach to Buffer merge, This is because Netty uses the slice() method to ensure that all subbuffers that are passed in to build the CompositeChannelBuffer are compliant.

The data access CompositeChannelBuffer. GetByte (int index) implementation is as follows:

public byte getByte(int index) { int componentId = componentId(index); return components[componentId].getByte(index - indices[componentId]); } 'componentId = componentId = componentId = componentId = componentId = componentId = componentId = componentId = componentId = componentId = componentId = componentId = componentId = componentId = componentId = componentId = componentId Then return the result. Private int componentId(int index) {int componentId = lastAccessedComponentId; if (index >= indices[lastComponentId]) { if (index < indices[lastComponentId + 1]) { return lastComponentId; } // Search right for (int i = lastComponentId + 1; i < components.length; i ++) { if (index < indices[i + 1]) { lastAccessedComponentId = i; return i; } } } else { // Search left for (int i = lastComponentId - 1; i >= 0; i --) { if (index >= indices[i]) { lastAccessedComponentId = i; return i; } } } throw new IndexOutOfBoundsException("Invalid index: " + index + ", maximum: " + indices.length); }Copy the code

From the code, we can see that Netty searches left and right with lastComponentId as the number of the last subbuffer accessed. The purpose of this is that when we randomly look for two similar character sequences (which is the case in most cases), The fastest search to the target index componentId.

The last

I have sorted out a Netty related information documents, Spring series family bucket, Java systematic information :(including Java core knowledge points, interview topics and 21 years of the latest Internet real questions, e-books, etc.) friends in need can follow the public number [program yuanxiaowan] can get.