Mind mapping

preface

This article mainly describes some features and important components of Netty framework, hope that after reading the Netty framework to have a more intuitive feeling, hope to help readers quickly start Netty, reduce some detments.

Overview of Netty

Official introduction:

Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.

Netty is an asynchronous event-driven network application framework for rapid development of maintainable high-performance protocol servers and clients.

Why use Netty

According to the official website, Netty is a network application framework, development server and client. A framework for network programming. Since it is network programming, Socket will not talk about, why not use NIO?

2.1 Shortcomings of NIO

I wrote a detailed introduction to NIO in an article called introduction to NIO. The main problems with NIO are:

  • NIO class library and API complex, high learning cost, you need to master Selector, ServerSocketChannel, SocketChannel, ByteBuffer and so on.
  • Familiarity with Java multithreaded programming is required. This is because NIO programming involves the Reactor model, and you need to be very familiar with multithreading and network programming to write high quality NIO programs.
  • The infamous epoll bug. It causes the Selector to poll for nothing, eventually causing the CPU to be 100%. Until JDK1.7, there was no fundamental solution.

2.2 Advantages of Netty

In contrast, Netty has many advantages:

  • The API is simple to use and cheap to learn.
  • Powerful, built in a variety of decoding encoder, support a variety of protocols.
  • High performance. Netty has the best performance compared to other mainstream NIO frameworks.
  • Active community, found bugs will be timely repair, iteration cycle is short, constantly add new features.
  • Dubbo and Elasticsearch both use Netty, and the quality is proven.

3. Architecture diagram

The diagram above is the architecture diagram on the homepage of the official website. Let’s analyze it from top to bottom.

The green Core modules include zero copy, API libraries, and an extensible event model.

Protocol Support Includes Http, webSocket, Secure Sockets Layer (SSL), Google Protobuf, Zlib /gzip compression and decompression, and Large File Transfer.

The red part of Transport Services, including Socket, Datagram, Http Tunnel, etc.

The above can be seen Netty function, protocol, transmission mode are more complete, more powerful.

Hello Word forever

First set up a HelloWord project, first familiar with the API, and pave the way for the later study. Based on the following picture:

4.1 Introducing Maven dependencies

The version used is 4.1.20, a relatively stable one.

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.20. The Final</version>
</dependency>
Copy the code

4.2 Creating a Server Startup Class

public class MyServer {
    public static void main(String[] args) throws Exception {
        // Create two thread groups boosGroup and workerGroup
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            // Create a server startup object and set the parameters
            ServerBootstrap bootstrap = new ServerBootstrap();
            // Set two thread groups boosGroup and workerGroup
            bootstrap.group(bossGroup, workerGroup)
                // Set the server channel implementation type
                .channel(NioServerSocketChannel.class)
                // Set the number of connections to the thread queue
                .option(ChannelOption.SO_BACKLOG, 128)
                // Set the connection to remain active
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                // Initializes the channel object with an anonymous inner class
                .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            // Set the handler for the pipeline
                            socketChannel.pipeline().addLast(newMyServerHandler()); }});// Set the handler for the pipe corresponding to the workerGroup EventLoop
            System.out.println("Java technology lovers' server is ready...");
            // Bind the port number to start the server
            ChannelFuture channelFuture = bootstrap.bind(6666).sync();
            // Listen for closed channels
            channelFuture.channel().closeFuture().sync();
        } finally{ bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }}}Copy the code

4.3 Creating a Server Processor

