Introduction

Channel is a bridge connecting ByteBuf and Event. Channel in netty provides a unified API. Through this unified API, netty can easily connect to multiple transmission types, such as OIO, NIO, etc. Today this article will introduce the use of Channel and some concepts related to Channel.

Channel detailed

What is Channel? Channel is a bridge connecting network input and IO processing. You can use the Channel to determine the current state, whether it is open or connected, you can also determine the IO operations supported by the current Channel, and you can also use ChannelPipeline to process messages in the Channel.

First look at the definition of Channel:

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

You can see that Channel is an interface that inherits three classes: AttributeMap, ChannelOutboundInvoker, and Comparable. Comparable means that this class can be used for comparison. AttributeMap is used to store various attributes of Channel. ChannelOutboundInvoker is mainly responsible for the connection and writing between Channel and external SocketAddress.

Look at the methods defined in the channel:

It can be seen that there are various methods defined in the channel. What are the characteristics of these methods? Next, I will explain to you one by one.

Asynchronous IO and ChannelFuture

All IOs in netty are asynchronous IOs, which means that all IOs are returned immediately. When returning, the IO may not be over yet, so a ChannelFuture needs to be returned. When the IO has a result, the ChannelFuture will be notified, so You can retrieve the result.

ChannelFuture is a subclass of java.util.concurrent.Future. In addition to getting the execution result of the thread, it also extends it, adding the functions of judging the current task status, waiting for task execution, and adding listeners.

The other functions are well understood. Its breakthrough is that you can add a listener to ChannelFuture. We list a method to add a listener:

Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);

The added Listener will be notified after the execution of the future ends. You don't need to call get to wait for the future to end. This is actually the realization of the concept of asynchronous IO, you don't need to call it actively, just notify me when you are done. Very beautiful!

ChannelFuture has two states: uncompleted or completed, which respectively represent the execution state of the task.

When an IO is just started, a ChannelFuture object is returned. The initial state of this object is uncompleted. Note that the IO in this state has not yet started working. When the IO is completed, whether it is in the succeeded, failed or canceled state, the state of the ChannelFuture will change to completed.

The following figure shows the corresponding diagram of ChannelFuture state and IO state:

                                    +---------------------------+
                                    | Completed successfully    |
                                    +---------------------------+
                               +---->      isDone() = true      |

+--------------------------+ | | isSuccess() = true |
| Uncompleted | | +===========================+
+--------------------------+ | | Completed with failure |
| isDone() = false | | +---------------------------+
| isSuccess() = false |----+----> isDone() = true |
| isCancelled() = false | | | cause() = non-null |
| cause() = null | | +===========================+
+--------------------------+ | | Completed by cancellation |

                               |    +---------------------------+
                               +---->      isDone() = true      |
                                    | isCancelled() = true      |
                                    +---------------------------+

If you want to monitor the status of IO, you can use the addListener method we mentioned above to add a ChannelFutureListener to ChannelFuture.

If you want to wait for the completion of the IO execution, there is an await() method, but this method will wait for the completion of the IO execution. It is a synchronous method, so it is not recommended.

In contrast, addListener(GenericFutureListener) is a non-blocking asynchronous method that will add a ChannelFutureListener to the ChannelFuture, and automatically notify the ChannelFutureListener when the IO ends, which is very easy to use.

For the ChannelHandler that handles IO operations, in order to avoid IO blocking, you must not call await() in the ChannelHandler's IO method. This may cause the ChannelHandler to degrade performance due to IO blocking.

Here are two examples, one is the wrong operation, the other is the correct operation:

   // 错误操作
    @Override
   public void channelRead(ChannelHandlerContext ctx, Object msg) {
       ChannelFuture future = ctx.channel().close();
       future.awaitUninterruptibly();
       // 调用其他逻辑
   }
  
   // 正确操作
    @Override
   public void channelRead(ChannelHandlerContext ctx, Object msg) {
       ChannelFuture future = ctx.channel().close();
       future.addListener(new ChannelFutureListener() {
           public void operationComplete(ChannelFuture future) {
               // 调用其他逻辑
           }
       });
   }

You can compare the difference between the above two ways of writing.

Also note that these await methods in ChannelFuture such as: await(long), await(long, TimeUnit), awaitUninterruptibly(long), or awaitUninterruptibly(long, TimeUnit) can have an expiration time, everyone should pay attention to this expiration The time is the time to wait for the execution of IO, not the timeout time of IO. That is to say, after await times out, IO may not be executed. This leads to the following code may report an error:

   Bootstrap b = ...;
   ChannelFuture f = b.connect(...);
   f.awaitUninterruptibly(10, TimeUnit.SECONDS);
   if (f.isCancelled()) {
       // 用户取消了Channel
   } else if (!f.isSuccess()) {
       // 这里可能会报异常,因为底层的IO可能还没有执行完成
       f.cause().printStackTrace();
   } else {
       // 成功建立连接
   }
  

The above code can be changed to the following example:

  Bootstrap b = ...;
   // 配置连接timeout的时间
   b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
   ChannelFuture f = b.connect(...);
   f.awaitUninterruptibly();
  
   // 等待直到底层IO执行完毕
   assert f.isDone();
  
   if (f.isCancelled()) {
       // 用户手动取消Channel
   } else if (!f.isSuccess()) {
       f.cause().printStackTrace();
   } else {
       // 成功建立连接
   }
   

Channel hierarchy

The Channel in netty has a hierarchical structure, and this hierarchical structure can be obtained through the parent attribute. The object obtained by the parent is related to the way the Channel is created. For example, if it is a SocketChannel accepted by ServerSocketChannel, then its parent is ServerSocketChannel.

Release resources

Like all IO, the Channel needs to be released after it is used up, and the close() or close(ChannelPromise) method needs to be called.

Event handling

The channel is responsible for establishing the connection, and the established connection can be used to process the event ChannelEvent. In fact, the ChannelEvent is processed by the defined Channelhandlers. And ChannelPipeline is the bridge connecting channel and channelhandler.

We will explain the relationship between ChannelEvent, Channelhandler and ChannelPipeline in detail in the next chapter, so stay tuned.

Summarize

Channel exists as a key channel in netty. The following Event and Handler run on the basis of channel, so Channel is the basis of netty. Ok, today’s introduction is over here, please. Looking forward to follow-up articles.

This article has been included in http://www.flydean.com/04-netty-channel/

The most popular interpretation, the most profound dry goods, the most concise tutorial, and many tips you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", know technology, know you better!


flydean
890 声望433 粉丝

欢迎访问我的个人网站:www.flydean.com