02-VIP-Netty Core Functions and Thread Model Explained

First exploration of Netty

NIO's class library and API are complicated and troublesome to use: you need to be proficient in Selector, ServerSocketChannel, SocketChannel, ByteBuffer, etc.

The development workload and difficulty are very large: for example, the client is facing disconnection and reconnection, network flash interruption, heartbeat processing, half-packet reading and writing, network congestion and abnormal flow processing, etc.

Netty has well encapsulated the NIO API that comes with the JDK to solve the above problems. And Netty has the advantages of high performance, higher throughput, lower latency, reduced resource consumption, and minimized unnecessary memory copying.

Netty is currently using 4.x, 5.x version is obsolete, Netty 4.x requires JDK 6 or higher version support

Netty usage scenarios:

1) Internet industry: In a distributed system, remote service calls are required between various nodes, and a high-performance RPC framework is essential. As an asynchronous high-performance communication framework, Netty is often used by these RPC frameworks as a basic communication component. Typical applications are: Ali's distributed service framework Dubbo's RPC framework uses the Dubbo protocol for inter-node communication, and the Dubbo protocol uses Netty as the basic communication component by default for implementation. Internal communication between process nodes. The bottom layer of Rocketmq also uses Netty as the basic communication component.

2) Game industry: Whether it is a mobile game server or a large-scale online game, the Java language has been more and more widely used. As a high-performance basic communication component, Netty itself provides TCP/UDP and HTTP protocol stacks.

3) Big data field: Classic Hadoop's high-performance communication and serialization component Avro's RPC framework uses Netty for cross-border communication by default, and its Netty Service is based on the Netty framework for secondary encapsulation.

Netty related open source projects: https://netty.io/wiki/related-projects.html

Netty communication example

Netty's maven dependency:

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.35.Final</version>
        </dependency>

Server code:

package com.tuling.netty.base;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer {

    public static void main(String[] args) throws Exception {
        //创建两个线程组bossGroup和workerGroup, 含有的子线程NioEventLoop的个数默认为cpu核数的两倍
        // bossGroup只是处理连接请求 ,真正的和客户端业务处理,会交给workerGroup完成
        EventLoopGroup bossGroup = new NioEventLoopGroup(10);
        EventLoopGroup workerGroup = new NioEventLoopGroup(100000);
        try {
            //创建服务器端的启动对象
            ServerBootstrap bootstrap = new ServerBootstrap();
            //使用链式编程来配置参数
            bootstrap.group(bossGroup, workerGroup) //设置两个线程组
                    .channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作为服务器的通道实现
                    // 初始化服务器连接队列大小,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。
                    // 多个客户端同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() {//创建通道初始化对象,设置初始化参数

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //对workerGroup的SocketChannel设置处理器
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            System.out.println("netty server start。。");
            //绑定一个端口并且同步, 生成了一个ChannelFuture异步对象,通过isDone()等方法可以判断异步事件的执行情况
            //启动服务器(并绑定端口),bind是异步操作,sync方法是等待异步操作执行完毕
            ChannelFuture cf = bootstrap.bind(9000).sync();
            //给cf注册监听器,监听我们关心的事件
            /*cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (cf.isSuccess()) {
                        System.out.println("监听端口9000成功");
                    } else {
                        System.out.println("监听端口9000失败");
                    }
                }
            });*/
            //对通道关闭进行监听,closeFuture是异步操作,监听通道关闭
            // 通过sync方法同步等待通道关闭处理完毕,这里会阻塞等待通道关闭完成
            cf.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

Client code:

package com.tuling.netty.base;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class NettyClient {
    public static void main(String[] args) throws Exception {
        //客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //创建客户端启动对象
            //注意客户端使用的不是ServerBootstrap而是Bootstrap
            Bootstrap bootstrap = new Bootstrap();
            //设置相关参数
            bootstrap.group(group) //设置线程组
                    .channel(NioSocketChannel.class) // 使用NioSocketChannel作为客户端的通道实现
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //加入处理器
                            ch.pipeline().addLast(new NettyClientHandler());
                        }
                    });

            System.out.println("netty client start。。");
            //启动客户端去连接服务器端
            ChannelFuture cf = bootstrap.connect("127.0.0.1", 9000).sync();
            //对通道关闭进行监听
            cf.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

After reading the code, we found that the goal of the Netty framework is to separate your business logic from the basic network application coding, so that you can focus on business development without writing a lot of NIO-like network processing operations.

Netty threading model

You can first understand some of the IO processing modes mentioned in the article "Scalable IO in Java". Netty's threading model is shown in the following figure:

Model explanation:

1) Netty abstracts two groups of thread pools, BossGroup and WorkerGroup, BossGroup is responsible for receiving client connections, and WorkerGroup is responsible for network read and write

2) Both BossGroup and WorkerGroup types are NioEventLoopGroup