/** * Custom handlers need to inherit the Netty specified HandlerAdapter * to be associated with the Netty framework, similar to the SpringMVC adapter pattern **/
public class MyServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // Get the message sent by the client
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("Received client" + ctx.channel().remoteAddress() + "Message sent:" + byteBuf.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // Send messages to the client
        ctx.writeAndFlush(Unpooled.copiedBuffer("Has the server received the message and sent you a question mark?", CharsetUtil.UTF_8));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // An exception occurs and the channel is closedctx.close(); }}Copy the code

4.4 Creating a Client Startup Class

public class MyClient {

    public static void main(String[] args) throws Exception {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            // Create the bootstrap object and configure the parameters
            Bootstrap bootstrap = new Bootstrap();
            // Set the thread group
            bootstrap.group(eventExecutors)
                // Set the channel implementation type for the client
                .channel(NioSocketChannel.class)
                // Initialize the channel with an anonymous inner class
                .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            // Add a client channel handler
                            ch.pipeline().addLast(newMyClientHandler()); }}); System.out.println("Client is ready and ready to fly.");
            // Connect to the server
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1".6666).sync();
            // Listen for channel closure
            channelFuture.channel().closeFuture().sync();
        } finally {
            // Close the thread groupeventExecutors.shutdownGracefully(); }}}Copy the code

4.5 Creating a Client Processor

public class MyClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // Send a message to the server
        ctx.writeAndFlush(Unpooled.copiedBuffer("Are you good?", CharsetUtil.UTF_8));
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // Receives the message sent by the server
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("Received server" + ctx.channel().remoteAddress() + "The news:"+ byteBuf.toString(CharsetUtil.UTF_8)); }}Copy the code

4.6 test

Start the server first, then the client, and you can see the result:

MyServer prints the result:

MyClient prints the result:

5. Netty features and key components

5.1 taskQueue taskQueue

If the Handler Handler has some long-running business processing, it can be sent to taskQueue asynchronously. How to use it, see the code demo:

public class MyServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // Get the thread pool eventLoop, add thread, execute
        ctx.channel().eventLoop().execute(new Runnable() {
            @Override
            public void run(a) {
                try {
                    // Long time operation, so that the long time service operation will not cause Handler block
                    Thread.sleep(1000);
                    System.out.println("Long hours of business.");
                } catch(Exception e) { e.printStackTrace(); }}}); }}Copy the code

The taskQueue has a task added to it.

5.2 scheduleTaskQueue Indicates the queue of delayed tasks

The delayed task queue is very similar to the task queue described above, except that it can be delayed for a certain amount of time, as shown in the code demonstration:

ctx.channel().eventLoop().schedule(new Runnable() {
    @Override
    public void run(a) {
        try {
            // Long time operation, so that the long time service operation will not cause Handler block
            Thread.sleep(1000);
            System.out.println("Long hours of business.");
        } catch(Exception e) { e.printStackTrace(); }}},5, TimeUnit.SECONDS);// Execute after 5 seconds
Copy the code

Still open debug for debugging view, we can have a scheduleTaskQueue task waiting to execute

5.3 Future Asynchrony

When setting up the HelloWord project, we see a line like this:

ChannelFuture channelFuture = bootstrap.connect("127.0.0.1".6666);
Copy the code

Many operations return this ChannelFuture object. What is this ChannelFuture object used for?

ChannelFuture provides an asynchronous way to notify when an operation is complete. In Socket programming, waiting for the result of the response is blocked synchronously. Netty does not block because ChannelFuture fetches the result in an observer-like mode. Take a look at a code demonstration:

// Add a listener
channelFuture.addListener(new ChannelFutureListener() {
    // Use the anonymous inner class, ChannelFutureListener interface
    // Override the operationComplete method
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
        // Check whether the operation is successful
        if (future.isSuccess()) {
            System.out.println("Connection successful");
        } else {
            System.out.println("Connection failed"); }}});Copy the code

5.4 the Bootstrap and ServerBootStrap

Bootstrap and ServerBootStrap are Netty’s factory classes for creating client and server initiators. Using this factory class makes it very easy to create Bootstrap classes and, according to some of the examples above, can significantly reduce the difficulty of development. Let’s start with a class diagram:

It can be seen that both classes are derived from AbstractBootStrap abstract classes, so the configuration methods are generally the same.

Generally speaking, the steps for creating an initiator using Bootstrap can be divided into the following steps:

5.4.1 group ()

In the last article, The Reactor Model, we talked about the server using two thread groups:

  • BossGroup is used to listen for client connections and is responsible for creating connections with clients and registering them with the Selector of the workerGroup.
  • WorkerGroup is used to handle read and write events for each connection.

