Netty learning route

Netty learning route

I said I would write a blog about Netty for a long time. Now he's here, he's here!!!

Chapter 1 Introduction to Netty

1.1 overview of netty

1.1.1 introduction to netty

Explanation on the official website:

Netty is an asynchronous event driven network application framework for rapid development of maintainable high-performance protocol servers and clients. (Granny Wang boasts when she sells melons)

1.1.2 who is using Netty?

Large open source projects such as Dubbo, zk, RocketMQ, ElasticSearch, spring5 (implementation of HTTP protocol), GRPC and Spark are using Netty as the underlying communication framework

1.1.3 core concepts in netty

  1. Channel
    • Pipeline, which encapsulates the Socket and contains a set of API s, greatly simplifies the complexity of directly operating with the Socket
  2. EventLoopGroup
    • EventLoopGroup is an EventLoop pool that contains many eventloops
    • Netty assigns an EventLoop to each Channel to handle all events such as user connection requests and user requests. EventLoop itself is just a thread driver. Only one thread will be bound in its life cycle to process all IO events of a Channel.
    • Once a Channel is bound to an EventLoop, it cannot be changed in the whole life cycle of the Channel. An EventLoop can be bound to multiple channels. That is, the relationship between Channel and EventLoop is n:1, while the relationship between EventLoop and thread is 1:1.
  3. ServerBootStrap
    • It is used to configure the entire Netty code and associate various components. The server uses ServerBootStrap, while the client uses BootStrap. It's just a scaffold for rapid development..
  4. ChannelHandler and ChannelPipeline
    • ChannelHandler is the processor for data in the Channel. These processors can be codecs defined by the system itself or user-defined. These processors will be uniformly added to a ChannelPipeline object, and then the data in the Channel will be processed in the order of addition.
  5. ChannelFuture
    • All IO operations in Netty are asynchronous, that is, the operation will not get the return result immediately. Therefore, Netty defines a ChannelFuture object as the "spokesman" of the asynchronous operation, representing the asynchronous operation itself. If you want to get the return value of the asynchronous operation, you can add a listener for the asynchronous operation through the addListener() method of the asynchronous operation object and register a callback for it: call and execute immediately when the result comes out
    • Netty's asynchronous programming model is based on the concepts of Future and callback.

1.1.4 Netty execution process

1.2 small attempts

Purpose:

  • The purpose of this program is to understand the basic structure and process of Netty programming
  • The program processes Http requests through Netty, that is, it accepts Http and returns Http response.

1.2.1 create project

Create an ordinary Maven Java project.

1.2.2 import dependency

Just import one netty all dependency

<dependencies>
        <!--netty-all-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.65.Final</version>
        </dependency>
        <!--lombok rely on-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

1.2.3 define service startup class

public class SomeServer {
    public static void main(String[] args) throws InterruptedException {

        // It is used to process client connection requests and send the requests to eventLoop in the childGroup
        EventLoopGroup parentGroup = new NioEventLoopGroup();
        // Used to process client requests
        EventLoopGroup childGroup = new NioEventLoopGroup();

        try {
            // User starts ServerChannel
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(parentGroup, childGroup)  // Specify eventLoopGroup
                    .channel(NioServerSocketChannel.class)  // Specify communication using NIO
                    .childHandler(new SomeChannelInitializer());   // Specifies the processor to be processed by the thread bound by eventLoop in childGroup

            // Specifies the port number on which the current server listens
            // The execution of the bind() method is asynchronous
            // The sync() method will change the bind() operation and subsequent code execution from asynchronous to synchronous
            ChannelFuture future = bootstrap.bind(8888).sync();
            // System.out.println("the server starts successfully. The listening port number is 8888");
            // Close Channel
            // The execution of closeFuture() is asynchronous.
            // The closeFuture() method will not be executed until the Channel calls the close() method and closes successfully
            future.channel().closeFuture().sync();
        } finally {
            // Elegant close
            parentGroup.shutdownGracefully();
            childGroup.shutdownGracefully();
        }
    }
}

1.2.4 define server processor

/ Custom server processor
// Requirement: after submitting a request, the user will see hello netty world in the browser
public class SomeServerHandler extends ChannelInboundHandlerAdapter {

