A study of Netty

Posted by captain_scarlet87 on Mon, 20 Jan 2020 04:00:28 +0100

Introduction to Netty

1, Composition**

1.Bootstrap
Netty applications set the start of the bootstrap class, which provides a container for application network configuration

2.Channel
The underlying network transmission API must provide interfaces for I/O operations, such as read, write, connection, binding and waiting. The interface Channel in Netty defines the operation set of rich interaction of rain socket: bind, close, config, isActive, isOpen, isWritable, read, write, etc. It also provides a large number of Channel implementations

3.ChannelHandler
ChannelHandler supports many protocols and provides containers for data processing. We already know that ChannelHandler is triggered by a specific event. ChannelHandler can be dedicated to almost all actions, including turning an object into bytes (or vice versa) and exception handling thrown during execution.
A common interface is ChannelInboundHandler, which can handle application logic when receiving inbound events (including received data). When you need to provide a response, you can also scour the data from the channel inboundhandler. In a word, business logic often lives in one or more channel inboundhandlers.

4. Channelpipeline
ChannelPipeline provides a container to the ChannelHandler chain and an API to manage the flow of inbound and outbound events along the chain. Each Channel has its own ChannelPipeline, which is created automatically when the Channel is created. How is ChannelHandler installed in ChannelPipeline? It mainly implements the abstract ChannelInitializer of ChannelHandler. The ChannelInitializer subclass is registered through ServerBootstrap. When its method initChannel() is called, this object installs the custom ChannelHandler set to pipeline. When this is done, the ChannelInitializer subclass automatically removes itself from ChannelPipeline.

5.EventLoop
EventLoop is used to handle I/O operations of channels. A single EventLoop usually handles multiple Channel events. An EventLoopGroup can have more than one EventLoop and provide an iteration to act on the next in the retrieval list

6.ChannelFuture
All of Netty's I/O operations are asynchronous. Because an operation may not return immediately, we need a way to determine its result later. For this purpose, Netty provides the interface ChannelFuture. Its addListener method registers a ChannelFutureListener, which can be notified (whether successful or not) when the operation is completed.
More about ChannelFuture
Think of a ChannelFuture object as a placeholder for the results of future operations. When to execute depends on several factors, so it is impossible to predict and accurately. But we can be sure that it will be implemented. In addition, all operations returning ChannelFuture objects and belonging to the same Channel will be executed in the correct order after they are called.

An EventLoopGroup has one or more eventloops. Imagine the EventLoop working as a Thread for the Channel. (in fact, an EventLoop is bound to be a Thread for its life cycle.)
When creating a Channel, Netty registers the service life of the Channel (and also a separate Thread) through a separate EventLoop instance. That's why your application doesn't need to synchronize Netty's I/O operations; all Channel I/O is always performed with the same Thread.

Two, demo

Introduction of pom

<!-- netty package -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.43.Final</version>
        </dependency>

1. server side
Code to create an instance of ServerBootstrap (step 4). Since we use in NIO transmission, we have specified NioEventLoopGroup (3) to accept and process new connections, and NioServerSocketChannel (5) as the channel type. After that, we set the local address as InetSocketAddress and the selected port (6) as follows. The server will be bound to this address to listen for new connection requests.
Step 7 is the key: Here we use a special class, ChannelInitializer. When a new connection is accepted, a new subchannel will be created, and the ChannelInitializer will add an instance of our EchoServerHandler to the ChannelPipeline of the Channel. As we mentioned earlier, if there is inbound information, the processor will be notified.
Although NIO is extensible, its correct configuration is not simple. Especially for multithreading, it is not easy to deal with it correctly. Fortunately, Netty's design encapsulates most of the complexity, especially through abstraction, such as EventLoopGroup, SocketChannel, and ChannelInitializer.
In step 8, we bind the server and wait for the binding to complete. (the reason for calling sync() is that the current thread is blocked) the application in step 9 will wait for the server Channel to shut down (because we call sync() on the CloseFuture of the Channel). Now, we can close EventLoopGroup and release all resources, including all created threads (10).

Main code component of server:
Business logic implemented by EchoServerHandler
In the main() method, the server is booted