Creating a thread group is usually done with the following new:

EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
Copy the code

A little curious, since it is a thread group, what is the default number of threads? In-depth source code:

    // Use a constant
    private static final int DEFAULT_EVENT_LOOP_THREADS;

    static {
        / / NettyRuntime. AvailableProcessors () * 2, twice the number of CPU cores to constants
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

        if (logger.isDebugEnabled()) {
            logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS); }}protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        // If not passed, the constant value is used, which is twice the number of CPU cores
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }
Copy the code

As you can see from the source code, the default number of threads is twice the number of CPU cores. To imagine a custom thread count, we could use a parameterized constructor:

// Set the number of bossGroup threads to 1
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// Set the number of workerGroup threads to 16
EventLoopGroup workerGroup = new NioEventLoopGroup(16);
Copy the code

5.4.2 channel ()

This method is used to set the Channel type, and when the connection is established, the corresponding Channel instance is created based on this setting.

You can see this in debug mode

The channel types are as follows:

NioSocketChannel: asynchronous non-blocking client TCP Socket connection.

NioServerSocketChannel: asynchronous, non-blocking, server-side TCP Socket connection.

These two channel types are commonly used because they are asynchronous and non-blocking. So it’s the first choice.

OioSocketChannel: synchronizes blocked CLIENT TCP Socket connections.

OioServerSocketChannel: blocked TCP Socket connections on the server.

Slightly debugged locally, it works a little differently from Nio, blocking, so the API calls are different. Because it is blocking IO, few people will choose to use Oio, so it is hard to find examples. I thought about it a little, after several times of error, finally tune through. The code is as follows:

// The server code is almost the same as above, with only three changes
// This place uses OioEventLoopGroup
EventLoopGroup bossGroup = new OioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup)// You only need to set one thread group boosGroup
        .channel(OioServerSocketChannel.class)// Set the server channel implementation type

// Client-side code, just two changes
// Use OioEventLoopGroup
EventLoopGroup eventExecutors = new OioEventLoopGroup();
// Set the channel type to OioSocketChannel
bootstrap.group(eventExecutors)// Set the thread group
        .channel(OioSocketChannel.class)// Set the channel implementation type for the client
Copy the code

NioSctpChannel: Asynchronous client Stream Control Transmission Protocol (Sctp) connection.

NioSctpServerChannel: Asynchronous Sctp server connection.

Local startup is not successful, online read some netizens comments, it is only in the Linux environment can be started. SCTP is not supported on this platform. Because my computer is Windows system, so the netizen said some truth.

5.4.3 option () and childOption ()

First of all, the difference between the two.

Option () sets the connection that the server uses to receive it, namely the boosGroup thread.

ChildOption () is the connection that the parent pipe receives, that is, the workerGroup thread.

With that in mind, let’s take a look at some common Settings:

The SocketChannel argument, which is the usual argument for childOption() :

SO_RCVBUF Socket specifies the size of the TCP data receiving buffer. TCP_NODELAY TCP parameter: sends data immediately. The default value is true. The SO_KEEPALIVE Socket parameter is used to keep the connection alive. The default value is False. When this function is enabled, TCP proactively detects the validity of idle connections.

ServerSocketChannel option()

The SO_BACKLOG Socket parameter indicates the length of the queue for the server to accept connections. If the queue is full, the client connection will be rejected. The default value is 200 for Windows and 128 for others.

Due to space restrictions, other will not list, we can go online to find information to see, understand.

5.4.4 Setting the Pipeline (key)

ChannelPipeline is Netty’s chain of responsibility for handling requests, and ChannelHandler is the handler for handling requests. Each channel actually has a pipeline of processors.

In Bootstrap, the childHandler() method needs to initialize the channel and instantiate a ChannelInitializer. In this case, you need to override the initChannel() method to initialize the channel. This is where the assembly process takes place. The code is shown as follows:

