The original mind map (xmind/ PDF/JPG) can pay attention to the public number: a flower is not romantic reply netty01 can be.

preface

NIO is already very elegant compared to traditional IO. Why do we use Netty?

The last article left a lot of holes in the end, talking about the drawbacks of NIO, which is also designed to introduce Netty. In this article, we will take a good look at the mystery of Netty.

The purpose of this article is very simple, I hope after reading you can understand Netty example code, for simple network communication, their own Netty handwriting a development application!

A simple Netty example

The following is a simple chat room Server side program, since the code reference: http://www.imooc.com/read/82/article/2166

The code is a bit long, but the core code is in the main() method, which I hope you can understand and will be dissected step by step later.

PS: I use the MAC system, directly enter Telnet 127.0.0.1 8007 in the terminal to start a chat box, if the message can not find the Telnet command, you can install through BREW, the specific steps please baidu.

/ * * *@DescriptionNetty easy chat room * *@AuthorA flower is not romantic *@Date 2020/8/10 6:52 上午
 */
public final class NettyChatServer {

    static final int PORT = Integer.parseInt(System.getProperty("port"."8007"));

    public static void main(String[] args) throws Exception {
        // 1. EventLoopGroup
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            // 2. Server boot
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            // 3. Set the line bootStrap information
            serverBootstrap.group(bossGroup, workerGroup)
                    // 4. Set the ServerSocketChannel type
                    .channel(NioServerSocketChannel.class)
                    // 5. Set parameters
                    .option(ChannelOption.SO_BACKLOG, 100)
                    // 6. Set the Handler for ServerSocketChannel. Only one Handler can be set
                    .handler(new LoggingHandler(LogLevel.INFO))
                    // 7. Set the Handler for SocketChannel
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            // Multiple child handlers can be added
                            p.addLast(new LoggingHandler(LogLevel.INFO));
                            p.addLast(newChatNettyHandler()); }});// 8. Bind ports
            ChannelFuture f = serverBootstrap.bind(PORT).sync();
            // 9. Wait for the server listening port to close, which blocks the main thread
            f.channel().closeFuture().sync();
        } finally {
            // 10. Gracefully close both thread poolsbossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }}private static class ChatNettyHandler extends SimpleChannelInboundHandler<ByteBuf> {
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            System.out.println("one conn active: " + ctx.channel());
            // The channel is placed in the EventLoopGroup within the ServerBootstrapAcceptor
            ChatHolder.join((SocketChannel) ctx.channel());
        }

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
            byte[] bytes = new byte[byteBuf.readableBytes()];
            byteBuf.readBytes(bytes);
            String content = new String(bytes, StandardCharsets.UTF_8);
            System.out.println(content);

            if (content.equals("quit\r\n")) {
                ctx.channel().close();
            } else{ ChatHolder.propagate((SocketChannel) ctx.channel(), content); }}@Override
        public void channelInactive(ChannelHandlerContext ctx) {
            System.out.println("one conn inactive: "+ ctx.channel()); ChatHolder.quit((SocketChannel) ctx.channel()); }}private static class ChatHolder {
        static final Map<SocketChannel, String> USER_MAP = new ConcurrentHashMap<>();

        /** * join a group chat */
        static void join(SocketChannel socketChannel) {
            // Assign an ID to someone who joins
            String userId = "User"+ ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE);
            send(socketChannel, Your ID is: + userId + "\n\r");

            for (SocketChannel channel : USER_MAP.keySet()) {
                send(channel, userId + "Joined the group chat." + "\n\r");
            }

            // Add the current user to the map
            USER_MAP.put(socketChannel, userId);
        }

        /** ** exit group chat */
        static void quit(SocketChannel socketChannel) {
            String userId = USER_MAP.get(socketChannel);
            send(socketChannel, "You dropped out of the group chat." + "\n\r");
            USER_MAP.remove(socketChannel);

            for (SocketChannel channel : USER_MAP.keySet()) {
                if(channel ! = socketChannel) { send(channel, userId +"Quit the group chat." + "\n\r"); }}}/** * spread the content of the speech */
        public static void propagate(SocketChannel socketChannel, String content) {
            String userId = USER_MAP.get(socketChannel);
            for (SocketChannel channel : USER_MAP.keySet()) {
                if(channel ! = socketChannel) { send(channel, userId +":"+ content); }}}/** * Send message */
        static void send(SocketChannel socketChannel, String msg) {
            try {
                ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
                ByteBuf writeBuffer = allocator.buffer(msg.getBytes().length);
                writeBuffer.writeCharSequence(msg, Charset.defaultCharset());
                socketChannel.writeAndFlush(writeBuffer);
            } catch(Exception e) { e.printStackTrace(); }}}}Copy the code

