netty series: channel and channelGroup

brief introduction

Channel is not only the channel of data transmission and data processing in netty, but also an indispensable part of netty program. In netty, channel is an interface, which has different implementations for different data types or protocols.

Although channel is very important, it is really mysterious in the code. Basically, we rarely see the direct use of channel. Is this really the case? What is the role of ChannelGroup related to channel? Let's have a look.

The Dragon sees the head but not the tail

In fact, the code of netty has a fixed template. First, it depends on whether it is the server side or the client side, and then create the corresponding Bootstrap and ServerBootstrap. Then configure the corresponding group method for this Bootstrap. Then configure channel and handler for Bootstrap, and finally start Bootstrap.

Such a standard netty program is completed. All you need to do is choose the right group, channel and handler.

Let's take a look at the simplest NioServerSocketChannel:

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChatServerInitializer());

            b.bind(PORT).sync().channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

Here, we set NioServerSocketChannel as the channel of ServerBootstrap.

Is that all? Where is channel used?

Don't worry, let's take a closer look at the last sentence in try block:

b.bind(PORT).sync().channel().closeFuture().sync();

b.bind(PORT).sync() actually returns a ChannelFuture object. By calling its channel method, it returns the channel object associated with it.

Then we called channel closeFuture() method. The closeFuture method will return a ChannelFuture object, which will be notified when the channel is closed.

The sync method will implement synchronization blocking until the channel is closed, so as to carry out the subsequent shutdown operation of eventGroup.

In the template built in ServerBootstrap, channel actually has two functions. The first function is to specify the channel of ServerBootstrap. The second function is to obtain the channel closing event through channel, and finally close the whole netty program.

Although we can hardly see the direct method call of channel, there is no doubt that channel is the soul of netty.

Next, let's take a look at the basic operations of the handler for specific message processing:

    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // channel active
        ctx.write("Channel Active state!\r\n");
        ctx.flush();
    }

Usually, if we need to write data to the channel in the handler, we call the write method of ChannelHandlerContext. What is the relationship between this method and channel?

First, the write method is the method in the ChannelOutboundInvoker interface, and both ChannelHandlerContext and Channel inherit the ChannelOutboundInvoker interface, that is, both ChannelHandlerContext and Channel have write methods:

ChannelFuture write(Object msg);

Because we use NioServerSocketChannel here, let's take a specific look at the implementation of write in NioServerSocketChannel.

After checking the code, we will find that NioServerSocketChannel inherits from AbstractNioMessageChannel,AbstractNioMessageChannel inherits from AbstractNioChannel, AbstractNioChannel inherits from AbstractChannel, and this write method is implemented in AbstractChannel:

    public ChannelFuture write(Object msg) {
        return pipeline.write(msg);
    }

The write method of Channel actually calls the write method of pipeline. The following is the write method in pipeline:

    public final ChannelFuture write(Object msg) {
        return tail.write(msg);
    }

The tail here is an AbstractChannelHandlerContext object.

In this way, we come to the conclusion that the write method in channel actually calls the write method in ChannelHandlerContext.

So the above:

ctx.write("Channel Active state!\r\n");

In fact, it can be called directly from channel:

Channel ch = b.bind(0).sync().channel();

// Write message to channel
ch.writeAndFlush("Channel Active state!\r\n").sync();

channel and channelGroup

Channel is the soul of netty. For Bootstrap, to get the corresponding channel, you can call:

b.bind(PORT).sync().channel()

From the above code, we can also see that a Bootstrap only corresponds to one channel.

There is a parent() method in the channel to return its parent channel, so the channel has a hierarchical structure,

Let's take another look at the definition of channelGroup:

public interface ChannelGroup extends Set<Channel>, Comparable<ChannelGroup> 

You can see that the ChannelGroup is actually a collection of channels. ChannelGroup is used to build similar channels into a collection, so that multiple channels can be managed uniformly.

Can I have a little partner to ask, doesn't a Bootstrap correspond to only one channel? So where did the collection of channels come from?

In fact, in some complex programs, we may start multiple bootstraps to handle different services, so there will be multiple channel s accordingly.

If too many channels are created and these channels are very homogeneous, it is necessary to manage these channels uniformly. At this time, you need to use channelGroup.

Basic use of channelGroup

