Simple use and understanding of Netty

Posted by brynjar on Wed, 05 Jan 2022 12:11:46 +0100

Netty study notes

1. Introduction and application scenario of netty

1.1 INTRODUCTION

  1. Netty is an open source framework for jboss
  2. Netty is an asynchronous, event driven network application framework
  3. Based on nio

1.2 application scenarios

  1. Rpc e.g. dubbo
  2. game
  3. big data

Applications involving network communication can use netty

2. i/o model

2.1 introduction

  1. bio synchronizes and blocks a connection, corresponding to a server thread. It is applicable to the architecture with less connections jdk1 four
  2. nio synchronous non blocking server processes multiple connections in one thread, which is suitable for a large number of connections and a short connection time jdk1 four
  3. aio(nio.2) asynchronous non blocking is applicable to jdk1 seven

2.2 bio

blocking i/o

2.2.1 simple demo

Develop a server and create a thread pool. When the client sends a request, the server will create a thread for processing. When there are multiple client requests, multiple threads will be created for processing

Here, the demo client is simulated with telnet

public static void handler(Socket socket){
        try(InputStream in = socket.getInputStream();){
            System.out.println("Thread information: id "+Thread.currentThread().getId()+" name " + Thread.currentThread().getName());

            byte[] bytes = new byte[1024];
            while (true){
                int read = in.read(bytes);
                if(read!=-1){
                    System.out.println("Output information: "+new String(bytes,"UTF-8"));
                }else {
                    break;
                }
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try(ServerSocket serverSocket = new ServerSocket(6666);) {
            ExecutorService executorService = Executors.newCachedThreadPool();

            System.out.println("Thread information: id "+Thread.currentThread().getId()+" name " + Thread.currentThread().getName());

            while (true){
                System.out.println("Wait for link");
                final Socket socket = serverSocket.accept();
                System.out.println("Link to a client");

                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("Thread information: id "+Thread.currentThread().getId()+" name " + Thread.currentThread().getName());
                        handler(socket);
                    }
                });
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

2.3 nio

Non blocking I / O non blocking

2.3.1 introduction

Three cores

  1. channel
  2. Buffer buffer
  3. Selector selector

Briefly describe the operating principle: the selector selects the available channels. Channels and buffers can read and write to each other. The application does not directly operate the channel, but indirectly operates the channel by operating the buffer

There will be multiple selectors in a thread. Multiple channel s can be registered in a selector. If there is no data transmission, the thread can do other things and will not wait all the time

2.4 difference between NiO and bio

  1. nio non blocking bio blocking
  2. nio processes io in block mode, and bio processes io in stream mode, which is faster than stream mode
  3. bio is based on byte stream / character stream, nio is based on buffer and channel selector to listen for events of multiple channels, so one thread can process data of multiple channels

Illustration: nio

bio

3. nio detailed explanation

3.1 relationship among three components of NiO model

  1. A thread corresponds to a selector
  2. One selecor corresponds to multiple channel s
  3. A channel corresponds to a buffer
  4. One thread corresponds to multiple channel s
  5. Both channel and buffer are bidirectional, that is, they can be read or written. Use the flip() method to switch
  6. buffer is a memory block, which is faster to read and write memory
  7. The selector switches different channel s according to different events

3.2 Buffer buffer

3.2.1 introduction

It is essentially a memory block for reading and writing data, which can be understood as a container object (array) that provides methods for operating memory blocks

Some mechanisms are built in the buffer, which can detect the data change and state change of the buffer

The data read and written in the channel must pass through the Buffer

3.2.2 source code analysis

Several common operation methods

public static void main(String[] args) {
        //allocate specifies the length of the intbuffer
        IntBuffer buffer = IntBuffer.allocate(5);

        //capacity() get capacity
        //put() write
        for(int i = 0;i<buffer.capacity();i++){
            buffer.put(i*2);
        }

        //flip() reverses from write to read
        buffer.flip();

        //read
        //get() moves the index back one bit after each read
        for(int i = 0;i<buffer.capacity();i++){
            System.out.println(buffer.get());

        }
    }

3.2.2.1 definitions

An int array is defined in IntBuffer, which is similar to other types of buffers

public abstract class IntBuffer
    extends Buffer
    implements Comparable<IntBuffer>
{

    // These fields are declared here rather than in Heap-X-Buffer in order to
    // reduce the number of virtual method invocations needed to access these
    // values, which is especially costly when coding small buffers.
    //
    final int[] hb;                  // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;                 // Valid only for heap buffers

Four attributes are defined in the Buffer class at the top level

public abstract class Buffer {

    /**
     * The characteristics of Spliterators that traverse and split elements
     * maintained in Buffers.
     */
    static final int SPLITERATOR_CHARACTERISTICS =
        Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;

    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1; //sign
    private int position = 0; //The position of the current index cannot exceed the limit
    private int limit;//Maximum length that can be read and written
    private int capacity;//Length of capacity allocate definition

3.2.2.2 reverse rotation

   public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

You can see that after the reversal, it changes from read to write, or from write to read

Set the index to 0. The maximum read-write length cannot exceed the index of the last operation

3.2 channel

3.2.1 introduction

  1. Channels are similar to streams / connections, but streams can only be written or read, and channels can be read and written
  2. Channel asynchronous read / write data
  3. The channel can read and write data to the cache

3.2.2 hierarchical relationship

When a client sends a request, the server will create a ServerSocketChannel (implementation class: ServerSocketChannelImpl), and then the ServerSocketChannel will create a SocketChannel (implementation class: SocketChannelImpl is the channel that really reads and writes data). This SocketChannel corresponds to the client request

3.2.3 case analysis

3.2.3.1 filechanle output file stream

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @author: zhangyao
 * @create:2020-08-25 14:50
 **/
public class FileChannelTest {
    public static void main(String[] args) {
        FileOutputStream fileOutputStream = null;
        try {
            //File output stream
            fileOutputStream = new FileOutputStream("D:\\file01.txt");
			//The file output stream is wrapped as FileChannel, where FileChannel implements FileChannelImpl by default
            FileChannel fileChannel = fileOutputStream.getChannel();

            //Create corresponding buffer
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

            //Data write buffer
            byteBuffer.put("hello nio".getBytes());

            //Reverse, because you need to read data from the buffer and write it to the Channel
            byteBuffer.flip();

            //Write Channel from buffer
            fileChannel.write(byteBuffer);



        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //Close file stream
            if(fileOutputStream!=null){
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

The overall process is to write the data into the buffer, write the data into the Channel in the read buffer, and output the data from the file output stream

The illustration is as follows

3.2.3.2 filechannel input file stream

public static void main(String[] args) {
    FileInputStream fileInputStream = null;
    try {
        fileInputStream = new FileInputStream("D:\\file01.txt");

        //Get Channel
        FileChannel channel = fileInputStream.getChannel();

        //Create byteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        //Read data from channel and write to buffer
        channel.read(byteBuffer);

        //In the next step, you need to read the data output from the buffer
        byteBuffer.flip();

        //output
        byte[] array = byteBuffer.array();
        System.out.println(new String(array));

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            fileInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Contrary to the above example, the data is read from the file, written to the buffer buffer through the channel, and output

Illustration

3.2.3.3 FileChannel copy files

In fact, it is the combination of the above two examples to copy the data in one file to another

public static void main(String[] args) {
    FileInputStream fileInputStream = null;
    FileOutputStream fileOutputStream = null;

    try {
        fileInputStream = new FileInputStream("D:\\file01.txt");
        fileOutputStream = new FileOutputStream("D:\\file02.txt");
        FileChannel channel = fileInputStream.getChannel();

        FileChannel channel1 = fileOutputStream.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        while (true){
            //Reset byteBuffer
            byteBuffer.clear();
            int read = channel.read(byteBuffer);
            if(read==-1){
                break;
            }

            byteBuffer.flip();
            channel1.write(byteBuffer);
        }


    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            fileInputStream.close();
            fileOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}

ByteBuffer is used here Clear() method

Because the ByteBuffer buffer has a length, when the read file exceeds the length of the buffer, if the buffer is not emptied, the next read will start from the last read position, resulting in an endless loop

3.2.3.4 TransferFrom of filechannel copied files

public static void main(String[] args) {
    FileInputStream fileInputStream = null;
    FileOutputStream fileOutputStream = null;

    try {
        fileInputStream = new FileInputStream("D:\\file01.txt");
        fileOutputStream = new FileOutputStream("D:\\file02.txt");
        FileChannel channel = fileInputStream.getChannel();

        FileChannel channel1 = fileOutputStream.getChannel();

	
        //Copy from channel channel to channel1 channel
        channel1.transferFrom(channel, 0, channel.size());

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            fileInputStream.close();
            fileOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}

3.2.4 Buffer dispersion and aggregation

The above examples use a single buffer to read and write data. If the data is too large, you can also use multiple buffers (buffer arrays) to read and write data, that is, use space for time

3.3 Selector

3.3.1 basic introduction

One selector manages multiple channel channels and processes io in an asynchronous manner

Only when reading and writing really happen, data will be processed, which reduces the pressure of threads and eliminates the need to maintain a thread for each request

It avoids the overhead caused by context switching between multiple threads

3.3.2 api of selector

Selector:

  1. select() blocking
  2. select(Long timeout) has a timeout
  3. selectNow() non blocking
  4. wakeup() wakes up the selector immediately

3.3.2 selecor workflow

In fact, it is the working principle of selector selectionkey serversocketchannel sockcetchannel

  1. When the client is linked, get the SocketChannel through ServerSockertChannel and register it with the Selector

    1. Registration source code
    public abstract SelectionKey register(Selector sel, int ops)
        throws ClosedChannelException;
    

    This is the method that SocketChannel registers on the Selector. The first parameter is the Selector object to be registered, and the second parameter is the event driven type

    public abstract class SelectionKey {
        public static final int OP_READ = 1;
        public static final int OP_WRITE = 4;
        public static final int OP_CONNECT = 8;
        public static final int OP_ACCEPT = 16;
        private volatile Object attachment = null;
    
  2. After registration, a selectionKey will be returned, which will be associated with SocketChannel

  3. The Selector listens to the Channel through the select method. If an event occurs, it returns the corresponding selectionKey collection

    1. Source code

      public int select(long var1) throws IOException {
              if (var1 < 0L) {
                  throw new IllegalArgumentException("Negative timeout");
              } else {
                  return this.lockAndDoSelect(var1 == 0L ? -1L : var1);
              }
          }
      
          public int select() throws IOException {
              return this.select(0L);
          }
      
          public int selectNow() throws IOException {
              return this.lockAndDoSelect(0L);
          }
      
      	private int lockAndDoSelect(long var1) throws IOException {
              synchronized(this) {
                  if (!this.isOpen()) {
                      throw new ClosedSelectorException();
                  } else {
                      int var10000;
                      synchronized(this.publicKeys) {
                          synchronized(this.publicSelectedKeys) {
                              var10000 = this.doSelect(var1);
                          }
                      }
      
                      return var10000;
                  }
              }
          }
      
  4. The Channel can be obtained inversely through the obtained selectionKey

    1. Source code

          public abstract SelectableChannel channel();
      
  5. Finally, the service is processed through the channel

3.3.3 cases

Service side idea:

  1. Create serverSocketChannel binding port 6666 and register this channel with the Selector. The registered event is OP_ACCEPT
  2. Loop listening to determine whether an event occurs in the channel. If an event occurs, judge different event types for different links and read / write operations

Client ideas

  1. Create a SocketChannel. After connecting to the server, send messages and keep the link closed

3.3.3.1 server side

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * @author: zhangyao
 * @create:2020-08-26 16:55
 **/
public class ServerChannel {
    public static void main(String[] args) {

        try {
            //Generate a ServerScoketChannel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //Set to non blocking
            serverSocketChannel.configureBlocking(false);
            //serverSocket listens on port 6666
            serverSocketChannel.socket().bind(new InetSocketAddress(6666));

            //Create Selector
            Selector selector = Selector.open();

            //serverSocketChannel registers with the Selector
            SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);


            //Loop wait link
            while (true){
                //If no event occurs, the loop continues
                if(selector.select(1000) == 0){
                    System.out.println("Wait 1 s,No connection");
                    continue;
                }

                //If you have event driven, you need to traverse events
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    //If the event is a connection
                    if(key.isAcceptable()){
                        try {
                            SocketChannel channel = serverSocketChannel.accept();
                            channel.configureBlocking(false);
                            SelectionKey register = channel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                            System.out.println("Link successful");
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    //If reading data
                    if(key.isReadable()){
                        SocketChannel channel = (SocketChannel) key.channel();
                        ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
                        try {
                            int read = channel.read(byteBuffer);
                            byte[] array = byteBuffer.array();
                            System.out.println("Read data:"+ new String(byteBuffer.array()));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    iterator.remove();


                };

            }

        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}

3.3.3.2 client

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * @author: zhangyao
 * @create:2020-08-26 17:24
 **/
public class ClientChannel {
    public static void main(String[] args) {

        //Create a SocketChannel
        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
            if(!socketChannel.connect(inetSocketAddress)){
                while (!socketChannel.finishConnect()){
                    System.out.println("Server connection in progress,Threads are not blocked,Other operations can be performed");
                }
            }

            //Connection succeeded
            socketChannel.write(ByteBuffer.wrap("hello ,server".getBytes()));

            System.in.read();




        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.Netty

4.1 introduction

https://netty.io/ Official website

netty is the encapsulation of java nio api, which simplifies the development of nio program. The minimum requirement of jdk is 1.6

The popular network programming communication framework, Dubbo Elasticsearch and other frameworks, and the underlying network communication framework are Netty

Architecture model

edition

netty has 3 x 4. x 5. X three major versions

3.x older, 5 X has a major bug and was abandoned by the official website. Now it mainly uses 4 x

4.2 thread model

There are several Threading Models

4.2.1 traditional I/O blocking model

Each link needs a corresponding thread to process, and after the link is established, if the current link has no data transmission, this thread will be blocked in the read() method

4.2.2 Reactor mode

The principle diagram is shown above

This paper mainly improves the problem that a connection will block a thread in the traditional I/O model. When the connection is established, it will be processed by calling the thread in the thread pool through ServiceHandler. In this way, only one ServiceHandler thread will be blocked to achieve the purpose of multiplexing

There are three ways to implement Reactor mode

4.2.2.1 single Reactor single thread

Use one thread to multiplex all operations, including read-write connections

redis uses this model, single thread

4.2.2.2 single Reactor multithreading

Compared with single Reactor and single thread, the main thread does not conduct business processing. When a request comes, the specific business processing is handed over to the thread in the thread pool for processing. After the thread processing is completed, it is returned to the Client through the handler

4.2.2.3 master slave Reactor multithreading

Compared with single Reacotr, the master-slave Reactor divides the Reactor into main Reactor and SubReactor

MainReactor is responsible for distribution and connection

SubReactor is responsible for reading and writing

One MainReactor can correspond to multiple subreactors

4.2.3 Netty model

Briefly describe Netty model

  1. role

    1. Bossgroup the type of bossgroup is NioEventLoopGroup, which contains many nioeventloops
    2. NioEventLoop nio event loop. Each NioEventLoop has a Selctor and a task queue
    3. The type of WorkerGroup is NioEventLoopGroup, which is similar to BossGroup, but with different functions. BossGroup is only responsible for establishing a connection with the client. WorkerGroup needs to read and write and handle business
    4. The PipeLine encapsulates the Channel. The specific business processing is to process the Channel through the PipeLine
  2. Specific process

    1. When the client sends a request, it first enters the BossGroup. NioEventLoop polls the request for events. If it is a connection event, it will be processed
      1. The processing steps are divided into three steps
      2. polling
      3. Registration here refers to registering the generated SocketChannel with a Selector in a NioEventLoop in the workerGroup
      4. Execute task list
    2. When the requested event is read-write, the worker group performs specific business processing on the request
      1. The processing steps are similar to those of BossGroup
  3. summary

    It can be seen that Netty's model is similar to the master-slave Reactor model. A master Reactor is responsible for connecting events and a slave Reactor is responsible for reading and writing events

4.2.4 case demo

4.2.4.1 server

4.2.4.1.1 NettyServer
package netty;

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;

/**
 * @author: zhangyao
 * @create:2020-09-03 08:55
 **/
public class NettyServer {

    public static void main(String[] args) {

        //Create BossGroup and WorkerGroup
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        ChannelFuture channelFuture = null;
        try {
        serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<SocketChannel>() {

                    @Override
                    protected void initChannel(SocketChannel socketChannel) {
                        socketChannel.pipeline().addLast(new NettyServerHandler());
                    }
                });

        System.out.println("Server ready.....");


        //Binding port
            channelFuture = serverBootstrap.bind(6668).sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                channelFuture.channel().closeFuture().sync();

                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

4.2.4.1.2 NettyServerHandler
package netty;

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

/**
 * @author: zhangyao
 * @create:2020-09-03 09:12
 **/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    //Read data
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;

        System.out.println("Client send message:"+ buf.toString(CharsetUtil.UTF_8));
        System.out.println("Client address:"+ ctx.channel().remoteAddress());

    }

    //Data read complete
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello client",CharsetUtil.UTF_8));
    }

    //Handle exception close ctx
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

4.2.4.2 client

4.2.4.2.1 NettyClient
package netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
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;

/**
 * @author: zhangyao
 * @create:2020-09-03 09:52
 **/
public class NettyClient {
    public static void main(String[] args) {


        EventLoopGroup executors = new NioEventLoopGroup();


        Bootstrap bootstrap = new Bootstrap();

        try {
            bootstrap.group(executors)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyClientHandler());
                        }
                    });

            System.out.println("Client ready........");

            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
            //Close channel
            channelFuture.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                executors.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

4.2.4.2.2 NettyClientHandler
package netty;

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

/**
 * @author: zhangyao
 * @create:2020-09-03 10:00
 **/
public class NettyClientHandler extends ChannelInboundHandlerAdapter {


    //Triggered when ready
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("ctx: "+ctx);

        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,Server", CharsetUtil.UTF_8));

    }

    //Read information
    //The information returned by the server is read here
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        ByteBuf byteBuf = (ByteBuf) msg;

        System.out.println("The server sends a message: "+ byteBuf.toString(CharsetUtil.UTF_8));
        System.out.println("Server address: "+ ctx.channel().remoteAddress());

    }

    //exception handling
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

A simple TCP service communication, the client sends messages, the server receives messages and returns messages to the client

4.2.4.3 case demo source code analysis

4.2.4.3.1 NioEventGroup
public NioEventLoopGroup() {
    this(0);
}

public NioEventLoopGroup(int nThreads) {
    this(nThreads, (Executor)null);
}

You can see that if you use the parameterless NioEventGroup, the default is 0. You can also specify the number of threads

Find it layer by layer:

private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)); //NettyRuntime.availableProcessors() gets the number of cores (logical processors) of the current computer

protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
    super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}

Finally, find the method of NioEventGroup parent class

If the number of threads of NioEventGroup is specified and is not 0, the specified number of threads will be used

Otherwise, use the number of cores * 2 of the current computer as the number of threads

debug to see the results

The computer has 12 cores and 24 threads by default

Specify a thread

There is only one thread

4.2.5 asynchronous model

ChannelFuture in the above case Demo

ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();

The asynchronous model Future is relative to synchronization

Asynchrony means that when an asynchronous call is issued, it will not get the result immediately, but notify the caller of the call result through callback and status

The connect and bind() sync methods in Netty return an asynchronous result, and then get the result through listening

That is, the future listener mechanism

When the Future object is just created, it is in an incomplete state. You can view the operation execution status through the returned ChannelFuture, or register a listening function to execute the completed operation

Is isSucess() successful

Is isDone() complete

isCancelable() cancel

cause() failure reason

addListener add listener

//Binding port
            channelFuture = serverBootstrap.bind(6668).sync();
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if(channelFuture.isSuccess()){
                        System.out.println("Listening port succeeded");
                    }else {
                        System.out.println("Listening port failed");
                    }
                }
            });

4.2.6 Netty Http service

Make a simple demo. The browser (client) accesses the 7001 port of the server and returns a string information

Browser access is an http request, and the server also needs a corresponding httpResponse

4.2.6.1 server

NettyHttpServer startup class
package netty.http;

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

/**
 * @author: zhangyao
 * @create:2020-09-04 11:16
 **/
public class NettyHttpServer {

    public static void main(String[] args) throws InterruptedException {

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();

        serverBootstrap.group(bossGroup,workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new NettyHttpInitialize());

        ChannelFuture channelFuture = serverBootstrap.bind(7001).sync();

        channelFuture.channel().closeFuture().sync();

        bossGroup.shutdownGracefully();

        workerGroup.shutdownGracefully();

    }
}
NettyHttpInitialize processor class

Encapsulate the previous ChannelInitialize(SocketChannel)

package netty.http;

import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;

/**
 * @author: zhangyao
 * @create:2020-09-04 11:21
 **/
public class NettyHttpInitialize extends ChannelInitializer<SocketChannel> {


    @Override
    protected void initChannel(SocketChannel sc) throws Exception {
        //Get pipe
        ChannelPipeline pipeline = sc.pipeline();

        //The processor added to the pipeline is mainly used to process Http requests and parse request headers
        pipeline.addLast("myDecoder",new HttpServerCodec());

        //Add processor
        pipeline.addLast("myServerHandler",new NettyServerHandler());
    }
}
Specific processing of NettyServerHandler (return http response)
package netty.http;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import org.springframework.http.HttpStatus;

/**
 * @author: zhangyao
 * @create:2020-09-04 14:01
 **/
public class NettyServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    //read message
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject) throws Exception {
        System.out.println(httpObject);
        System.out.println("Client address+"+ channelHandlerContext.channel().remoteAddress());


        ByteBuf byteBuf = Unpooled.copiedBuffer("hello , im Server", CharsetUtil.UTF_8);

        //Return client information
        FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, HttpResponseStatus.OK,byteBuf);

        fullHttpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=UTF-8");

        fullHttpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH,byteBuf.readableBytes());


        channelHandlerContext.writeAndFlush(fullHttpResponse);



    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

Record of problems:

  1. The first time you bind port 6668, the browser access fails. Just change to 7001

    Reason: Google browser has disabled 6665-6669 and some other insecure ports

  2. The first request after access is abnormal, and the data can be returned normally

    Cause: exception handling method is not overridden in NettyServerHandler

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
    

    Add this part, and the error information is obvious

4.2.6.2 filtering of HTTP services

You can filter some requests that you don't want to process. In fact, you can judge the uri of the request during the processing of http requests

Add the following code to the NettyServerHandler class above to intercept / favicon ICO request

HttpRequest re  = (HttpRequest) httpObject;

String uri = re.uri();
if(uri.equals("/favicon.ico")){
    System.out.println("I don't want to deal with it,return");
    return;
}

4.3 Netty API sorting

Based on the above demo s, this paper systematically combs the classes and methods commonly used by Netty

4.3.1 Bootstrap

  1. ServerBootstrap server boot class

    BootStrap client boot class

    1. . group() set NioEventLoopGroup for BootStrap, and multiple can be set
    2. . channel() sets the channel class used by the service
    3. . option() set channel parameters
    4. . handler() sets the BossGroup
    5. . childrenHandler() sets the workerGroup
    6. . bind() bind a port number to the server and listen on the port
    7. The connect() client is used to connect to the server

4.3.2 Future

io operations in Netty are asynchronous, that is, they cannot return results immediately, but notify the caller when they are completed

  1. Future

  2. ChannelFutrue

    method

    1. channel() returns the channel for which the IO operation is currently in progress
    2. sync() turns to synchronization and waits for the asynchronous operation to complete

4.3.3 Channel

Different protocols and different blocking types have corresponding channels

  1. NioSocketChannel asynchronous tcp protocol Socket connection
  2. NioServerSocketChannel asynchronous tcp protocol server connection
  3. NioDatagramChannel asynchronous udp connection
  4. NioSctpChannel asynchronous sctp client connection
  5. NioSctpServerChannel asynchronous sctp server connection

4.3.4 Selector

Netty implements multiplexing based on Nio Selector object. One selector manages multiple channel s

4.3.5 ChannelHandler

It is mainly used for data processing. There are many encapsulated methods, which can inherit their subclasses when used

Implementation class

There are many subclasses, including several commonly used ones

channelHandler

  1. ChannelInboundHandler
  2. ChannelOutboundHandler
  3. Adapter
    1. channelInboundHandlerAdapter
    2. channelOutboundHandlerAdapter

4.3.6 pipeline

The structure diagram is as above

A channelpipeline can be created in the channel, and a two-way linked list composed of ChannelHandlerContext is maintained in the channelpipeline

Each ChannelHandlerContext corresponds to a Channelhandler

Common methods:

​ addFirst(); Add a Handler to the first position in the linked list

​ addLast(); Add to the last position in the linked list

4.3.7 channelHandlerContext

Each channelhandlerContext contains a channelhandler (business processing)

The corresponding channel and pipeline information can also be obtained in the channelHandlerContext

channelHandlerContext.channel();

channelHandlerCOntext.pipeline();

4.3.8 EventLoopGroup

netty generally provides two eventloopgroups, bosseventloopgroup and workerEventLoopGroup

EventLoopGroup can specify how many cores to use

4.3.9 Unplooed

A utility class provided by Netty to manipulate buffer data

Common methods:

​ copiedBuffer(); The Bytebuf object provided by Netty is returned

4.3.10 ByteBuf

Netty's data container (buffer)

You can read / write directly. There is no need to flip() between reads and writes because ByteBuf internally maintains two indexes readindex and writeindex

common method

getByte()

readByte()

writeByte()

capacity()

4.4 Netty heartbeat detection mechanism

When the client has no read / write operation for a long time, the server needs to detect whether the client is still connected, that is, heartbeat detection

Netty provides the processing class IdleStateHandler for heartbeat detection

Sample code

package netty.hearbeat;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;

import java.util.concurrent.TimeUnit;

/**
 * @author: zhangyao
 * @create:2020-09-10 10:04
 **/
public class MyServer {


    public static void main(String[] args) throws Exception{

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap().group(bossGroup,workerGroup)
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler())
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline.addLast(new IdleStateHandler(3,5,7, TimeUnit.SECONDS));
                        pipeline.addLast("inleHandler",new MyHearBeatHandler());
                    }
                });

        ChannelFuture channelFuture = serverBootstrap.bind(8090).sync();

        channelFuture.channel().closeFuture().sync();

        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}

package netty.hearbeat;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

/**
 * @author: zhangyao
 * @create:2020-09-10 16:50
 **/
public class MyHearBeatHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {

        if(o instanceof IdleStateEvent){

            IdleStateEvent event = (IdleStateEvent) o;

            IdleState state = event.state();
            switch (state){
                case READER_IDLE:
                    System.out.println("Read idle");
                    break;
                case WRITER_IDLE:
                    System.out.println("Write idle");
                    break;
                case ALL_IDLE:
                    System.out.println("Read write idle");
                    break;
            }
        }
    }




}

There is no difference between clients

4.5 Netty's webSocket

Writing webSocket long connection using netty

4.5.1 server

package netty.websocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

/**
 * @author: zhangyao
 * @create:2020-09-11 14:43
 **/
public class MyServer {


    public static void main(String[] args) throws Exception{

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();


        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup,workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) {
                        ChannelPipeline pipeline = socketChannel.pipeline();

                        //Add http decoder
                        pipeline.addLast(new HttpServerCodec());
                        //Add block transport processor
                        pipeline.addLast(new ChunkedWriteHandler());
                        //http segmented transmission, adding an aggregation processing
                        pipeline.addLast(new HttpObjectAggregator(8192));
                        //Add websocket protocol processing
                        pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));


                        //Add custom processing business processor
                        pipeline.addLast(new MyServerHandler());
                    }
                });


        ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();

        channelFuture.channel().closeFuture().sync();

        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}

4.5.2 customized processor on the server

package netty.websocket;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;

/**
 * @author: zhangyao
 * @create:2020-09-11 14:49
 **/
public class MyServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {


    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        //When the client closes the connection
        System.out.println("Client close connection..."+ ctx.channel().id());
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //When the client connects to the server
        System.out.println("A client is connected to the server id by" + ctx.channel().id());

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //Close connection abnormally
        ctx.close();
    }

    //Received message
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
        //Read the message and return the same message to the client
        String text = textWebSocketFrame.text();
        System.out.println("The server receives the message" + text);

        //Return to client

        Channel channel = channelHandlerContext.channel();
        channel.writeAndFlush(new TextWebSocketFrame(LocalDateTime.now()+"  Server return message:" + text));

    }
}