// Initializes the channel object with an anonymous inner class
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
	@Override
	protected void initChannel(SocketChannel socketChannel) throws Exception {
		// Set a custom handler for the pipeline pipeline
		socketChannel.pipeline().addLast(newMyServerHandler()); }});Copy the code

There are two main types of Handler:

ChannelInboundHandlerAdapter (inbound processor), ChannelOutboundHandler (outbound processor)

Inbound refers to the Channel of data from the underlying Java NIO Channel to Netty.

Outbound refers to the operation of the underlying Java NIO Channel through Netty’s Channel.

ChannelInboundHandlerAdapter processor common events are:

  1. Register event fireChannelRegistered.

  2. Connection establishment event fireChannelActive.

  3. Read events and read completion events fireChannelRead and fireChannelReadComplete.

  4. Abnormal notification event fireExceptionCaught.

  5. User-defined event fireUserEventTriggered.

  6. The Channel can be written fireChannelWritabilityChanged state change events.

  7. Connection closing event fireChannelInactive.

ChannelOutboundHandler Common events of the processor are:

  1. Bind the port.

  2. Connect to server Connect.

  3. Write event Write.

  4. Flush time Flush.

  5. Read event read.

  6. Disconnect actively.

  7. Close the channel event close.

A similar handler() is used to assemble parent channels, also known as bossGroup threads. In most cases, you don’t use this method.

5.4.5 bind ()

Provides binding server address and port number for server or client, default is asynchronous startup. It is synchronized if the sync() method is added.

There are five overloaded methods with the same name that bind address port numbers. I won’t introduce you one by one.

5.4.6 Gracefully closing the EventLoopGroup

// Release all resources, including the created thread
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
Copy the code

Will close all Child Channels. When closed, the underlying resources are freed.

5.5 the Channel

What is a Channel? Take a look at the official documentation:

A nexus to a network socket or a component which is capable of I/O operations such as read, write, connect, and bind

A component connected to a network socket or capable of performing I/O operations such as read, write, connect, and bind.

If the above statement is abstract, here is another:

A channel provides a user:

the current state of the channel (e.g. is it open? is it connected?) , the configuration parameters of the channel (e.g. receive buffer size), the I/O operations that the channel supports (e.g. read, write, connect, and bind), and the ChannelPipeline which handles all I/O events and requests associated with the channel.

Translation:

Channel provides users with:

  1. The current state of the channel (e.g., is it open? Or connected?

  2. Configuration parameters for a channel (such as the size of the receive buffer)

  3. The IO operations supported by a channel (such as read, write, connect, and bind) and the Channel pipeline that handles all IO events and requests associated with a channel.

5.5.1 Obtaining the Channel status

boolean isOpen(a); // Returns true if the channel is open
boolean isRegistered(a);// Return true if the channel is registered with EventLoop
boolean isActive(a);// Returns true if the channel is active and connected
boolean isWritable(a);// Returns true if and only if the I/O thread will execute the requested write immediately.
Copy the code

That’s how you get the four states of a channel.

5.5.2 Obtaining Channel Configuration Parameters

Get a single configuration message using getOption().

ChannelConfig config = channel.config();// Get configuration parameters
// Get the channeloption. SO_BACKLOG argument,
Integer soBackLogConfig = config.getOption(ChannelOption.SO_BACKLOG);
// soBackLogConfig=128 because my launcher is configured with 128
Copy the code

Get multiple configurations, use getOptions(), code demo:

ChannelConfig config = channel.config(); Map<ChannelOption<? >, Object> options = config.getOptions();for(Map.Entry<ChannelOption<? >, Object> entry : options.entrySet()) { System.out.println(entry.getKey() +":" + entry.getValue());
}
/** SO_REUSEADDR : false WRITE_BUFFER_LOW_WATER_MARK : 32768 WRITE_BUFFER_WATER_MARK : WriteBufferWaterMark(low: 32768, high: 65536) SO_BACKLOG: 128 * /
Copy the code

5.5.3 I/O operations supported by channel

Write operations, which demonstrate writing messages from the server to the client:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
	ctx.channel().writeAndFlush(Unpooled.copiedBuffer("This wave, this wave is meat, egg, scallion chicken.", CharsetUtil.UTF_8));
}
Copy the code

Client console:

// Received server /127.0.0.1:6666 message: this wave ah, this wave is meat egg chicken ~
Copy the code

Connection operation, code demonstration:

ChannelFuture connect = channelFuture.channel().connect(new InetSocketAddress("127.0.0.1".6666));// Initiators are usually used
Copy the code

Obtain channel pipeline through channel and do relevant processing:

// Get the ChannelPipeline object
ChannelPipeline pipeline = ctx.channel().pipeline();
// Add the ChannelHandler handler to the pipeline
pipeline.addLast(new MyServerHandler());
Copy the code

5.6 the Selector

In NioEventLoop, there’s a member variable selector, which is a selector in the NIO package, and I talked about selector earlier in Getting Started with NIO.

A Netty Selector is the same as a NIO Selector, which is used to listen for events, manage channels registered in the Selector, and implement multiplexers.

5.7 PiPeline and ChannelPipeline

In the previous section, we learned that a Channel can be loaded with a ChannelHandler handler. A Channel cannot have only one ChannelHandler handler. Since there are many ChannelHandlers working on a pipeline, there must be a sequence.

Thus came pipelines, which act as containers for processors. When initializing a channel, install the channelHandler in the pipeline in order to execute channelHandler sequentially.

Within a Channel, there is only one Channel pipeline. The pipeline is created when the Channel is created. ChannelPipeline contains a list of ChannelHanders, and all ChannelHandlers are registered with ChannelPipeline.

5.8 ChannelHandlerContext

In Netty, the Handler Handler is defined by us and is implemented by integrating the inbound or outbound Handler as described above. If we want to get a Pipeline object or a channel object in Handler, how do we get it?

So Netty designed this ChannelHandlerContext object, you can get channel, pipeline and other objects, you can read and write operations.

Through the class diagram, ChannelHandlerContext is an interface with three implementation classes.

The ChannelHandlerContext is actually a linked list in the pipeline. Look at a piece of source code to understand:

ChannelPipeline implements the constructor method of DefaultChannelPipeline
protected DefaultChannelPipeline(Channel channel) {
	this.channel = ObjectUtil.checkNotNull(channel, "channel");
	succeededFuture = new SucceededChannelFuture(channel, null);
	voidPromise =  new VoidChannelPromise(channel, true);
	// set head to head and tail to tail
	tail = new TailContext(this);
	head = new HeadContext(this);
	
	head.next = tail;
	tail.prev = head;
}
Copy the code

Here’s a picture to make it a little clearer:

5.9 EventLoopGroup

Let’s take a look at the EventLoopGroup class diagram:

This includes the commonly used implementation class NioEventLoopGroup. The OioEventLoopGroup was also used in the previous example.

From the Netty architecture diagram, you can see that the server needs two thread groups to work together, and the interface of this thread group is the EventLoopGroup.

Each EventLoopGroup contains one or more Eventloops, and each EventLoop maintains a Selector instance.

5.9.1 Implementation principles of the polling mechanism

We might as well see a DefaultEventExecutorChooserFactory source code:

private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;

@Override
public EventExecutor next(a) {
    //idx.getAndIncrement() equals idX ++, then modulo the task length
    return executors[idx.getAndIncrement() & executors.length - 1];
}
Copy the code

This code can determine the execution mode is polling mechanism, next debug debugging:

It also has a judgment here, if the number of threads is not 2 to the N, then use the modulus algorithm to achieve.

@Override
public EventExecutor next(a) {
    return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}
Copy the code

Write in the last

For details, see Netty API Documentation

It’s not easy. Give it a thumbs up if you think it helps.

I don’t want next time certain, hope this time certain quality three even, thank you!

If you want to see my updated article for the first time, you can search the official account on wechat.Java technology enthusiast“,Refusing to be a salt fish, I’m a programmer trying to be remembered. See you next time!!

Ability is limited, if there is any mistake or improper place, please criticize and correct, study together!