The code is a bit long, and the result is as shown in the figure above. The following content is all about how to read and write this code. I hope you can easily write Netty server code after reading it. Simple demo development allows you to experience the Netty implementation is much simpler than NIO, but the advantages are not limited to that, just need to know the choice of Netty.

Netty core components

The core components of Netty are:

  • Bootstrap && ServerBootstrap
  • EventLoopGroup
  • EventLoop
  • ByteBuf
  • Channel
  • ChannelHandler
  • ChannelFuture
  • ChannelPipeline
  • ChannelHandlerContext

The class diagram is as follows:

Bootstrap & ServerBootstrap

When you see BootStrap, you should think of BootStrap class and BootStrap class. Before analyzing the BootStrap class of EurekaServer project, you introduced EurekaBootstrap, which is used for context initialization and configuration initialization.

In Netty we have similar classes, Bootstrap and ServerBootstrap, which are Bootstrap classes for Netty programs. They are used to configure various parameters and start the entire Netty service.

ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)      
        .channel(NioServerSocketChannel.class)
        .option(ChannelOption.SO_BACKLOG, 100)
        .handler(new LoggingHandler(LogLevel.INFO))
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline p = ch.pipeline();
                p.addLast(new LoggingHandler(LogLevel.INFO));
                p.addLast(newChatNettyHandler()); }});Copy the code

Bootstrap and ServerBootstrap are two sets of Bootstrap classes defined for the Client and Server. The differences are as follows:

  • BootstrapIs the client boot class, andServerBootstrapIs a server side boot class.
  • BootstrapUsually useconnect()Method to connect to remote host and port as oneTCP client.
  • ServerBootstrapUsually usebind()Method binds a local port and waits for a client to connect.
  • ServerBootstrapCan handleAcceptThe event, this insidechildHandlerIs used to deal withChannelRequested. We can check it outchaildHandler()Method notes:

  • BootstrapClient boot requires only oneEventLoopGroupBut aServerBootstrapUsually two (aboveboosGroupandworkerGroup).

EventLoopGroup && EventLoop

The names of the EventLoopGroup and EventLoop classes are so oddly defined that they are often difficult for beginners to understand, including me.

An EventLoopGroup is a thread pool. For server applications, we bind two thread pools, one for Accept events and one for read and write events.

Using the class diagram above, we suddenly realize that it is a thread pool. (The name of the gas corner turned is really difficult to recognize)

An EventLoopGroup is a collection of eventloops. An EventLoopGroup contains one or more eventloops. We can think of Eventloops as worker threads in the EventLoopGroup thread pool.

As for the reason why two thread pools are used here, you can refer to the Reactor design model, and I won’t give too much explanation here.

  • An EventLoopGroup contains one or more Eventloops, that is, EventLoopGroup: EventLoop = 1: n
  • An EventLoop can be bound to only one Thread in its lifetime, EventLoop: Thread = 1:1
  • All I/O events that are handled by EventLoop are handled on their proprietary threads to ensure thread-safety, namely Thread: EventLoop = 1:1
  • A Channel can register with only one EventLoop in its lifetime, Channel: EventLoop = n: 1
  • An EventLoop can be assigned to one or more channels, i.e. EventLoop: Channel = 1: n

