This is the seventh day of my participation in the August More text Challenge. For details, see:August is more challenging

preface

In the last two articles, we have provided a simple understanding of Netty and an analysis of the architecture design principles.

Links to related articles are as follows:

Netty source analysis series (a) Netty overview

Netty source code analysis series (2) Netty architecture design

In this article, we will begin to analyze the Netty source code, first we will explain the Netty Channel related functions and interfaces.

The body of the

Summary of the Channel

A Channel, as its name suggests, is a pipe, representing a relationship between a network Socket or component that can perform I/O operations. These I/O operations include read, write, connect, and bind.

Simply put, a Channel stands for a connection, a connection between entities, a connection between programs, a connection between files, a connection between devices. It is also a carrier of inbound and outbound data.

A Channel in Netty provides users with the following functions:

  • Query the status of the current Channel. For example, whether it is open or connected.
  • Provides Channel parameter configuration. For example, the receive buffer size.
  • Provides supported I/O operations. For example, read, write, join, and bind.
  • Provide ChannelPipeline. The ChannelPipeline user handles all I/O events and requests associated with the Channel.

The Channel characteristics

I/O operations are asynchronous

All I/O operations in a Channel are asynchronous, returning as soon as they are called, and there is no guarantee that the requested I/O operation will be complete by the end of the call. Instead, each call returns a ChannelFuture instance to represent the future result. The instance will notify the user when the I/O operation is actually completed, and then the results of the specific I/O operation can be obtained.

Channels are layered

A Channel has a parent, which is also a Channel. And depending on which Channel is created, its parent will also be different. For example, after a SocketChannel connects to a ServerSocketChannel, the parent of that SocketChannel will be that ServerSocketChannel. The semantics of the hierarchical structure depend on which transport implementation the Channel uses.

Transition down to access the following output-specific operations

Some transports expose operations specific to that transports, so the Channel can be cast down into a subtype to invoke such operations.

Release resources

Call ChannelOutboundInvoker, once a Channel. The close () or ChannelOutboundInvoker. Close (ChannelPromise) to release all narcissism is very important. This ensures that all resources are released in the appropriate manner (as file handles).

Channel interface method

Here is the core source code for the Channel interface:

public interface Channel extends AttributeMap.ChannelOutboundInvoker.Comparable<Channel> {

    // Return the globally unique channel ID
    ChannelId id(a);

    // Returns the event poller registered for this channel
    EventLoop eventLoop(a);
    
    // Return the parent channel of this channel, or null if it is an instance of ServerSocketChannel.
    // The SocketChannel instance returns the corresponding ServerSocketChannel
    Channel parent(a);

    // Return the configuration parameters of the channel
    ChannelConfig config(a);

    // if the port isOpen, the isOpen method returns true as soon as the channel is created, and false when the close method is called
    boolean isOpen(a);

    // Whether the EventLoop is registered
    boolean isRegistered(a);

    // Whether the channel is active
    boolean isActive(a);

    // Return metadata for the channel
    ChannelMetadata metadata(a);

    // IP address of the server
    SocketAddress localAddress(a);

    // remoteAddress Specifies the IP address of the client
    SocketAddress remoteAddress(a);

    // Channel close credentials (license), here is a typical design pattern of multithreaded programming, a channle returns a fixed
    ChannelFuture closeFuture(a);

    // Whether the channel is writable, returns true if the channel's write buffer is full, indicating that the write operation can operate on the buffer immediately, and then returns.
    boolean isWritable(a);

    long bytesBeforeUnwritable(a);

    long bytesBeforeWritable(a);

    Channel.Unsafe unsafe(a);

    // Return the pipe
    ChannelPipeline pipeline(a);
       
    // Return the ByteBuf memory allocator
    ByteBufAllocator alloc(a);

    Channel read(a);

    Channel flush(a);

    public interface Unsafe {
        Handle recvBufAllocHandle(a);

        SocketAddress localAddress(a);

        SocketAddress remoteAddress(a);

        // Register the channel in EventLoop
        void register(EventLoop var1, ChannelPromise var2);

        // Bind the channel to an adress,
        void bind(SocketAddress var1, ChannelPromise var2);