Let's take a look at the basic use of channelGroup. First, create a channelGroup:

ChannelGroup recipients =
           new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

After having a channelGroup, you can call the add method to add different channels to it:

   recipients.add(channelA);
   recipients.add(channelB);

You can also send messages to these channel s:

recipients.write(Unpooled.copiedBuffer(
           "This is from channelGroup Unified messaging from.",
           CharsetUtil.UTF_8));

Basically, channelGroup provides functions such as write, flush, fluxandwrite, writeandflush, disconnect, close, newclosefuture, etc., which are used for unified management of channels in the collection.

If you have multiple channels, consider using channelGroup.

In addition, channelGroup has some features. Let's learn more about them.

Automatically remove the closed channel

ChannelGroup is a collection of channels. Of course, we only want to save channels in open state. If channels in close d state need to be removed from ChannelGroup manually, it is too troublesome.

So in the ChannelGroup, if a channel is closed, it will be automatically removed from the ChannelGroup. How is this function realized?

Let's first look at the add method of channelGroup:

   public boolean add(Channel channel) {
        ConcurrentMap<ChannelId, Channel> map =
            channel instanceof ServerChannel? serverChannels : nonServerChannels;

        boolean added = map.putIfAbsent(channel.id(), channel) == null;
        if (added) {
            channel.closeFuture().addListener(remover);
        }

        if (stayClosed && closed) {
            channel.close();
        }

        return added;
    }

You can see that in the add method, the channel is distinguished between server channel and non server channel. Then store it in the ConcurrentMap according to the channel id.

If the addition is successful, a closeFuture callback is added to the channel. When the channel is closed, the recover method will be called:

private final ChannelFutureListener remover = new ChannelFutureListener() {
        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
            remove(future.channel());
        }
    };

The recover method will remove the channel from serverChannels or nonServerChannels. This ensures that only channels in the open state are saved in the ChannelGroup.

Close both serverChannel and acceptedChannel

Although the bind method of ServerBootstrap will only return one channel, for the server, there can be multiple worker eventloopgroups, so the accepted Channel established after the connection between the client and the server is a subchannel of the server channel.

In other words, a server has one server channel and multiple accepted channel s.

If we want to close these channels at the same time, we can use the close method of ChannelGroup.

Because if the server channel and the non server channel are in the same ChannelGroup, all IO commands will be sent to the server channel first and then to the non server channel.

Therefore, we can add both server channels and non server channels into the same ChannelGroup. At the end of the program, we can uniformly call the close method of ChannelGroup to achieve this purpose:

   ChannelGroup allChannels =
           new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
  
   public static void main(String[] args) throws Exception {
       ServerBootstrap b = new ServerBootstrap(..);
       ...
       b.childHandler(new MyHandler());
  
       // Start server
       b.getPipeline().addLast("handler", new MyHandler());
       Channel serverChannel = b.bind(..).sync();
       allChannels.add(serverChannel);
  
       ... wait for shutdown instructions ...
  
       // Close serverChannel and all accepted connections
       allChannels.close().awaitUninterruptibly();
   }
  
   public class MyHandler extends ChannelInboundHandlerAdapter {
        @Override
       public void channelActive(ChannelHandlerContext ctx) {
           // Add accepted channel s to allChannels
           allChannels.add(ctx.channel());
           super.channelActive(ctx);
       }
   }

ChannelGroupFuture

In addition, like channel, the operation of channelGroup is asynchronous, and it will return a ChannelGroupFuture object.

Let's look at the definition of ChannelGroupFuture:

public interface ChannelGroupFuture extends Future<Void>, Iterable<ChannelFuture>

It is also one of the channels in the group Future traversal, and you can see that it is also one of the channels in the group Future traversal.

At the same time, ChannelGroupFuture provides methods such as isSuccess, ispartialsuccess and ispartialfailure to judge whether the command is partially successful.

ChannelGroupFuture also provides the addListener method to listen for specific events.

summary

channel is the core of netty. When multiple channels are inconvenient to manage, we can use channelGroup for unified management.

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

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 find!

Welcome to my official account: "those things in procedure", understand technology, know you better!

Keywords: Java Front-end Netty bootstrap

Added by mitzleah on Thu, 17 Feb 2022 23:43:14 +0200