When a connection arrives, Netty creates a Channel and assigns an EventLoop from the EventLoopGroup to the Channel binding. The bound EventLoop serves the Channel throughout its life cycle.

ByteBuf

In Java NIO we have the ByteBuffer Buffer pool, and we should be impressed by its operation. When writing to the Buffer we need to pay attention to where the data is written, and when switching to read mode we need to switch to read/write state, otherwise we will have a big problem.

Netty provides ByteBuf as an alternative to NIO’s extremely difficult Buffer class. ByteBuf declares two Pointers: a read pointer and a write pointer to separate read and write operations and simplify the operation process of buffer.

Netty provides several implementations of ByteBuf for us to choose from. ByteBuf can be divided into:

  • PooledandUnpooledPooled and unpooled
  • Heap and Direct, Heap memory and off-heap memory, and Buffer created in NIO can also be specified
  • “Unsafe” means Safe and Unsafe

What are the options for creating buffers? Netty has taken care of it for us, so we can use it directly (Ntetty) :

ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
ByteBuf buffer = allocator.buffer(length);
Copy the code

Using this approach, Netty will do its best to create buffers for us using pooled, Unsafe, and external memory.

Channel

Mention the Channel is not strange, on an article about the NIO mentioned three major components, the most common is Java. NIO. SocketChannel and Java. NIO. ServerSocketChannel, they are used for nonblocking I / 0 operations. Similar to NIO’s channels, Netty provides its own Channel and subclass implementations for asynchronous I/0 operations and other related operations.

In Netty, a Channel is an abstraction of a Socket connection. It provides the user with information about the underlying Socket state (whether it is connected or disconnected) and operations such as reading and writing to the Socket. Each time Netty establishes a connection, there is a corresponding Channel instance. In addition, there is the concept of parent-child channel. The server connects to the listening channel, also called parent Channel. The channel that corresponds to each Socket connection, also called child Channel.

Since a channel is a network I/O read-write interface abstracted by Netty, there are several main reasons why JDK NIO native channels should be used instead:

  • JDK SocketChannelServersocketChannel There is no uniformChannelInterface for business developers to use, for a user, there is no unified view of operation, it is not convenient to use.
  • JDKSocketChannel andScrversockctChannel The primary responsibilities of the network ARE I/O operations as they are SPIClass interfaces, provided by specific virtual machine manufacturers, are implemented directly by inheriting SPI functionalityServersocketChannelSocketChannelTo expand its workload and re ChannelThe work classes are pretty much the same.
  • The NettyChannelPipeline ChannelIt needs to be integrated into Netty’s overall architecture, such as the I/O model, the base custom model, and TCP parameters configured based on metadata description JDK SocketChannelServersocketChannelNeither is provided and needs to be repackaged.
  • The custom ofChannelAnd work implementation is more flexible.

Based on the above four reasons, its design principle is relatively simple. Netty redesigned the Channel interface and gave many different implementations. But the functions are more complex, the main design concept is as follows:

  • inChannelInterface layer, associated with other operations encapsulated, usingFacadeMode Encapsulates network I/O operations and provides network I/ OS externally in a unified manner.
  • ChannelThe definition of the interface as large and complete as possible, unified view, by different subclasses to achieve different functions, common functions in the abstract parent class to achieve the maximum reuse of the interface.
  • The implementation uses aggregation rather than inclusion to aggregate related work classes inChannel byChannelUnified responsible for distribution and scheduling, more flexible function implementation.

There are many implementation classes of Channel, and the inheritance relationship is complex. From the perspective of learning, we extract the two most important NioServerSocketChannel and NioSocketChannel.

The NioServerSocketChannel inheritance class diagram is as follows:

The client NioSocketChannel inheritance class diagram is as follows:

The following article source code series will be specific analysis, here will not further elaborate on the analysis.

ChannelHandler

ChannelHandler is the most commonly used component in Netty. ChannelHandler is used to handle a wide range of events, such as connections, data receiving, exceptions, data conversion, etc.