        // The Netty client connects to the server
        void connect(SocketAddress var1, SocketAddress var2, ChannelPromise var3);

        // Disconnect the server, but no resources are released. The channel can be reconnected to the server through connect
        void disconnect(ChannelPromise var1);

        // Close the channel to recycle resources. The life cycle of the channel is completely ended
        void close(ChannelPromise var1);

        void closeForcibly(a);
        
        // Cancel registration.
        void deregister(ChannelPromise var1);

        // Read IO data from channel
        void beginRead(a);
        
        // Write data to channe
        void write(Object var1, ChannelPromise var2);

        void flush(a);

        ChannelPromise voidPromise(a);

        ChannelOutboundBuffer outboundBuffer(a); }}Copy the code

We can see that the Channel interface inherits from AttributeMap, ChannelOutboundInvoker, and Comparable

, and provides many other interfaces.

ChannelOutboundInvoker

The ChannelOutboundInvoker interface declares all outbound network operations.

ChannelOutboundInvoker interface core source code:

public interface ChannelOutboundInvoker {
    ChannelFuture bind(SocketAddress var1);

    ChannelFuture connect(SocketAddress var1);

    ChannelFuture connect(SocketAddress var1, SocketAddress var2);

    ChannelFuture disconnect(a);

    ChannelFuture close(a);

    ChannelFuture deregister(a);

    ChannelFuture bind(SocketAddress var1, ChannelPromise var2);

    ChannelFuture connect(SocketAddress var1, ChannelPromise var2);

    ChannelFuture connect(SocketAddress var1, SocketAddress var2, ChannelPromise var3);

    ChannelFuture disconnect(ChannelPromise var1);

    ChannelFuture close(ChannelPromise var1);

    ChannelFuture deregister(ChannelPromise var1);

    ChannelOutboundInvoker read(a);

    ChannelFuture write(Object var1);

    ChannelFuture write(Object var1, ChannelPromise var2);

    ChannelOutboundInvoker flush(a);

    ChannelFuture writeAndFlush(Object var1, ChannelPromise var2);

    ChannelFuture writeAndFlush(Object var1);

    ChannelPromise newPromise(a);

    ChannelProgressivePromise newProgressivePromise(a);

    ChannelFuture newSucceededFuture(a);

    ChannelFuture newFailedFuture(Throwable var1);

    ChannelPromise voidPromise(a);
}
Copy the code

From the above source code, we can see that most methods in the ChannelOutboundInvoker interface return values are ChannelFuture and ChannelPromise. Therefore, operations on the ChannelOutboundInvoker interface are asynchronous.

The difference between ChannelFuture and ChannelPromise is that ChannelFuture is used to get asynchronous results, while ChannelPromise is an extension of ChannelFuture, supporting write operations. Therefore, ChannelPromise is also known as writable ChannelFuture.

AttributeMap

The Channel interface inherits from the AttributeMap interface, so what does the AttributeMap interface do?

Here is the core source code for AttributeMap:

public interface AttributeMap {
    <T> Attribute<T> attr(AttributeKey<T> var1);

    <T> boolean hasAttr(AttributeKey<T> var1);
}
Copy the code

As you can see from the source code, an AttributeMap is actually a pair of keys and values similar to a Map, where the key is of type AttributeKey and the value is of type Attribute. In other words, AttributeMap is used to hold attributes.

We know that every ChannelHandlerContext is a bridge between ChannelHandler and ChannelPipeline. Every ChannelHandlerContext has its own context. So every ChannelHandlerContext that has AttributeMap in it is context-bound, which means that if an AttributeMap is in A ChannelHandlerContext, Is not read by B’s ChannelHandlerContext.

Netty provides a default implementation class for AttributeMap, DefaultAttributeMap. Compared with ConcurrentHashMap in JDK, DefaultAttributeMap can save more memory under high concurrency.

DefaultAttributeMap’s core source code is as follows:

public class DefaultAttributeMap implements AttributeMap {

        // Update the reference to the attributes variable atomically
        private static final AtomicReferenceFieldUpdater<DefaultAttributeMap, AtomicReferenceArray> updater = 		AtomicReferenceFieldUpdater.newUpdater(DefaultAttributeMap.class, AtomicReferenceArray.class, "attributes");