Perform the steps required for the latter:
Create a ServerBootstrap instance to boot the server and then bind it
Create and assign a NioEventLoopGroup instance to handle event processing, such as accepting new connections and read / write data.
Specify the local InetSocketAddress to bind to the server
Initialize each new Channel through the EchoServerHandler instance
Finally, call ServerBootstrap.bind() to bind the server.
In this way, the initialization of the server is completed and can be used.

 public class EchoServer {
 	private final int port;

    public EchoServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 8001; //1 set the port value (throw a NumberFormatException if the port parameter is not in the correct format)
        new EchoServer(port).start(); // 2 server start method
    }

    private void start() throws InterruptedException {
        NioEventLoopGroup group = new NioEventLoopGroup();  // 3 create EventLoopGroup

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap(); // 4 create ServerBootstrap
            serverBootstrap.group(group)
                    .channel(NioServerSocketChannel.class)  // 5 specify the transmission Channel using NIO
                    .localAddress(new InetSocketAddress(port))  // 6 port used to set socket address
                    .childHandler(new ChannelInitializer<SocketChannel>() { // 7 add a custom EchoServerHandler to the channel's ChannelPipeline
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new EchoServerHandler());
                        }
                    });
            ChannelFuture future = serverBootstrap.bind().sync(); // 8. The reason why sync waits for the server to shut down and calls sync() is that the current thread is blocked
            System.out.println(EchoServer.class.getName() + " started and listen on " + future.channel().localAddress());
            future.channel().closeFuture().sync();  // 9 turn off the channel and block until it is turned off
        } finally {
            group.shutdownGracefully().sync();  // 10 shut down EventLoopGroup, release all resources
        }
    }
 }

2. Server Handler
channelRead() - every inbound message calls
channelReadComplete() - called when the last channelread() is notified to the processor that it is the last message in the current batch
Exceptionfault() - called when an exception is caught during a read operation

@ChannelHandler.Sharable // Identify that such instances can be shared in the channel
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf in = (ByteBuf) msg;
        System.out.println("Server received:" + in.toString(CharsetUtil.UTF_8));  // Log message output to console
        ctx.write(in);  // Returning the received message to the sender has not flushed the data
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER) //Flush all pending messages to the remote node and close the channel before the operation is completed
                .addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace(); // Print exception stack trace
        ctx.close(); // Close channel
    }
}

3. client

Create a Bootstrap to initialize the client
An instance of NioEventLoopGroup is assigned to handle the event, which includes creating new connections and handling inbound and outbound data
Create an InetSocketAddress to connect to the server
When connecting to the server, an EchoClientHandler will be installed in the pipeline
Bootstrap.connect () is then called to connect to the remote - in this case, the echo server.

public class echoClient {
    private final String host;
    private final int port;

    public echoClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public static void main(String[] args) throws InterruptedException {
        final String host = "127.0.0.1";
        final int port = 8001;
        new echoClient(host, port).start();
    }

    private void start() throws InterruptedException {
        NioEventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap(); // 1 create client bootstrap
            bootstrap.group(group)  // 2. Specify NioEventLoopGroup to handle client events. Because NIO transport is used, all the implementations of NioEventLoopGroup are used
                    .channel(NioSocketChannel.class)  // 3 specify Channel type for NIO transmission
                    .remoteAddress(new InetSocketAddress(host, port))  // 4 specify the connection address of the server
                    .handler(new ChannelInitializer<SocketChannel>() { // 5 when creating a new connection and a new channel, create an instance of the EchoClientHandler in the pipeline channel of the single channel
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            ChannelFuture future = bootstrap.connect().sync();  // 6. Connect to remote server sync and wait for completion
            future.channel().closeFuture().sync(); // 7 blocked to channel off
        } finally {
            group.shutdownGracefully();  // 8 call shutdown gracefully to shut down the thread pool and release resources
        }

    }
}

4. Client Handler

@ChannelHandler.Sharable  // Mark this instance to be shared in the channel
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    @Override  // After the connection of the server is established, it is called.
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
    }

    @Override  // Call received from server after data
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
        System.out.println("Client received: "+byteBuf.toString(CharsetUtil.UTF_8));
    }


    @Override  //  Called when an exception is caught
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

3, Netty features

I. design
1. Unified interface for multiple transmission types - blocking and non blocking
2. Simple but more powerful thread model
3. Real connectionless datagram socket support
4. Link logic supports reuse

2, Ease of use
1. A large number of javadoc and code instances
2. In addition to the additional restrictions in JDK1.6 +. (some features are only supported in Java 1.7 +. )

Three, performance
1. Better throughput and lower latency than the core Java API
2. Less resource consumption, which benefits from shared pool and reuse
3. Reduce memory copy

4, Robustness
1. Eliminate OutOfMemoryError memory overflow caused by slow, fast, or overloaded connections
2. Eliminate the often found unfair read / write ratio in NIO applications in high-speed networks

Published 4 original articles, won praise 2, visited 490
Private letter follow

Topics: Netty socket network Java