ChannelHandler has two core subclasses ChannelInboundHandler and ChannelOutboundHandler, ChannelInboundHandler receives and processes Inbound data and events, while ChannelOutboundHandler receives and processes Outbound data and events.

ChannelInboundHandler

ChannelInboundHandler handles inbound data and various state changes, and some of the lifecycle methods in ChannelInboundHandler are called when a Channel state changes. These methods are closely related to Channel’s life.

Inbound data is the data that enters the socket. Here are some lifecycle apis for this interface:

When an implementation of a ChannelInboundHandler overrides the channelRead() method, it is responsible for explicitly freeing memory associated with pooled ByteBuf instances. ReferenceCountUtil Netty provides a practical method. The release ().

@Sharable
public class DiscardHandler extends ChannelInboundHandlerAdapter {
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) { ReferenceCountUtil.release(msg); }}Copy the code

This way is quite tedious, Netty provides a SimpleChannelInboundHandler, rewrite channelRead0 () method, which can automatically in the process of call release resources.

public class SimpleDiscardHandler
	extends SimpleChannelInboundHandler<Object> {
	@Override
	public void channelRead0(ChannelHandlerContext ctx, Object msg) {
			/ / don't have to call ReferenceCountUtil. Release (MSG) will release resources}}Copy the code

ChannelOutboundHandler

Outbound operations and data will be handled by the ChannelOutboundHandler. Its methods will be called by Channel, ChannelPipeline, and ChannelHandlerContext. A powerful feature of ChannelOutboundHandler is the ability to defer actions or events on demand, which makes it possible to handle requests in complex ways. For example, if writing to a remote node is suspended, you can postpone the flush operation and continue at a later time.

ChannelPromise vs. ChannelFuture: Most methods in ChannelOutboundHandler require a ChannelPromise parameter to be notified when the operation completes. ChannelPromise is a subclass of ChannelFuture that defines writable methods, such as setSuccess() and setFailure(), to make ChannelFuture immutable.

ChannelHandlerAdapter

The ChannelHandlerAdapter is, as its name implies, an adapter for the handler. You need to know what the adapter pattern is. Suppose we have an interface A, we need the subclass of A to implement the function, but we do not want to copy and paste the methods and properties of B, so we can write an adapter class Adpter to implement A. In this way, Adapter is A subclass of A and can use methods in B directly. This pattern is called Adapter pattern.

For example, Netty’s SslHandler class wants to use the ByteToMessageDecoder method to decode, but must be a ChannelHandler subclass object to be added to ChannelPipeline. With the following signature and implementation details (SslHandler implementation details are not covered), messages can be processed as a handler.

public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundHandler
Copy the code

ChannelHandlerAdapter provides utility methods isSharable() if the corresponding implementation is marked Sharable, this method returns true, indicating that it can be added to multiple ChannelPipelines. If you want to use these adapter classes in your own ChannelHandler, you can simply extend them and override the methods you want to customize.

ChannelPipeline

Each newly created Channel will be assigned a new Channel pipeline. The association is permanent; A Channel can neither attach another Channel pipeline nor detach its current one. This is a fixed operation in the life cycle of a Netty component and does not require any developer intervention.

Netty’s ChannelHandler provides the basic abstraction for the processor, and for now you can think of each instance of ChannelHandler as a callback being executed in response to a particular event. From an application developer’s point of view, it acts as an intercepting vehicle for all application logic that processes inbound and outbound data. The ChannelPipeline provides a container for the ChannelHandler chain and defines an API for propagating a stream of inbound and outbound events on that chain. When a Channel is created, it is automatically assigned to its own ChannelPipeline.

ChannelHandler is installed in the ChannelPipeline as follows:

  • aChannelInitializerThe implementation of theServerBootstrapIn the
  • whenChannelInitializer.initChannel()When the method is called,ChannelInitializerWill be inChannelPipeline To install a set of customChannelHandler
  • ChannelInitializerTake it from itselfChannelPipeline Remove the

This is a layout of a ChannelPipeline with both inbound and outbound channelhandlers, and confirms our previous statement that a ChannelPipeline consists primarily of a series of channelhandlers. ChannelPipeline also provides a way to propagate events through ChannelPipeline itself. If an inbound event is fired, it is propagated from the head of the ChannelPipeline to the end of the ChannelPipeline.

You might say that from the point of view of the event passing through the ChannelPipeline, the headers and tails of the ChannelPipeline depend on whether the event is inbound or outbound. However, Netty always uses ChannelPipeline’s inbound port (left side of the diagram) as the head and outbound port (right side of the diagram) as the tail. When you are finished adding the ChannelInboundHandler and the ChannelOutboundHandler to the channelPipeline.add *() method After ChannelPipeline, each ChannelHandler is positioned from head to tail in the same order as we just defined them. Therefore, if you number the handlers (ChannelHandler) in Figure 6-3 from left to right, the first ChannelHandler seen by the inbound event will be 1, and the first ChannelHandler seen by the outbound event will be 5.

As ChannelPipeline propagates an event, it tests whether the type of the next ChannelHandler in ChannelPipeline matches the direction of the event’s movement. If there is no match, the ChannelPipeline skips the ChannelHandler and proceeds to the next one until it finds a match for the expected direction of the event. (Of course, ChannelHandler can also implement both the ChannelInboundHandler interface and the ChannelOutboundHandler interface.)

Modify theChannelPipeline

Modifying refers to adding or removing ChannelHandler, as shown in the code example:

ChannelPipeline pipeline = .. ; FirstHandler firstHandler =new FirstHandler();
// Add a Handler to the ChannelPipeline
pipeline.addLast("handler1", firstHandler);
// This Handler is placed first, which means it is placed before handler1
pipeline.addFirst("handler2".new SecondHandler());
// This Handler is placed in last, meaning after handler1
pipeline.addLast("handler3".newThirdHandler()); .// Delete by name
pipeline.remove("handler3");
// Delete by object
pipeline.remove(firstHandler);
// The name "handler2" is replaced with the name "handler4", and the instance of handler2 is replaced with the instance of handler4
pipeline.replace("handler2"."handler4".new ForthHandler());
Copy the code

ChannelPipelineIn and out of stationAPI

The inbound API looks like this:

The outbound API shows:

Just remember these three points:

  • ChannelPipelineSaved andChannelThe associatedChannelHandler
  • ChannelPipeline It can be added or removed as neededChannelHandlerTo modify it dynamically
  • ChannelPipeline Have a wealth ofAPITo be called in response to inbound and outbound events

ChannelHandlerContext

When a ChannelHandler is added to a ChannelPipeline, it will be assigned a ChannelHandlerContext, which represents the binding between ChannelHandler and ChannelPipeline. The main function of the ChannelHandlerContext is to manage the interaction between the ChannelHandler it is associated with and other ChannelHandlers in the same ChannelPipeline.

If a method on a Channel or ChannelPipeline is called, it propagates along the entire ChannelPipeline, and if the same method on ChannelHandlerContext is called, it propagates from the corresponding current ChannelHandler.

The ChannelHandlerContext API is shown in the following table:

  • ChannelHandlerContextChannelHandlerThe association (binding) between them is never changed, so the cache’s reference to it is safe;
  • As explained at the beginning of this section, in contrast to other classes with methods of the same name,ChannelHandlerContextThe method will produce a shorter stream of events, and you should take advantage of this feature as much as possible for maximum performance.

withChannelHandler,ChannelPipelineAssociated use of

Access a channel from the ChannelHandlerContext

ChannelHandlerContext ctx = .. ;// Get a channel reference
Channel channel = ctx.channel();
// Write to buffer via channel
channel.write(Unpooled.copiedBuffer("Netty in Action",
CharsetUtil.UTF_8));
Copy the code

Access ChannelPipeline from ChannelHandlerContext

ChannelHandlerContext ctx = .. ;/ / get ChannelHandlerContext
ChannelPipeline pipeline = ctx.pipeline();
// Write buffer via ChannelPipeline
pipeline.write(Unpooled.copiedBuffer("Netty in Action",
CharsetUtil.UTF_8));
Copy the code

Sometimes we don’t want to pass data from scratch, we want to skip a few handlers and start with a handler. We must get the ChannelHandlerContext associated with the handler before the target handler.

ChannelHandlerContext ctx = .. ;// Write data directly to ChannelHandlerContext and send it to the next handler
ctx.write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));
Copy the code