        // The default bucket size
        private static final int BUCKET_SIZE = 4;

        // Mask Bucket size 3
        private static final int MASK = 3;

        // Delay initialization to save memory
        private volatileAtomicReferenceArray<DefaultAttributeMap.DefaultAttribute<? >> attributes;public <T> Attribute<T> attr(AttributeKey<T> key) {
        ObjectUtil.checkNotNull(key, "key"); AtomicReferenceArray<DefaultAttributeMap.DefaultAttribute<? >> attributes =this.attributes;
        if (attributes == null) {
            // Create it when attributes is empty. The default array length is 4
            attributes = new AtomicReferenceArray(4);
            
            // Update attributes atomically, if attributes is null then assign a value to the newly created object
			// Atomic update solves the concurrent assignment problem
            if(! updater.compareAndSet(this, (Object)null, attributes)) {
                attributes = this.attributes; }}// Calculate the index based on key
        inti = index(key); DefaultAttributeMap.DefaultAttribute<? > head = (DefaultAttributeMap.DefaultAttribute)attributes.get(i);if (head == null) {
            head = new DefaultAttributeMap.DefaultAttribute();
            DefaultAttributeMap.DefaultAttribute<T> attr = new DefaultAttributeMap.DefaultAttribute(head, key);
            head.next = attr;
            attr.prev = head;
            if (attributes.compareAndSet(i, (Object)null, head)) {
                return attr;
            }	
            
			// Return the first element in the Attributes array
            head = (DefaultAttributeMap.DefaultAttribute)attributes.get(i);
        }
		
        // Lock the head synchronously
        synchronized(head) {
            Head is the first variable in the list structure
            DefaultAttributeMap.DefaultAttribute curr = head;

            while(true) {
                // Get nextDefaultAttributeMap.DefaultAttribute<? > next = curr.next;//next==null, indicating that curr is the last element
                if (next == null) {
                    // Create a new object and pass in the head and key
                    DefaultAttributeMap.DefaultAttribute<T> attr = new DefaultAttributeMap.DefaultAttribute(head, key);
                    //curr is followed by attr
                    curr.next = attr;
                    // The front of attr points to curr
                    attr.prev = curr;
                    // Return the new object
                    return attr;
                }
				// If the key of next is equal to the key passed in and is not removed
                if(next.key == key && ! next.removed) {// This returns next directly
                    return next;
                }
				// Otherwise, point the curr variable to the next elementcurr = next; }}}public <T> boolean hasAttr(AttributeKey<T> key) {
        ObjectUtil.checkNotNull(key, "key");
        // Attributes return false for nullAtomicReferenceArray<DefaultAttributeMap.DefaultAttribute<? >> attributes =this.attributes;
        if (attributes == null) {
            return false;
        } else {
            // Calculate the array index
            int i = index(key);
            // Return false if the header is nullDefaultAttributeMap.DefaultAttribute<? > head = (DefaultAttributeMap.DefaultAttribute)attributes.get(i);if (head == null) {
                return false;
            } else {
                // Lock the head synchronously
                synchronized(head) {
                    // Start next to head, because head does not store elements
                    for(DefaultAttributeMap.DefaultAttribute curr = head.next; curr ! =null; curr = curr.next) {
                        if(curr.key == key && ! curr.removed) {return true; }}return false; }}}}private static int index(AttributeKey
        key) {
		// Make sure that <=mask is the index of the array
        return key.id() & 3; }}Copy the code

As you can see from the above source code, DefaultAttributeMap uses AtomicReferenceArray and synchronized to ensure concurrency security.

The AtomicReferenceArray class provides low-level operations for referencing arrays that can be read and written atomically, as well as high-level atomic operations. The AtomicReferenceArray supports atomic operations on underlying reference array variables. It has methods for getting and setting, such as reading and writing on variables. The compareAndSet() method is used to determine whether the current value is equal to the expected value, and if so, atomically sets the element at position I to the given updated value.

conclusion

After reading the above introduction about Channel, I believe you should have a certain understanding of Channel, and we continue to analyze the interface and source code related to Channel.

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!