    /**
     *  When there is data from the client in the Channel, the execution of this method will be triggered
     * @param ctx  Context object
     * @param msg   Is the data from the client
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // System.out.println("msg = " + msg.getClass());
        // System.out.println("client address =" + ctx.channel().remoteAddress());

        if(msg instanceof HttpRequest) {
            HttpRequest request = (HttpRequest) msg;
            System.out.println("Request method:" + request.method().name());
            System.out.println("request URI: " + request.uri());

            if("/favicon.ico".equals(request.uri())) {
                System.out.println("Do not handle/favicon.ico request");
                return;
            }

            // Construct the response body of the response
            ByteBuf body = Unpooled.copiedBuffer("hello netty world", CharsetUtil.UTF_8);
            // Generate response object
            DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, body);
            // Initialize after obtaining the response header
            HttpHeaders headers = response.headers();
            headers.set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            headers.set(HttpHeaderNames.CONTENT_LENGTH, body.readableBytes());

            // Write response object to Channel
            // ctx.write(response);
            // ctx.flush();
            // ctx.writeAndFlush(response);
            ctx.writeAndFlush(response)
                    // Add a listener and close the Channel directly after sending the response body
                    .addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     *  The execution of this method will be triggered when an exception occurs in the processing of data in the Channel
     * @param ctx  context
     * @param cause  Exception object that occurred
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        // Close Channel
        ctx.close();
    }
}

1.3 socket

The previous project is a server with only HTTP requests on the server, and the most common in Netty is the Socket code of C/S architecture. So let's take a look at the Socket communication code of Netty.

1.3.1 create project

Create a normal Maven Java project

The function of this example is: after the client connects to the server, it will immediately send a data to the server. After receiving the data, the server will immediately restore a data to the client. Every time the client receives a data from the server, it will send another data to the server. Each time the server receives a data from the client, it will send another data to the client. So repeatedly. (maven dependency, ibid.)

1.3.2 define the server

  1. Define the server startup class

    // Define the server startup class
    public class SomeServer {
        public static void main(String[] args) throws InterruptedException {
            NioEventLoopGroup parentGroup = new NioEventLoopGroup();
            NioEventLoopGroup childGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap bootstrap = new ServerBootstrap();
                bootstrap.group(parentGroup, childGroup)
                         .channel(NioServerSocketChannel.class)
                        .childHandler(new ChannelInitializer<SocketChannel>() {
    
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ChannelPipeline pipeline = ch.pipeline();
                                // StringDecoder: String decoder, which decodes ByteBuf data in Channel into string
                                pipeline.addLast(new StringDecoder());
                                // StringEncoder: a String encoder that encodes a String as a ByteBuf to be sent to the Channel
                                pipeline.addLast(new StringEncoder());
                                pipeline.addLast(new SomeServerHandler());
                            }
                        });
                ChannelFuture future = bootstrap.bind(8888).sync();
                System.out.println("Server started");
                future.channel().closeFuture().sync();
            } finally {
                parentGroup.shutdownGracefully();
                childGroup.shutdownGracefully();
            }
        }
    }
    
    
    
  2. Define server processor

    public class SomeServerHandler extends ChannelInboundHandlerAdapter {
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            // Display the data from the client on the server console
            System.out.println(ctx.channel().remoteAddress() + "," + msg);
            // Send data to client
            ctx.channel().writeAndFlush("from server: " + UUID.randomUUID());
            TimeUnit.MILLISECONDS.sleep(500);
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }
    }
    

1.3.3 define client

  1. Define client startup class

    public class SomeClient {
        public static void main(String[] args) throws InterruptedException {
            NioEventLoopGroup group = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(group)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ChannelPipeline pipeline = ch.pipeline();
                                pipeline.addLast(new StringDecoder());
                                pipeline.addLast(new StringEncoder());
                                pipeline.addLast(new SomeClientHandler());
                            }
                        });
    
                ChannelFuture future = bootstrap.connect("localhost", 8888).sync();
                future.channel().closeFuture().sync();
            } finally {
                group.shutdownGracefully();
            }
        }
    }
    
    
  2. Define client processor

    public class SomeClientHandler extends SimpleChannelInboundHandler<String> {
    
        // msg's message type is consistent with the generic type in the class
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
            System.out.println(ctx.channel().remoteAddress() + "," + msg);
            ctx.channel().writeAndFlush("from client: " + LocalDateTime.now());
            TimeUnit.MILLISECONDS.sleep(500);
        }
    
        // When the Channel is activated, the execution of this method will be triggered
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            ctx.channel().writeAndFlush("from client: begin talking");
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }
    }
    
    

Chapter 2 unpacking and sticking of TCP

2.1 introduction to unpacking and sticking

  • In the network communication based on TCP protocol, Netty has unpacking and sticking packets. Unpacking and gluing occur simultaneously at the sender and receiver of data
  • Every time the sender sends a batch of binary packets through the network, the packet sent this time is called a Frame. During network transmission based on TCP, TCP protocol will split or reorganize the data that users really want to send according to the actual situation of the current cache, and turn it into a Frame for network transmission. In Netty, the data in ByteBuf is split or reorganized into binary frames. The receiver needs to reorganize or split the data in the received Frame and restore it to the ByteBuf data sent by the sender.
  • Specific scenario description
    • The ByteBuf sent by the sender is large. Before transmission, it will be split into multiple frames by the TCP bottom layer for transmission. This process is called sending and unpacking; The receiver needs to merge these frames after receiving them. This merging process is called receiver sticky package.
    • The sender's ByteBuf is too small to form a Frame. At this time, the TCP bottom layer will merge many such small bytebufs into a Frame for transmission. This merging process is called the sender's sticky packet; After receiving the Frame, the receiver needs to unpack and split multiple original small bytebufs. This splitting process is called receiver unpacking.
    • When a Frame cannot be put into multiple bytebufs, the last ByteBuf will be unpacked. Part of the ByteBuf is put into one Frame, and the other part is put into another Frame. This process is the sender unpacking. However, for the process of putting these bytebufs into a Frame, the sender sticks the packet; When the receiver receives two frames, the last part of the first Frame is connected with the second Frame

2.2 unpacking by the sender

2.3 sender sticky package

2.4 sticking and unpacking of the receiver

Netty encapsulates linebasedframedecoder, delimiterbasedframedecoder, fixedlengthframedecoder and lengthfieldbasedframedecoder classes to solve TCP packet sticking and unpacking problems. See the documentation for specific usage methods!!!

Stay longer!!

Tags: Java Netty NIO

Posted on Fri, 26 Nov 2021 05:02:24 -0500 by dhorn