Ok, ChannelHandlerContext basic use should be mastered, but do you really understand ChannelHandlerContext, the relationship between the ChannelPipeline and Channelhandler? It doesn’t matter if you don’t understand, because the source code will help you understand more deeply later.

Relationships between core components

  • aChannel Corresponds to aChannelPipeline
  • aChannelPipelineContains one two-wayChannelHandlerContext chain
  • aChannelHandlerContext Contains a ChannelHandler
  • aChannel It’s bound to oneEventLoopon
  • aNioEventLoopMaintained aThe Selector (I’m using a Java native Selector.)
  • aNioEventLoopThe equivalent of a thread

Sticking and unpacking problems

Sticky packet unpacking is a problem at the bottom of the network, which may occur at the data link layer, network layer and transmission layer. Our daily network application development is mostly carried out in the transport layer, because UDP has message protection boundary, there is no sticky packet unpacking problem, and therefore sticky packet unpacking problem only occurs in TCP protocol. Specifically speaking TCP is a “flow” protocol, only the concept of flow, no packet concept, for the specific meaning and boundary of the upper layer of the business data does not understand, it will only according to the actual situation of the TCP buffer packet division. Therefore, a complete packet may be divided into multiple packets by TCP for sending, or several small packets may be encapsulated into one large packet for sending. This is the so-called TCP sticky packet and unpacking problem.