3) NioEventLoopGroup is equivalent to an event loop thread group, this group contains multiple event loop threads, each event loop thread is NioEventLoop

4) Each NioEventLoop has a selector, which is used to monitor the network communication of the socketChannel registered on it

5) There are 3 steps in each Boss NioEventLoop thread internal loop execution

  • Process the accept event, establish a connection with the client, and generate a NioSocketChannel
  • Register NioSocketChannel to the selector on a worker NIOEventLoop
  • Process the tasks of the task queue, namely runAllTasks

6) The steps executed by each worker NIOEventLoop thread in a loop

  • Polling the read and write events of all NioSocketChannels registered on its selector
  • Process I/O events, namely read and write events, and process business in the corresponding NioSocketChannel
  • runAllTasks handles the tasks of the task queue TaskQueue, and some time-consuming business processing can generally be put into the TaskQueue to be processed slowly, so as not to affect the flow of data in the pipeline.

7) When each worker NIOEventLoop processes the NioSocketChannel business, it will use the pipeline (pipeline), and many handler processors are maintained in the pipeline to process the data in the channel

Netty module components

【Bootstrap、ServerBootstrap】:

Bootstrap means boot. A Netty application usually starts with a Bootstrap. The main function is to configure the entire Netty program and connect various components. The Bootstrap class in Netty is the startup boot class of the client program, and ServerBootstrap is the server startup boot class.

【Future、ChannelFuture】:

As mentioned earlier, all IO operations in Netty are asynchronous, and it is not immediately known whether the message is processed correctly.

But you can wait for it to finish executing or register a listener directly. The specific implementation is through Future and ChannelFutures. They can register a listener. When the operation is executed successfully or fails, the listener will automatically trigger the registered listener event.

【Channel】:

A component of Netty network communication that can be used to perform network I/O operations. Channel provides users with:

1) The status of the current network connection channel (for example, is it open? Is it connected?)

2) The configuration parameters of the network connection (such as the size of the receiving buffer)

3) Provide asynchronous network I/O operations (such as establishing a connection, reading and writing, and binding ports). Asynchronous calls mean that any I/O call will return immediately, and the requested I/O at the end of the call is not guaranteed The operation has been completed.

4) The call immediately returns a ChannelFuture instance. By registering the listener to the ChannelFuture, the caller can be called back to notify the caller when the I/O operation succeeds, fails, or is cancelled.

5) Support associated I/O operations and corresponding processing procedures.

Connections of different protocols and different blocking types have different channel types corresponding to them.

The following are some commonly used Channel types:

NioSocketChannel, asynchronous client TCP Socket connection. NioServerSocketChannel, asynchronous server-side TCP Socket connection. NioDatagramChannel, asynchronous UDP connection. NioSctpChannel, asynchronous client Sctp connection. NioSctpServerChannel, asynchronous Sctp server-side connection. These channels cover UDP and TCP network IO and file IO.

【Selector】:

Netty implements I/O multiplexing based on the Selector object. Through the Selector, one thread can monitor the Channel events of multiple connections.

When a Channel is registered to a Selector, the internal mechanism of the Selector can automatically and continuously query (Select) whether these registered Channels have ready I/O events (such as readable, writable, network connection complete, etc.), so The program can simply use one thread to efficiently manage multiple Channels.

【NioEventLoop】 :

A thread and task queue are maintained in NioEventLoop, which supports asynchronous submission and execution of tasks. When the thread starts, the run method of NioEventLoop is called to perform I/O tasks and non-I/O tasks:

I/O tasks, that is, the ready events in selectionKey, such as accept, connect, read, write, etc., are triggered by the processSelectedKeys method.

Non-IO tasks, tasks added to taskQueue, such as register0, bind0 and other tasks, are triggered by the runAllTasks method.

【NioEventLoopGroup】:

NioEventLoopGroup, which mainly manages the life cycle of eventLoop, can be understood as a thread pool. A set of threads is maintained internally. Each thread (NioEventLoop) is responsible for processing events on multiple Channels, and a Channel corresponds to only one thread.

【ChannelHandler】:

ChannelHandler is an interface that processes I/O events or intercepts I/O operations and forwards it to the next handler in its ChannelPipeline (business processing chain).

