Demystifying Netty - Quick start netty

As one of the most popular network communication frameworks today, netty, including middleware such as RPC and messages, is in use, so it is necessary to study it in depth. Let's start the analysis from the quick start of netty.

The startup of netty includes the startup of the server and the startup of the client. The main entrances are ServerBootstrap and Bootstrap. ServerBootstrap starts the server, and Bootstrap starts the client. The startup of the server is more complicated, and the startup of the client is relatively simple.

TCP handshake process

The startup process is actually the process of establishing a network connection, so first of all, we have to review the three-way handshake and four-way handshake of TCP.

The process of establishing a connection

 

process of disconnecting

1) What should I do if the client fails after the connection is established?

If the server fails, all connections will be passively disconnected, and the client can retry the connection at regular intervals.

If the client fails, the more connections there are, the greater the pressure on the server will be, so the server will not allow useless connections to occupy resources. This involves a detector (heartbeat design). The server will send heartbeat packets to the client regularly. If there is no response several times in a row, the connection is considered meaningless, and the server will actively disconnect the connection.

server start

The startup of the server starts with ServerBootstrap. This class is designed in a flow mode, which is convenient for configuring various information. Let’s look at a simple code as follows.

public static void main(String[] args) throws InterruptedException {
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        NioEventLoopGroup boss = new NioEventLoopGroup(2);
        NioEventLoopGroup worker = new NioEventLoopGroup(5);

        serverBootstrap.channel(NioServerSocketChannel.class)
                .group(boss, worker)
                .option(ChannelOption.SO_BACKLOG, 5)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new StringEncoder());

                    }
                });

        ChannelFuture future = serverBootstrap.bind(18080).sync();
        future.channel().closeFuture().addListener(new GenericFutureListener() {

            @Override
            public void operationComplete(Future future) throws Exception {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }).sync();


    }

In the process of starting the server, the core mainly does the following things:

1) Specify the channel type, which determines what type of channel the server creates.

2) Specify the thread group running on the server, including the boss thread group and worker thread group. Only when the thread group is specified, the subsequent server can work normally

3) Specify the network protocol configuration for parentChannel and childChannel

4) Specify the relevant channelHandler for the channel, which is the logical processing class used in the network IO read and write process, mainly to set the Handler of the childChannel.

5) Bind the port. This is the last step of starting the server. After the server binds the port, it waits for the client to establish a connection.

What is the main purpose of binding the port? We can find through the source code that when binding the port, it mainly does the following things:

1) Create a parentChannel --> initialize the configuration of the parentChannel, including the configuration of the network protocol, the configuration of the pipeline, etc. --> register the parentChannel, register the parentChannel to a thread in the boss thread group, associate it with the selector, and wait for the client to connect. From then on, the thread and selector will follow the parentChannel for a lifetime. The advantage of this is that there is no need to switch thread contexts, which improves server performance.

2) When a client request comes in, the pipeline associated with the parentChannel will have a corresponding channelHandler process, read the client's connection request, and generate a childChannel. At the same time, for the childChannel, go through the process of 1 again (creating channel, initializing the channel, and registering the channel).

To sum up, the whole startup process is actually done around the channel, mainly: create channel - initialize channel (including channelPipeline, channelHandler settings, when adding channelHandler to channelPipeline, it will automatically trigger handleAdded event) - register channel - doBind() - channelActive.

 

client start

The startup of the client is relatively simple. Also, let’s look at a simple code for starting the client.

public static void main(String[] args) throws InterruptedException {
        Bootstrap bootstrap = new Bootstrap();

        NioEventLoopGroup worker = new NioEventLoopGroup(5);

        bootstrap.channel(NioSocketChannel.class)
                .group(worker)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new StringEncoder());

                    }
                })
                .remoteAddress("127.0.0.1", 18080);

        ChannelFuture future = bootstrap.connect().sync();
        future.channel().closeFuture().addListener(new GenericFutureListener() {

            @Override
            public void operationComplete(Future future) throws Exception {
                worker.shutdownGracefully();
            }
        }).sync();


    }

It can be seen that the startup process of the client mainly includes:

1) Specify the channel type, which determines what type of channel is created when the client establishes a connection

2) Specify the thread group model for client communication

3) Specify channelHandler to handle IO during communication

4) Specify ChannelOption network protocol configuration

5) After everything is ready, you can start connecting to the server. After the connection is successful, the communication can start.

One thing to note here is that, regardless of whether it is the client or the server, the processing of the channel is similar. Create a channel, initialize a channel, and register a channel.

Decomposition next time

What does the concept of thread group in netty mean?

Why are there boss linear groups and worker thread groups? How are they divided?

What is the relationship between parentChannel and childChannel? What do they represent in netty?

What is the working mechanism of ChannelHandler? How does netty implement it?

What exactly is a channel in network programming? What is the relationship between channel, client, and server?

What does the network thread model, which claims to be the best of the best, look like? How is it implemented in netty?

How is the famous pipeline mode used everywhere in netty implemented and applied?

 

Guess you like

Origin blog.csdn.net/qq_42672856/article/details/115682937