Problem Illustration

The following are examples of situations that occur when the client sends two data tables Packet1 and Packet2 to the server:

(1) In the first case, the server normally receives two independent packets in two times, that is, the phenomenon of unpacking and sticky packets does not occur;

(2) In the second case, the receiver only receives one packet. Because TCP does not lose packets, this packet contains the information of two packets sent by the client. This phenomenon is called sticky packets. This situation is difficult for the service receiver to handle because the receiver does not know the boundary between the two packets.

(3) In the third case, the server reads two packets in two times. The first one reads part of the complete Packet1 and Packet2 packets, and the second one reads the rest of Packet2 packets, which is called TCP unpacking;

(4) In the fourth case, the server reads two packets in two times. The first one reads part of Packet1 content, and the second one reads the rest of Packet1 content and the whole packet of Packet2.

If the TCP receiving window of the server is very small and the packet Packet1 and Packet2 are relatively large, it is likely that the server needs to receive the two packets for several times, during which multiple packet unpacking occurs. The reasons behind the above examples are as follows:

  1. Unpacking occurs when the application writes data larger than the socket buffer size.
  2. Applications write data that is smaller than the socket buffer size. The network adapter sends data that has been written more than once to the network. Sticky packets occur.
  3. forMSS(Maximum packet length) Indicates the size of the packetTCPSection, whenTCPMessage length –TCPHead length >MSSUnpacking will occur when.
  4. The receiving method does not read socket buffer data in a timely manner, and sticky packets occur.

How to deal with sticky and unpacked packets based on Netty

TCP at the bottom layer cannot understand upper-layer service data, so packets cannot be split and reassembled. This problem can only be solved by upper-layer application protocol stack design. Based on the solutions of mainstream protocols in the industry, the solutions can be summarized as follows:

  1. The message length is fixed. For example, each packet has a fixed length of 200 bytes.
  2. Add a carriage return newline character to the end of the package for segmentation, for exampleFTPAgreement;
  3. A message is divided into a header and a body. The header contains a field that represents the total length of the message. It is usually designed to be used by the first field in the headerint32To represent the total length of the message;
  4. More complex application layer protocols.

