Netty study notes 1--ByteBuf to be continued

       The ByteBuf in netty is extended based on the ByteBuffer of java.nio, mainly because there are some inconveniences in the use of the ByteBuffer in nio, such as: 1. The created ByteBuffer object has a fixed capacity, and an error will be reported when the capacity is exceeded. 2. There is only one pointer to identify the location. When reading and writing data, you need to manually call the flip() and rewind() methods. A little careless use will cause the program to report an error. Of course, there are other problems in the description of this in some official documents, which will not be described in detail here.

       Let's talk about ByteBuf in netty, let's see how this solves the above problems and what are the extended functions?

       1. How ByteBuf works:

        First of all, ByteBuf is still a buffer of byte array. Its basic function is the same as that of jdk's ByteBuffer, as follows:

(1) Reading and writing of 7 basic types of java, byte array, ByteBuffer (ByteBuf), etc.;

(2) Copy (copy) and slice (interception) of the buffer itself;

(3) Set the network byte order;

(4) Construct a buffer instance;

(5) Methods such as operating the position pointer.

 The above functions correspond to which methods in ByteBuf will be added later.

 ByteBuf assists the read and write operations of the buffer through two position pointers. The read operation uses the readerIndex, and the write operation uses the writerIndex. The readerIndex and writerIndex are both 0 at the beginning. As the data is written, the writerIndex will increase, and reading the data will make the readerIndex increases, but readerIndex is always smaller than writerIndex. After reading the data, the data between 0 and readerIndex is regarded as discard, and calling the discardReadBytes method can release this part of the space, which is a bit like the compact method in nio ByteBuffer. In addition, the data between the readerIndex and writerIndex methods is readable, which is equivalent to the data between position and limit in nio ByteBuffer. The space between writerIndex and capacity is writable, equivalent to the free space between limit and capacity in ByteBuffer.

 Since the read operation does not modify the value of the writerIndex, the write operation modifies the value of the readerIndex, so there is no need to adjust the position pointer between reading and writing, which greatly simplifies the reading and writing operations of the buffer and avoids omission or unfamiliar flip operations. abnormal function.

 Next, we continue to discuss how ByteBuf achieves dynamic expansion. Usually, when we put the ByteBuffer, we need to check whether the writable space of the current ByteBuffer is enough. If it is not enough, we need to copy the current ByteBuffer to a ByteBuffer with a larger capacity, and then copy the previous ByteBuffer. ByteBuffer is released, the specific code is as follows:

 

if(this.buffer.remaining() < needSize) {
   int toBeExtSize = needSize> 128? needSize: 128;
   ByteBuffer tmpBuffer = ByteBuffer.allocate(this.buffer.capacity+toBeExtSize);
   this.buffer.flip();
   tmpBuffer.put(this.buffer);
   this.buffer = tmpBuffer;
   
}

 

 

 

 

 

 It can be seen from the above code that every time a put operation is performed, a check of the available space must be performed, which is very troublesome in the usual code implementation, and a little carelessness may cause other problems. In order to solve this problem, netty ByteBuf encapsulates the write operation of nio ByteBuffer, and the encapsulated write method verifies whether the available space of the current buffer is sufficient and expands the capacity. The specific code is as follows:

@Override
    public ByteBuf ensureWritable(int minWritableBytes) {
        if (minWritableBytes < 0) {
            throw new IllegalArgumentException(String.format(
                    "minWritableBytes: %d (expected: >= 0)", minWritableBytes));
        }

        if (minWritableBytes <= writableBytes()) {
            return this;
        }

        if (minWritableBytes > maxCapacity - writerIndex) {
            throw new IndexOutOfBoundsException(String.format(
                    "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
                    writerIndex, minWritableBytes, maxCapacity, this));
        }

        // Normalize the current capacity to the power of 2.
        int newCapacity = calculateNewCapacity(writerIndex + minWritableBytes);

        // Adjust to the new capacity.
        capacity(newCapacity);
        return this;
    }

 

@Override
    public ByteBuf writeShort(int value) {
        ensureAccessible();
        ensureWritable(2);
        _setShort(writerIndex, value);
        writerIndex += 2;
        return this;
    }

    @Override
    public ByteBuf writeMedium(int value) {
        ensureAccessible();
        ensureWritable(3);
        _setMedium(writerIndex, value);
        writerIndex += 3;
        return this;
    }

    @Override
    public ByteBuf writeInt(int value) {
        ensureAccessible();
        ensureWritable(4);
        _setInt(writerIndex, value);
        writerIndex += 4;
        return this;
    }

 As can be seen from the above code, ByteBuf will call the ensureWritable(int) method every time a write operation is performed to ensure sufficient available space to ensure that there will be no shortage of capacity. At this time, you don't need to consider the problem of capacity, you only need to concentrate on the logic of what we specifically write into ByteBuf.

The principle of ByteBuf has been roughly summarized before. Let’s take a look at the api. What methods does ByteBuf provide us with?

1. Sequential read operation (read)

The read operation of ByteBuf is similar to the get operation of ByteBuffer,

 

 

 

 

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326608284&siteId=291194637