Tool and Resource Center

Help developers work more efficiently and provide tools and resources around the developer lifecycle

Developer.aliyun.com/tool?spm=a1…

Introduction to the

The netty class used to carry and communicate information is called ByteBuf. As the name indicates, this is a cache of bytes. Take a look.

ByteBuf,

Netty provides an io.netty.buffer package that defines various types of ByteBuf and their derived types.

The foundation of Netty Buffer is the ByteBuf class, an abstract class from which all other Buffer classes are derived. This class also defines the tone of netty’s entire Buffer.

Let’s start with the definition of ByteBuf:

public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf> {
Copy the code

ByteBuf implements two interfaces, ReferenceCounted and Comparable. Comparable is the JDK’s built-in interface to indicate that classes can be compared to one another. ReferenceCounted is a reference count for an object. When a ReferenceCounted is instantiated with a reference to count=1, each time the retain() method is called, the count is increased, and the release() method is called, the count is decreased. When the count is reduced to 0, the object will be released, and an access exception will be reported if an attempt is made to access a freed object.

If an object implements ReferenceCounted, and other objects within that object also implement ReferenceCounted, then the release() method will be used to release the contents of the container object when count=0.

To sum up, ByteBuf is a comparable object that counts the number of references. It provides sequential or random byte access.

Note that while the JDK comes with its own ByteBuffer class, ByteBuf in Netty is a reimplementation of Byte Buffers. They are not related.

Creating a Buff

ByteBuf is an abstract class and cannot be instantiated directly. Although it is possible to use subclasses of ByteBuf for instantiation, Netty does not recommend it. Netty recommends io.netty.buffer.Unpooled to create buffs. Unpooled is a utility class that can allocate space, copy, or encapsulate ByteBuf.

Here are some examples of creating different Bytebufs:

import static io.netty.buffer.Unpooled.*;
   ByteBuf heapBuffer    = buffer(128);
   ByteBuf directBuffer  = directBuffer(256);
   ByteBuf wrappedBuffer = wrappedBuffer(new byte[128], new byte[256]);
   ByteBuf copiedBuffer  = copiedBuffer(ByteBuffer.allocate(128));
Copy the code

Above we see four different buff builds: regular buffs, directBuffer, wrappedBuffer and copiedBuffer.

A normal buff is a fixed-size heap buff, while a directBuffer is a fixed-size direct buff. Direct buffs are more efficient than normal buffs because they use out-of-heap memory, eliminating the need to copy data to the kernel.

WrappedBuffer is the encapsulation of existing Byte Arrays or byte buffers, which can be regarded as a view. When the underlying data changes, the data in the Wrapped Buffer will also change.

Unlike a wrappedBuffer, which is a deep copy of an existing byte Array, byte buffers or string, it does not share data between the Copied buffer and the original data.

Random access Buff

If you are familiar with collections, you must use index to access a collection. Similarly, you can use capacity or ByteBuf to access its capacity. Then you can use getByte to access its bytes randomly.

ByteBuf buffer = heapBuffer; for (int i = 0; i < buffer.capacity(); i ++) { byte b = buffer.getByte(i); System.out.println((char) b); }Copy the code

Sequence, speaking, reading and writing

Reading and writing is a bit more complicated than accessing. ByteBuf provides two indexes to locate read and write positions. They are readerIndex and writerIndex.

The following figure shows a buffer divided into three parts: bytes that can be discarded, bytes that can be read, and bytes that can be written.

+-------------------+------------------+------------------+
    | discardable bytes |  readable bytes  |  writable bytes  |
    |                   |     (CONTENT)    |                  |
    +-------------------+------------------+------------------+
    |                   |                  |                  |
    0      <=      readerIndex   <=   writerIndex    <=    capacity
Copy the code

The figure also shows the size relationship between readerIndex, writerIndex, and Capacity.

The readable Bytes is the actual content and can be accessed or skipped by calling the read or skip methods. When these methods are called, the readerIndex will be increased synchronously. If the readable Bytes is outside the scope of the readable Bytes, Will throw IndexOutOfBoundsException. By default readerIndex=0.

Here is an example of traversing the readable bytes:

// Iterate over the readable Bytes while (directBuffer.isreadable ()) {system.out.println (DirectBuffer.readbyte ()); }Copy the code

First determine whether to call the readByte method by determining whether it is readable.

Writable bytes is an undefined area waiting to be filled. Its operation, by calling the write * way writerIndex will update at the same time, in the same way, if the space is not enough, also sell IndexOutOfBoundsException. By default the newly allocated writerIndex is 0 and the writerIndex of the copied buffer is buf capacity.

Here is an example of using writable bytes:

/ / write writable bytes while (wrappedBuffer maxWritableBytes () > = 4) {wrappedBuffer. WriteInt (new Random (). NextInt ()); }Copy the code

Discardable Bytes are bytes that have been read and are initially valued at 0. Each time the readerIndex moves to the right, the number of Discardable bytes increases. To completely delete or reset the Discardable bytes, you can call the discardReadBytes() method, which removes the Discardable bytes space and places the extra space in the writable bytes, as follows:

Before calling discardReadBytes() :  +-------------------+------------------+------------------+ | discardable bytes | readable bytes | writable bytes | + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + | | | | 0 < < = = readerIndex writerIndex < = capacity calls After discardReadBytes () :  +------------------+--------------------------------------+ | readable bytes | writable bytes (got more space) | +------------------+--------------------------------------+ | | |Copy the code

readerIndex (0) <= writerIndex (decreased) <= capacity

Note that the writable bytes are larger, but the contents are not controlled. There is no guarantee that the contents will be empty or unchanged.

The clear() method clears the readerIndex and writerIndex values. Note that the clear() method only sets the readerIndex and writerIndex values. It does not clear the content.

Before calling clear() :  +-------------------+------------------+------------------+ | discardable bytes | readable bytes | writable bytes | + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + | | | | 0 < < = = readerIndex writerIndex < = capacity calls After the clear () :  +---------------------------------------------------------+ | writable bytes (got more space) | +---------------------------------------------------------+ | | 0 = readerIndex = writerIndex <= capacityCopy the code

search

ByteBuf provides a single byte search function, such as indexOf(int, int, byte) and bytesBefore(int, int, byte) methods.

ForEachByte (int, int, ByteProcessor) is used for search processing of ByteBuf traversal, which receives a ByteProcessor for complex processing.

Other derived buffer methods

ByteBuf also provides a number of methods to create derived buffers, such as:

duplicate()
slice()
slice(int, int)
readSlice(int)
retainedDuplicate()
retainedSlice()
retainedSlice(int, int)
readRetainedSlice(int)
Copy the code

Note that these BUFs are derivatives of the existing BUFs. The underlying content is the same, except for the readerIndex, writerIndex and marked index. So they are sharing data with the original BUF. If you want to create an entirely new buffer, you can use the copy() method or the previously mentioned unpooled.copiedBuffer.

In the previous section, we said that ByteBuf is a ReferenceCounted, and this feature is used in the derived buf. We know that the reference count increases when we call retain(), but for duplicate(), slice(), slice(int, int), and readSlice(int), even though they’re all references, But the retain() method is not called, so the original data will be collected after any Buf call to the release() method.

RetainedDuplicate (), retainedSlice(), retainedSlice(int, int), and readRetainedSlice(int), These methods call the retain() method to add a reference.

To an existing JDK type

As mentioned earlier, ByteBuf is a rewrite of ByteBuffer. They are different implementations. Although these two are different, they do not prevent converting ByteBuf into a ByteBuffer.

Of course, the simplest conversion is to convert ByteBuf to the byte array byte[]. To convert to a byte array, call hasArray() for determination and then call the array() method for conversion.

The same ByteBuf can also be converted to a ByteBuffer by calling nioBufferCount() to determine the number of ByteBuffers that can be converted to, and then nioBuffer() to convert it.

The returned ByteBuffer is a share or copy of the existing BUF. Changing the position and limit of the returned buffer does not affect the original BUF.

Finally, ByteBuf can be converted to a String using the toString(Charset) method.

conclusion

ByteBuf is the underlying foundation of Netty, is the data transmission bearer object, in-depth understanding of ByteBuf can understand the design of Netty, very good.

An example for this article is learn-Netty4