The previous Netty example did not actually consider the half-packet read problem, which is usually fine in functional testing, but can occur if there are too many requests or large packets are sent. If the code is not considered, there will often be decoding mismatches or errors that will cause the program to not work properly. Here’s how Netty can help solve this problem by abstracting the implementation from the mainstream solution.

Netty uses frame sealing to find message boundaries, as shown in the following table:

way decoding coding
Fixed length FixedLengthFrameDecoder simple
The separator DelimiterBasedFrameDecoder simple
Specialized length field LengthFieldBasedFrameDecoder LengthFieldPrepender

Note that Netty provides decoders to solve corresponding problems. With these decoders, users do not need to manually decode the read packets themselves, nor do they need to consider TCP sticky and half-packet problems. Why do you say that? Here is an example of adding a delimiter to the end of a package:

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

import java.util.concurrent.atomic.AtomicInteger;

/ * * *@Author: wuxiaofei
 * @Date: 2020/8/150015 19:15 *@Version: 1.0
 * @Description: inbound processor */
@ChannelHandler.Sharable
public class DelimiterServerHandler extends ChannelInboundHandlerAdapter {

    private AtomicInteger counter = new AtomicInteger(0);
    private AtomicInteger completeCounter = new AtomicInteger(0);

    /*** The server reads the network data processing */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf in = (ByteBuf)msg;
        String request = in.toString(CharsetUtil.UTF_8);
        System.out.println("Server Accept["+request
                +"] and the counter is:"+counter.incrementAndGet());
        String resp = "Hello,"+request+". Welcome to Netty World!"
                + DelimiterEchoServer.DELIMITER_SYMBOL;
        ctx.writeAndFlush(Unpooled.copiedBuffer(resp.getBytes()));
    }

    /*** The server reads the network data after processing */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx)
            throws Exception {
        ctx.fireChannelReadComplete();
        System.out.println("the ReadComplete count is "
                +completeCounter.incrementAndGet());
    }

    /*** Handle the exception */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); }}Copy the code
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
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.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;

import java.net.InetSocketAddress;

/ * * *@Author: wuxiaofei
 * @Date: 2020/8/150015 19:17 *@Version: 1.0
 * @Description: Server */
public class DelimiterEchoServer {

    public static final String DELIMITER_SYMBOL = "@~";
    public static final int PORT = 9997;

    public static void main(String[] args) throws InterruptedException {
        DelimiterEchoServer delimiterEchoServer = new DelimiterEchoServer();
        System.out.println("Server about to start");
        delimiterEchoServer.start();
    }

    public void start(a) throws InterruptedException {
        final DelimiterServerHandler serverHandler = new DelimiterServerHandler();
        EventLoopGroup group = new NioEventLoopGroup();/* Thread group */
        try {
            ServerBootstrap b = new ServerBootstrap();/* Server startup must */
            b.group(group)/* Passes the thread group to */
                .channel(NioServerSocketChannel.class)/* Specify NIO for network transport */
                .localAddress(new InetSocketAddress(PORT))/* Specifies the server listening port */
                /* Each time the server receives a connection request, it initiates a new socket, that is, a channel, so this code adds handle*/ to the subchannel
                .childHandler(new ChannelInitializerImp());
            ChannelFuture f = b.bind().sync();/* Asynchronously bind to the server, sync() blocks until complete */
            System.out.println("Server startup complete, waiting for client connection and data.....");
            f.channel().closeFuture().sync();/* block until the server's channel is closed */
        } finally {
            group.shutdownGracefully().sync();/* Gracefully close the thread group */}}private static class ChannelInitializerImp extends ChannelInitializer<Channel> {

        @Override
        protected void initChannel(Channel ch) throws Exception {
            ByteBuf delimiter = Unpooled.copiedBuffer(DELIMITER_SYMBOL
                    .getBytes());
            / / the server after receiving the packet after DelimiterBasedFrameDecoder namely separator based framework decoders decoding for each packet with a delimiter.
            ch.pipeline().addLast( new DelimiterBasedFrameDecoder(1024,
                    delimiter));
            ch.pipeline().addLast(newDelimiterServerHandler()); }}}Copy the code