ChannelHandler itself does not provide many methods, because this interface has many methods that need to be implemented, and it can be inherited from its subclasses during convenient use:

ChannelInboundHandler is used to handle inbound I/O events. ChannelOutboundHandler is used to handle outbound I/O operations.

Or use the following adapter class:

ChannelInboundHandlerAdapter is used to handle inbound I/O events. ChannelOutboundHandlerAdapter is used to handle outbound I/O operations.

【ChannelHandlerContext】:

Save all the context information related to the Channel and associate a ChannelHandler object at the same time.

【ChannelPipline】:

Save the List of ChannelHandler, which is used to process or intercept Channel's inbound events and outbound operations.

ChannelPipeline implements an advanced form of interception filter mode, allowing users to fully control how events are handled and how each ChannelHandler in the Channel interacts with each other.

In Netty, each Channel has and only one ChannelPipeline corresponding to it, and their composition relationship is as follows: 

A Channel contains a ChannelPipeline, and ChannelPipeline maintains a doubly linked list composed of ChannelHandlerContext, and each ChannelHandlerContext is associated with a ChannelHandler.

The read event (inbound event) and the write event (outbound event) are in a doubly linked list. The inbound event will be passed from the head of the linked list to the last inbound handler, and the outbound event will be passed from the tail to the front of the linked list. The first outbound handler, the two types of handlers do not interfere with each other.

ByteBuf detailed

Structurally, ByteBuf consists of a string of byte arrays. Each byte in the array is used to store information.

ByteBuf provides two indexes, one for reading data and one for writing data. These two indexes move in the byte array to locate the position where information needs to be read or written.

When reading from ByteBuf, its readerIndex (read index) will increase according to the number of bytes read.

Similarly, when writing ByteBuf, its writerIndex will also increase according to the number of bytes written.

It should be noted that the limit is that readerIndex just reads the place where writerIndex writes.

If readerIndex exceeds writerIndex, Netty will throw IndexOutOf-BoundsException.

Sample code:

package com.tuling.netty.base;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;

public class NettyByteBuf {
    public static void main(String[] args) {
        // 创建byteBuf对象,该对象内部包含一个字节数组byte[10]
        // 通过readerindex和writerIndex和capacity,将buffer分成三个区域
        // 已经读取的区域:[0,readerindex)
        // 可读取的区域:[readerindex,writerIndex)
        // 可写的区域: [writerIndex,capacity)
        ByteBuf byteBuf = Unpooled.buffer(10);
        System.out.println("byteBuf=" + byteBuf);

        for (int i = 0; i < 8; i++) {
            byteBuf.writeByte(i);
        }
        System.out.println("byteBuf=" + byteBuf);

        for (int i = 0; i < 5; i++) {
            System.out.println(byteBuf.getByte(i));
        }
        System.out.println("byteBuf=" + byteBuf);

        for (int i = 0; i < 5; i++) {
            System.out.println(byteBuf.readByte());
        }
        System.out.println("byteBuf=" + byteBuf);


        //用Unpooled工具类创建ByteBuf
        ByteBuf byteBuf2 = Unpooled.copiedBuffer("hello,zhuge!", CharsetUtil.UTF_8);
        //使用相关的方法
        if (byteBuf2.hasArray()) {
            byte[] content = byteBuf2.array();
            //将 content 转成字符串
            System.out.println(new String(content, CharsetUtil.UTF_8));
            System.out.println("byteBuf=" + byteBuf2);

            System.out.println(byteBuf2.readerIndex()); // 0
            System.out.println(byteBuf2.writerIndex()); // 12
            System.out.println(byteBuf2.capacity()); // 36

            System.out.println(byteBuf2.getByte(0)); // 获取数组0这个位置的字符h的ascii码,h=104

            int len = byteBuf2.readableBytes(); //可读的字节数  12
            System.out.println("len=" + len);

            //使用for取出各个字节
            for (int i = 0; i < len; i++) {
                System.out.println((char) byteBuf2.getByte(i));
            }

            //范围读取
            System.out.println(byteBuf2.getCharSequence(0, 6, CharsetUtil.UTF_8));
            System.out.println(byteBuf2.getCharSequence(6, 6, CharsetUtil.UTF_8));
        }
    }
}

Netty actual combat chat room system

Tencent classroom chat window is a chat room

See sample code com.tuling.netty.chat package

Guess you like

Origin blog.csdn.net/nmjhehe/article/details/114740115