4.5.3 page (webSocket client)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test netty+webSocket</title>
</head>
<body>

    <form onsubmit="return false">
        <textarea id="sendMessage" style="height: 300px;width: 300px" placeholder="Please enter a message to send"></textarea>
        <button onclick="send(document.getElementById('sendMessage').value)">send message</button>

        <textarea id="responseMessage" style="height: 300px;width: 300px" ></textarea>
        <button onclick="document.getElementById('responseMessage').value=''">Clear message</button>

    </form>
</body>


<script type="application/javascript">

    var websocket;

    if(!window.WebSocket){
        alert("Browser does not support webSocket")
    }else {
        //Open and close the webSocket

        websocket = new WebSocket("ws://localhost:7000/hello");
        //webSocket opening event
        //Add a piece of data to the message return box
        websocket.onopen = function (ev) {
            document.getElementById("responseMessage").value = 'Connect to the server';
        }

        websocket.onclose = function (ev) {
            document.getElementById("responseMessage").value += '\n Connection closed';
        }

        //When the server responds to the message, trigger to echo the message returned by the server to the text box
        websocket.onmessage = function (ev) {
            document.getElementById("responseMessage").value += '\n ' ;
            document.getElementById("responseMessage").value += ev.data ;
        }

    }

    //send message
    function send (message) {
        if(!window.websocket){
            alert("socket Initialization has not completed yet");
            return;
        }

        if(websocket.readyState == WebSocket.OPEN){
            websocket.send(message);
            document.getElementById('sendMessage').value=''
        }
    }