Added to ChannelPipeline DelimiterBasedFrameDecoder used to use separator at the end of the message automatically decoding, of course, no use FixedLengthFrameDecoder used for automatic message of fixed length decoding decoder, etc. As the door code use case, Netty provides several code can easily complete the automatic decoding of a lot of messages, and do not need to consider TCP sticky/unpack caused by reading half a packet problem, greatly improving the development efficiency.

Netty example code details

I believe that after reading the matting above, you have a certain understanding of Netty coding, the following to comb the whole again.

1, Set EventLoopGroup (Reactor thread group)

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

Netty uses a Reactor model, a bossGroup that accepts new connections from clients. The other workerGroup represents the thread group that handles the data receiving and receiving of each connection to handle the read and write events of the message.

2. Server boot

ServerBootstrap serverBootstrap = new ServerBootstrap();
Copy the code

Integrates all configurations to start the Netty server.

3. Set ServerBootstrap information

serverBootstrap.group(bossGroup, workerGroup);
Copy the code

Set two thread groups to ServerBootstrap.

4. Set the ServerSocketChannel type

serverBootstrap.channel(NioServerSocketChannel.class);
Copy the code

Set the IO types of channel, Netty than support Java NIO, IO also support block type, such as OIOOioServerSocketChannel. Class)

5. Set parameters

serverBootstrap.option(ChannelOption.SO_BACKLOG, 100);
Copy the code

There are a number of parameters that can be set with the option() method, where SO_BACKLOG identifies the length of the queue from which the server accepts connections. If the queue is full, the client connection will be rejected. The default value is 200 for Windows and 128 for others, where 100 is set.

6. Set Handler

serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));
Copy the code

Set the Handler for ServerSocketChannel. Only one Handler can be set. This Handler will be executed before SocketChannel is set up.

7. Set the child Handler

serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline p = ch.pipeline();
        p.addLast(new LoggingHandler(LogLevel.INFO));
        p.addLast(newChatNettyHandler()); }});Copy the code

Netty provides a way to set multiple handlers by using ChannelInitializer. A ChannelPipeline is a chain of responsibilities for Netty to handle requests. It is a linked list of channelhandlers that handle network requests.

For each channel, there is a pipeline of processors. Assemble the Child Channel pipeline and call the childHandler() method, passing an instance of ChannelInitializer.

When the Child Channel is successfully created and channel initialization begins, the ChannelInitializer instance configured in the Bootstrap initiator will be called.

At this point, the actual execution of the initChannel initialization method begins channel pipelining.

Pipeline assembly, mainly in the pipeline behind the addition of data reading and writing, processing business logic handler.

The ChannelHandler handler is used to handle network requests. There are ChannelInboundHandler and ChannelOutboundHandler. ChannlPipeline calls the ChannelInboundHandler sequentially from start to finish to process the network request content and from end to end to process the network request content

8. Bind port numbers

ChannelFuture f = serverBootstrap.bind(PORT).sync();
Copy the code

Bind port number

9. Wait until the server port number is closed

f.channel().closeFuture().sync();
Copy the code

Sync () blocks the main thread while waiting for the server listening port to close, internally calling Object’s wait() method

Disable the EventLoopGroup thread group

bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
Copy the code

conclusion

This article introduces Netty’s package structure, Reactor model, programming specification and so on from a demo. I hope you can read this demo and write it.

Behind began to continue Netty source code analysis part, please look forward to.

The resources

  1. Netty in Action books
  2. Moocs Netty column
  3. The Nuggets flash Netty book
  4. Taro source Netty column
  5. Github[fork from krcys]

Thanks to the Netty columnists for excellent content