This is the 20th day of my participation in the November Gwen Challenge. Check out the details: The last Gwen Challenge 2021

Starting with the previous blog about NIO network programming, I’m going to introduce you to Netty.

What is a Netty

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

  • Event-driven means that the underlying implementation uses a multiplexing technique (selector) and only needs to process events as they occur
  • Asynchronous refers to the use of multiple threads to complete method calls and processing results separated, not asynchronous IO

Hello World

To learn a technology or framework, start with Hello World and work your way through it. This code can be seen as Netty’s Hello World. It is divided into two parts: the server side and the client side

The service side

public class HelloServer {
    public static void main(String[] args) {
        // 1. The server initiator is responsible for assembling the netty components below and starting the server
        new ServerBootstrap()
                // create a NioEventLoopGroup
                .group(new NioEventLoopGroup())
                // select the ServerSocketChannel implementation for the server
                .channel(NioServerSocketChannel.class)
                // Child (work) handles reads and writes. This method determines which operations the child(work) performs (handler)
                // ChannelInitializer processor (run only once)
                // 5. A channel is used to read and write to the client after the SocketChannel connection is established. InitChannel initialization is performed to add other handlers
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {/ / add the handler
                        // add a specific handler
                        nioSocketChannel.pipeline().addLast(new StringDecoder());// Use StringDecoder to decode, ByteBuf=>String

                        nioSocketChannel.pipeline().addLast(new SimpleChannelInboundHandler<String>() {// Create a custom handler that uses the results of the previous handler
                            @Override
                            protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception 							{
                                System.out.println(s);// Prints the string converted in the previous step}}); }// 7. Bind ServerSocketChannel to port 8080
                }).bind(8080); }}Copy the code

Here’s a look at the code:

  1. The first to usenew ServerBootstrap()Open a server side initiator that assembles the following Nettty components and starts the server
  2. group(new NioEventLoopGroup())To create an EventLoopGroup, you can think of it as a combination of Selector and thread pool
  3. channel(NioServerSocketChannel.class)Select the ServerSocketChannel implementation for the server
  4. childHandler(new ChannelInitializer<NioSocketChannel>()The child in worker is responsible for handling read and write events. This method determines which operations the child performs (handler).
  5. A channel is used to read and write data to the client after the SocketChannel connection is established. The initChannel initialization is performed and other handlers are added
  6. initChannel(NioSocketChannel nioSocketChannel)Method is used to add a specific handler, which is used bynioSocketChannel.pipeline().addLastThe first handler is added to the code to decode the data sent by the client into a String. The second handler is added to the custom handler, and this handler needs to use the result of the previous decoding handler.
  7. bind(8080)Finally, use the bind method to bind the port number

The client

public class HelloClient {
    public static void main(String[] args) throws InterruptedException {
        // start class
        new Bootstrap()
                // add EventLoop
                .group(new NioEventLoopGroup())
                // 3, select the client Socket implementation class. NioSocketChannel represents the client implementation based on NIO
                .channel(NioSocketChannel.class)
                // 4. Add the ChannelInitializer processor (run this command only once).
                // After the SocketChannel connection is established, initChannel is executed to add more processors
                .handler(new ChannelInitializer<NioSocketChannel>() {// The initializer is called after the connection is established, and the following initChannel is executed
                    @Override
                    protected void initChannel(NioSocketChannel channel) throws Exception {
                        // The message is processed by the channel handler, which in this case emits String => ByteBuf encoding
                        channel.pipeline().addLast(newStringEncoder()); }})// Specify the server and port to connect to
                .connect(new InetSocketAddress("localhost".8080))
                // Many methods in Netty are asynchronous, such as connect
                // Use sync to wait for the connection to complete
                .sync()
                // Get the channel object, which is the channel abstraction and can be read and written
                .channel()
                // Write the message and clear the buffer. Regardless of whether the data is sent or received, the handle goes to the internal method of the processor
                .writeAndFlush("hello world");// Convert the string to bytebuf}}Copy the code

As you can see, the client code is similar to the server code. The following is an interpretation of the client code:

  1. new Bootstrap()Start a client
  2. group(new NioEventLoopGroup())Open an event loop group to which you can add handlers
  3. channel(NioSocketChannel.class)Select the client Socket implementation class, NIO SocketChannel represents the NIO based client implementation
  4. handler(new ChannelInitializer<NioSocketChannel>()Add the ChannelInitializer processor, which is used to provide SocketChannel for clientsEstablish a connectionExecute initChannel to add more processors
  5. connect(new InetSocketAddress("localhost", 8080))Specify the server and port to connect to
  6. sync()Block waits for connect to complete
  7. channel()Gets a Channel object, which is a channel abstraction that can read and write data
  8. writeAndFlush("hello world")The message is written and the buffer is emptied. The handle calls the method inside the processor, regardless of whether the data is sent or received

Execute the process

The execution order differs from the code order in the following points:

  1. After initializing the ChannelInitializer processor, the server performs the bind method in the last line to bind the port number and waits for the client to connect
  2. After initializing ChannelInitializer, the client will execute the connect method to connect to the server. The connection will be blocked until the connection succeeds. After the connection succeeds, the client will execute the initChannel method above to add the handler
  3. The client takes the channel connection object and sends the data “Hello world”, then adds a handler that converts the String into a ByteBuf (similar to a ByteBuffer) and sends it to the server
  4. After detecting a read event, the server enables an EventLoop in the EventLoopGroup to process the read event and invokes a specific handler for processing

Components to explain

  • An eventLoop can be understood as a worker processing data

    • EventLoop can manage IO operations for multiple channels, and once An eventLoop is responsible for a channel, it binds it to the channel. (The channel sends data using Worker 1, and then the channel receives data, The eventLoop is responsible for all the IO operations in the channel. This is for thread safety and prevents message overwriting
    • An eventLoop can perform IO operations and process tasks. Each eventLoop has its own task queue, which can store multiple channels of pending tasks
    • EventLoop processes the data according to the handler’s plan (code) in pipeline order, and you can specify a different eventLoop for each handler
  • Handler refers to the process of processing data

    • A pipeline is responsible for publishing events (read, read done…). Propagated to each handler, which handles the event it is interested in (rewriting the corresponding event handling method).

    • A pipeline has multiple handlers, each of which is called in turn during processing

    • There are Inbound and Outbound handlers

      • Inbound Indicates a write
      • Outbound Outbound, write
  • A channel is a data channel

  • MSG is understood as flowing data. At first, the input is ByteBuf, but after processing by various handlers in pipeline, it will be changed into other types of objects, and finally the output becomes ByteBuf