</script>
</html>

4.6 Netty encoding and decoding

Encoding and decoding process in network transmission

Codec codec includes encoder and decoder

netty provides some codecs for stringcodec and objectcodec, but these codecs still rely on the underlying serialization technology of java. The underlying serialization technology of java is relatively inefficient, so it is necessary to introduce new and efficient serialization technology

4.6.1 ProtoBuf

4.6.2 custom codec

package netty.inboundAndOutbound;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import netty.inboundAndOutbound.client.MyClientMessageToByteHandler;

/**
 * @author: zhangyao
 * @create:2020-09-18 14:53
 **/
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();

        //Add stack decoder
        pipeline.addLast(new ByteToMessageHandler());

        //Add out of stack encoder
        pipeline.addLast(new MyClientMessageToByteHandler());

        //Add custom processor
        pipeline.addLast(new MyServerHandler());
    }
}

package netty.inboundAndOutbound;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

/**
 * @author: zhangyao
 * @create:2020-09-18 14:54
 **/
public class ByteToMessageHandler extends ByteToMessageDecoder {
    //Custom implemented stack decoder
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        if(byteBuf.readableBytes()>=8){
            list.add(byteBuf.readLong());
        }
    }
}
package netty.inboundAndOutbound.client;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

/**
 * @author: zhangyao
 * @create:2020-09-18 16:16
 **/
public class MyClientByteToLong extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        if(byteBuf.readableBytes()>=8){
            list.add(byteBuf.readLong());
        }
    }
}

4.6.3 handler processing mechanism and out stack in stack

Through the above encoding and decoding, it is extended to the handler processing mechanism

Simple explanation diagram of netty out of stack and into stack

Out of stack and in stack are relative. When the client sends a message to the server, it is out of stack for the client and in stack for the server, and vice versa

4.7 Tcp unpacking

4.7.1 introduction to unpacking

When Tcp service sends a message again, if it sends multiple packets With a small amount of data and a large number of packets, Tcp will combine multiple packets into a large packet through the algorithm and send it to the receiver. The problem is that the receiver cannot recognize the complete packet, and the resulting problem is packet contamination and unpacking

As shown in the figure above, the Client sends D1 and D2 packets to the Server

Four situations may occur when reading from the Server side

1. The data packets D1 and D2 are read in two times, and there is no packet contamination and unpacking

2. In one reading, the packet of D1D2 combined with two data packets is read, and packet contamination occurs

3. It is read twice. Part of the data of D1 and D2 is read for the first time, and the rest of the data of D2 is read for the second time, resulting in unpacking

4. It is read in two times. For the first time, some data of D1 is read, and for the second time, the remaining data of D1 and all data of D2 are read, resulting in unpacking

4.7.2 solutions

Idea: control the length of the content read by the receiver to solve the problem

Solution: solve the problem of unpacking and contamination through user-defined parsing + codec

Topics: Java Netty server