Netty battle NIO

Posted by vapokerpro on Tue, 18 Jan 2022 07:56:38 +0100

Introduction to Netty

1. Netty is an asynchronous, event driven network application framework to quickly develop high-performance and reliable network IO programs.

2. Netty is mainly aimed at high concurrency applications for Clients under TCP protocol, or applications for continuous transmission of a large amount of data in Peer-to-Peer scenario.

3. Netty is essentially a NIO framework, which is applicable to a variety of application scenarios related to server communication

Friendly tips:

Netty is implemented based on NIO, NIO is programmed based on java Native IO, and java Native IO is implemented based on TCP/IP

understand:

1. Asynchronous: similar to ajax

2. Event driven: a thread has a selector. When a client has event actions (actions include sending messages, connecting to the server, replying to messages, receiving messages, etc.), the selector will monitor and let the thread execute.

Friendly tip: non blocking, monitor the channel. If the channel does not act, it will monitor the next channel

I/O model

The simple understanding of I/O model is that what channel is used to send and receive data, which largely determines the performance of program communication

Java supports three network programming models / IO modes: BIO, NIO and AIO

Java BIO: synchronous and blocking (traditional blocking type). The server implementation mode is one thread per connection, that is, when the client has a connection request, the server needs to start a thread for processing. If the connection does not do anything, it will cause unnecessary thread overhead. Java BIO is traditional java io programming, and its related classes and interfaces are in java io.

[simple schematic diagram]

Java NIO: synchronous non blocking. The server implementation mode is that one thread processes multiple requests (connections), that is, the connection requests sent by the client will be registered on the multiplexer, and the multiplexer will process the I/O requests when it polls the connection.

Friendly note: for multiplexing, it is only for network transmission. Local file replication does not exist. The selector here is equivalent to the registry of microservices. Multiple clients need to register with the selector. And the selector monitors the events of each client.

[simple schematic diagram]

Java AIO(NIO.2): asynchronous and non blocking. AIO introduces the concept of asynchronous channel and adopts the Proactor mode, which simplifies the programming and starts the thread only after an effective request. Its feature is that the operating system notifies the server program to start the thread for processing after it is completed. It is generally used in applications with a large number of connections and a long time. (a brief introduction is not the key point. Remember that netty5. X is based on its implementation, but the effect is not very good, so it is invalidated because of the Linux system itself)

Analysis of applicable scenarios of BIO, NIO and AIO

Java BIO working mechanism

Java BIO application instance

Example description:

1) Write a server side using BIO model, listen to port 6666, and start a thread to communicate with it when there is a client connection.

2) It is required to improve the thread pool mechanism, which can connect multiple clients

3) The server can receive the data sent by the client (telnet).

The following is the code implementation:

Server:

package com.atguigu.bio;

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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

        //Thread pool mechanism

        //thinking
        //1. Create a thread pool
        //2. If there is a client connection, create a thread to communicate with it (write a separate method)

        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

        //Create ServerSocket
        ServerSocket serverSocket = new ServerSocket(6666);

        System.out.println("The server started");

        while (true) {

            System.out.println("Thread information id =" + Thread.currentThread().getId() + " name=" + Thread.currentThread().getName());
            //Listen and wait for the client to connect
            System.out.println("Waiting for connection....");
//            This place will be blocked. If there is no user connection, it will be blocked here
            final Socket socket = serverSocket.accept();
            System.out.println("Connect to a client");

            //Create a thread and communicate with it (write a separate method)
            newCachedThreadPool.execute(new Runnable() {
                public void run() { //We rewrite
                    //Can communicate with clients
                    handler(socket);
                }
            });

        }


    }

    //Write a handler method to communicate with the client
    public static void handler(Socket socket) {

        try {
            System.out.println("Thread information id =" + Thread.currentThread().getId() + " name=" + Thread.currentThread().getName());
            byte[] bytes = new byte[1024];
            //Get input stream through socket
            InputStream inputStream = socket.getInputStream();

            //Cyclic reading of data sent by the client
            while (true) {

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

                System.out.println("read....");
//                This place will be blocked. It will be blocked when the user has no input
               int read =  inputStream.read(bytes);
               if(read != -1) {
                   System.out.println(new String(bytes, 0, read
                   )); //Output data sent by client
               } else {
                   break;
               }
            }


        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            System.out.println("Close and client Connection of");
            try {
                socket.close();
            }catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

Client:

package yu.learn;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) {
        // Create a Socket through the constructor and connect the server with the specified address and port
        try {
            Socket socket = new Socket("127.0.0.1", 6666);
            System.out.println("Please enter information");
            new ReadMsg(socket).start();
            PrintWriter pw = null;
            // Write data to the server
            while (true) {
                pw = new PrintWriter(socket.getOutputStream());
                pw.println(new Scanner(System.in).next());
                pw.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static class ReadMsg extends Thread {
        Socket socket;

        public ReadMsg(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try (BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
                String line = null;
                // Read the data transmitted by the server through the input stream
                while ((line = br.readLine()) != null) {
                    System.out.printf("%s\n", line);
                }

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

Java BIO problem analysis

Basic introduction to Java NIO (key points)

1. NIO has three core parts: channel, buffer and selector

2. NIO is buffer oriented or block oriented programming.

Friendly tip: why is BIO not blocked compared to the buffer zone? It's just like people holding a pile of cans. It's more troublesome for the people in front to throw the cans to you little by little. You just say to let the people in front directly throw the cans into the bag. I'll take them away at one time. When he packs, you can listen to music and drink water.

NIO basic introduction

Comparison of NIO and BIO

Friendly tip: BIO is through byte stream and character stream. It is one-way. NIO is through Channel and Buffer, and Channel is bidirectional. The NIO is bidirectional.

Relationship between three core components of NIO

Friendly note: in fact, both the client and the server need a buffer when connecting to the Channel. The following figure is incomplete. It should be noted that the client and the server directly interact with each other through the buffer, and then interact with the Channel through the buffer

 

Buffer core component I

Basic introduction

Friendly note: buffer has some internal mechanisms that can track and record the state changes of the buffer. Channel is the channel. It can be imagined that the binary stream transmitted by the network is people. Channel is a way to reach the destination, such as waterway, air flight and road (in the network, it is equivalent to protocols such as WS protocol and http protocol). This buffer is vehicles, ships, aircraft and cars.

Buffer class and its children

ByteBuffer structure

ByteBuffer has the following four important properties

  • capacity(): it is equivalent to the initial capacity given by the array and remains unchanged
  • position(): it is equivalent to a pointer. It moves continuously. The initial value is 0
  • limit(): the limit position where the pointer moves, that is, the final position of the written data.
  • mark(): indicates a mark. If a mark is added to a certain position, it can be specified at the specified position and continuously read data from the mark bit. The initial value is - 1

be careful:

  • mark() and reset()

    mark() makes a mark when reading. Even if the position changes, you can return to the position of mark as long as you call reset

Rewind and flip will clear the mark position. You can call the rewind method to reset the position to 0

Friendly note: after Clear execution, the position will be written to the starting position again, but after compact execution, the unread data will be compressed to the starting position.

Initial state

 

In write mode

 

Switching using flip

 

After reading 4 bytes

The clear action will program the initial write state (from read to write)

 

The Compact method will move the unread data to the starting position. (from read to write)

Friendly tip: the last bit of mobile data is still the most original data, but the original data will be overwritten when writing.

Buffer related methods

ByteBuffer common methods

Friendly tips:

When the objects of these two methods are output, they are found

System.out.println(ByteBuffer.allocate(16).getClass());For heap
System.out.println(ByteBuffer.allocateDirect(16).getClass());For direct memory
class java.nio.HeapByteBuffer    - java Heap memory, with low read and write efficiency, is affected GC Influence of
class java.nio.DirectByteBuffer  - Direct memory, high reading and writing efficiency (less than one copy), and will not be affected GC Low efficiency of distribution

code:

package com.atguigu.nio;

import java.nio.IntBuffer;

public class BasicBuffer {
    public static void main(String[] args) {

        //Give an example to illustrate the use of Buffer (simple description)
        //Create a Buffer with a size of 5, which can store 5 int s
        IntBuffer intBuffer = IntBuffer.allocate(5);

        //Store data to buffer
//        intBuffer.put(10);
//        intBuffer.put(11);
//        intBuffer.put(12);
//        intBuffer.put(13);
//        intBuffer.put(14);
        for(int i = 0; i < intBuffer.capacity(); i++) {
            intBuffer.put( i * 2);
        }

        //How to read data from buffer
        //buffer conversion, read / write switching (!!!)
        /*
        public final Buffer flip() {
        limit = position; //Read data cannot exceed 5
        position = 0;
        mark = -1;
        return this;
    }
         */
        intBuffer.flip();
        intBuffer.position(1);//1,2
        System.out.println(intBuffer.get());
        intBuffer.limit(3);
        while (intBuffer.hasRemaining()) {
            System.out.println(intBuffer.get());
        }
    }
}

String to ByteBuffer

a key:

      Convert string to ByteBuffer
      ByteBuffer byteBuffer2= StandardCharsets.UTF_8.encode("Me");
      Buffer To convert to a string, you should note that it needs to be converted to a string Buffer conduct flip Otherwise, there is no display
      byteBuffer.flip();
     String str=  StandardCharsets.UTF_8.decode(byteBuffer).toString();
package yu.learn.FileChannel;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

public class StringAndBuffer {
  public static void main(String[] args) {
    //

      ByteBuffer byteBuffer=ByteBuffer.allocate(100);
      byteBuffer.put("You are?".getBytes());

//      Convert string to ByteBuffer
      ByteBuffer byteBuffer2= StandardCharsets.UTF_8.encode("Me");

      System.out.println(new String(byteBuffer2.array()));


//      When converting a Buffer to a string, it should be noted that the Buffer needs to be flip ped to convert to a string, otherwise it will not be displayed
      byteBuffer.flip();
    String str=  StandardCharsets.UTF_8.decode(byteBuffer).toString();
    System.out.println(str);
  }
}

Channel

Friendly tips:

1. The channel can read and write data asynchronously, and can be read and written

2. When using the channel, the read and write methods are for the channel. The read method reads data from the channel to the buffer, and the write method writes data from the buffer to the channel.

3. A Channel can be understood as a stream.

Common channels

  • FileChannel: dedicated to reading files
  • Datagram channel: dedicated to UDP
  • SocketChannel: the client corresponding to network transmission, which is used for TCP transmission
  • ServerSocketChannel: the server corresponding to network transmission, which is used for TCP transmission

The following are the corresponding different channels

Non blocking vs blocking

block

  • In blocking mode, the related methods will cause the thread to pause
    • ServerSocketChannel.accept suspends the thread when no connection is established
    • SocketChannel.read pauses the thread when there is no data to read
    • The performance of blocking is actually that the thread is suspended. During the suspension, the cpu will not be occupied, but the thread is equivalent to idle
  • In a single thread, blocking methods interact with each other and can hardly work normally, requiring multithreading support
  • However, under multithreading, there are new problems, which are reflected in the following aspects
    • A 32-bit jvm has 320k threads and a 64 bit jvm has 1024k threads. If there are too many connections, it will inevitably lead to OOM and too many threads. On the contrary, the performance will be reduced due to frequent context switching
    • Thread pool technology can be used to reduce the number of threads and thread context switching, but the symptoms are not the root cause. If many connections are established but inactive for a long time, all threads in the thread pool will be blocked. Therefore, it is not suitable for long connections, but only for short connections

Friendly tips:

1. The accept() and read() methods are blocked. For the NIO model, use configure blocking (false); Make it non blocking.

2. If single thread is used, blocking methods will affect each other, which is very inefficient and can hardly work.

3. If multithreading is used, the thread pool can be used to reduce the thread switching of online documents. However, if it is a long connection, the thread cannot release resources, and the thread pool is useless. Therefore, it is only suitable for short connection

Non blocking

  • In non blocking mode, will the related methods suspend the thread
    • In serversocketchannel When no connection is established, accept will return null and continue to run
    • SocketChannel.read returns 0 when there is no data to read, but the thread does not need to be blocked. You can read other socketchannels or execute serversocketchannel accept
    • When writing data, the thread just waits for the data to be written to the Channel without waiting for the Channel to send the data through the network
  • However, in non blocking mode, even if there is no connection and readable data, the thread is still running, wasting cpu in vain
  • During data replication, threads are actually blocked (where AIO improves)

Multiplexing

A single thread can cooperate with the Selector to monitor multiple Channel read-write events, which is called multiplexing

  • Multiplexing is only for network IO and ordinary file IO, and multiplexing cannot be used
  • Without the non blocking mode of the Selector, threads spend most of their time doing useless work, and the Selector can ensure that
    • Connect only when there are connectable events
    • Read only when there are readable events
    • Write only when there are writable events
    • Limited to the network transmission capacity, the Channel may not always be writable. Once the Channel is writable, the writable event of the Selector will be triggered

FileChannel class

As a common method of channel, FileChannel does not have a non blocking mode, so it cannot be used together with the Selector, and the Selector will appear in network programming

Note that transferTo uses the knowledge of zero copy

Example 1:

Example requirements:

1) Use the ByteBuffer and filechannel learned above to write "hello, Silicon Valley" to file01 Txt

2) Create if file does not exist

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

public class FileChannelTest {
  public static void main(String[] args) throws IOException {
    //
      String str="Who am I?";
      FileOutputStream fileOutputStream = null;
      try{
        fileOutputStream=new FileOutputStream("d:\\file01.txt");

    FileChannel fileChannel= fileOutputStream.getChannel();
          ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

          byteBuffer.put(str.getBytes());
          byteBuffer.flip();
          fileChannel.write(byteBuffer);

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


  }
}

Example 2:

Example requirements:

1) Use the ByteBuffer and filechannel learned above to set file01 Txt is read into the program and displayed on the console screen

2) Assume that the file already exists

package com.atguigu.nio;

import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

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

        //Create an input stream for the file
        File file = new File("d:\\file01.txt");
        FileInputStream fileInputStream = new FileInputStream(file);

        //Get the corresponding filechannel - > actual type FileChannelImpl through fileInputStream
        FileChannel fileChannel = fileInputStream.getChannel();

        //Create buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());

        //Read the data of the channel into the Buffer
        fileChannel.read(byteBuffer);

        //Convert byte data of byteBuffer to String
        System.out.println(new String(byteBuffer.array()));
        fileInputStream.close();

    }
}

In addition:

package cn.itcast.nio.c2;

import lombok.extern.slf4j.Slf4j;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

@Slf4j
public class TestByteBuffer {

    public static void main(String[] args) {
        // FileChannel
        // 1. Input / output stream, 2 RandomAccessFile
        try (FileChannel channel = new FileInputStream("data.txt").getChannel()) {
            // Prepare buffer
            ByteBuffer buffer = ByteBuffer.allocate(10);
            while(true) {
                // Read data from the channel and write data to the buffer
                int len = channel.read(buffer);
                log.debug("Bytes read {}", len);
                if(len == -1) { // There is no content
                    break;
                }
                // Print the contents of the buffer
                buffer.flip(); // Switch to read mode
                while(buffer.hasRemaining()) { // Is there any unread data left
                    byte b = buffer.get();
                    log.debug("Actual byte {}", (char) b);
                }
                buffer.clear(); // Switch to write mode
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Example 3:

The idea of the following implementation is that both reading and transmission are through a buffer

code

package com.atguigu.nio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

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

        FileInputStream fileInputStream = new FileInputStream("1.txt");
        FileChannel fileChannel01 = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
        FileChannel fileChannel02 = fileOutputStream.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(512);

        while (true) { //Cyclic reading

            //Here is an important operation. Don't forget
            /*
             public final Buffer clear() {
                position = 0;
                limit = capacity;
                mark = -1;
                return this;
            }
             */
            byteBuffer.clear(); //Empty buffer
            int read = fileChannel01.read(byteBuffer);
            System.out.println("read =" + read);
            if(read == -1) { //Indicates that you have finished reading
                break;
            }
            //Write the data in the buffer to filechannel02 -- 2 txt
            byteBuffer.flip();
            fileChannel02.write(byteBuffer);
        }

        //Close the associated flow
        fileInputStream.close();
        fileOutputStream.close();
    }
}

Example 4

Friendly tip: pay attention to the use of transferFrom

The missing (output stream, write) calls the transferFrom method

transferFrom parameters: owner (input stream, read), position, read location, and length of owner resource

destCh.transferFrom(sourceCh,0,sourceCh.size());

Using the transferFrom method of copying files

Example requirements:

1) Use filechannel and method transferFrom to copy the file

2) Copy a picture

package com.atguigu.nio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;

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

        //Create correlation flow
        FileInputStream fileInputStream = new FileInputStream("d:\\a.jpg");
        FileOutputStream fileOutputStream = new FileOutputStream("d:\\a2.jpg");

        //Get the filechannel corresponding to each stream
        FileChannel sourceCh = fileInputStream.getChannel();
        FileChannel destCh = fileOutputStream.getChannel();

        //Use transferForm to complete the copy
        destCh.transferFrom(sourceCh,0,sourceCh.size());
        //Close relevant channels and streams
        sourceCh.close();
        destCh.close();
        fileInputStream.close();
        fileOutputStream.close();
    }
}

Precautions and details about Buffer and Channel

1. What type is stored is what type is obtained. Otherwise, a BufferUnderflowException exception will be reported

What type to write and what type to take

package com.atguigu.nio;

import java.nio.ByteBuffer;

public class NIOByteBufferPutGet {
    public static void main(String[] args) {

        //Create a Buffer
        ByteBuffer buffer = ByteBuffer.allocate(64);

        //Type data
        buffer.putInt(100);
        buffer.putLong(9);
        buffer.putChar('still');
        buffer.putShort((short) 4);

        //take out
        buffer.flip();

        System.out.println();

        System.out.println(buffer.getInt());
        System.out.println(buffer.getLong());
        System.out.println(buffer.getChar());
        System.out.println(buffer.getShort());






    }
}

MappedByteBuffer 

Friendly tip: the code execution is completed, and the data viewed in the idea has not changed. Go to the file management system of the computer to check. The data inside is changed.

NIO also provides MappedByteBuffer, which allows files to be modified directly in memory (memory outside the heap). NIO completes the synchronization of files without one copy. Note that here is the meaning of 5, and only 5 bytes can be modified at most

package com.atguigu.nio;

import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/*
explain
1. MappedByteBuffer The file can be modified directly in memory (off heap memory), and the operating system does not need to copy it once
 */
public class MappedByteBufferTest {
    public static void main(String[] args) throws Exception {

        RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
        //Get the corresponding channel
        FileChannel channel = randomAccessFile.getChannel();

        /**
         * Parameter 1: filechannel MapMode. READ_ Read / write mode used by write
         * Parameter 2:0: the starting position can be modified directly
         * Parameter 3: 5: is the size mapped to memory (not the index position), i.e. 1 How many bytes of TXT are mapped to memory
         * The range that can be directly modified is 0-5. Note that 5 is not included
         * Actual type DirectByteBuffer
         */
        MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 15);

        mappedByteBuffer.put(0, (byte) 'H');
        mappedByteBuffer.put(3, (byte) '9');
        mappedByteBuffer.put(14, (byte) 'Y');//IndexOutOfBoundsException

        randomAccessFile.close();
        System.out.println("Modified successfully~~");



    }
}

Convert Buffer to read-only Buffer

Friendly tip: readonlybuffer Get(), if String is stored in the obtained content, it obtains ASC code. If number is stored, it obtains actual number.

package com.atguigu.nio;

import java.nio.ByteBuffer;

public class ReadOnlyBuffer {
    public static void main(String[] args) {

        //Create a buffer
        ByteBuffer buffer = ByteBuffer.allocate(64);

        for(int i = 0; i < 64; i++) {
            buffer.put((byte)i);
        }

        //read
        buffer.flip();

        //Get a read-only Buffer
        ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
        System.out.println(readOnlyBuffer.getClass());

        //read
        while (readOnlyBuffer.hasRemaining()) {
            System.out.println(readOnlyBuffer.get());
        }

        readOnlyBuffer.put((byte)100); //ReadOnlyBufferException
    }
}

Scattering and Gathering

NIO also supports reading and writing through multiple buffers (i.e. Buffer arrays), mainly because the internal parameters of the corresponding CHANLE read and write methods can make the Buffer array object

Code for Gathering

public class TestGatheringWrites {
    public static void main(String[] args) {
        ByteBuffer b1 = StandardCharsets.UTF_8.encode("hello");
        ByteBuffer b2 = StandardCharsets.UTF_8.encode("world");
        ByteBuffer b3 = StandardCharsets.UTF_8.encode("Hello");

        try (FileChannel channel = new RandomAccessFile("words2.txt", "rw").getChannel()) {
            channel.write(new ByteBuffer[]{b1, b2, b3});
        } catch (IOException e) {
        }
    }
}

Code for Scattering

public class TestScatteringReads {
    public static void main(String[] args) {
        try (FileChannel channel = new RandomAccessFile("words.txt", "r").getChannel()) {
            ByteBuffer b1 = ByteBuffer.allocate(3);
            ByteBuffer b2 = ByteBuffer.allocate(3);
            ByteBuffer b3 = ByteBuffer.allocate(5);
            channel.read(new ByteBuffer[]{b1, b2, b3});
            b1.flip();
            b2.flip();
            b3.flip();
            debugAll(b1);
            debugAll(b2);
            debugAll(b3);
        } catch (IOException e) {
        }
    }
}

Network programming code:

Friendly tips:

Scattering: when writing data to the buffer, you can use the buffer array to write [scattered] successively

Gathering: when reading data from the buffer, you can use the buffer array to read data in sequence

package com.atguigu.nio;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;

/**
 * Scattering: When writing data to the buffer, you can use the buffer array to write [scattered] successively
 * Gathering: When reading data from the buffer, you can use the buffer array to read data in sequence
 */
public class ScatteringAndGatheringTest {
    public static void main(String[] args) throws Exception {

        //Using ServerSocketChannel and SocketChannel networks

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);

        //Bind the port to the socket and start
        serverSocketChannel.socket().bind(inetSocketAddress);

        //Create buffer array
        ByteBuffer[] byteBuffers = new ByteBuffer[2];
        byteBuffers[0] = ByteBuffer.allocate(5);
        byteBuffers[1] = ByteBuffer.allocate(3);

        //Wait for client connection (telnet)
        SocketChannel socketChannel = serverSocketChannel.accept();
        int messageLength = 8;   //Assume 8 bytes are received from the client
        //Cyclic reading
        while (true) {

            int byteRead = 0;

            while (byteRead < messageLength ) {
//                Read data from client
                long l = socketChannel.read(byteBuffers);
                byteRead += l; //Cumulative bytes read
                System.out.println("byteRead=" + byteRead);
                //Use stream printing to see the position and limit of the current buffer
                Arrays.asList(byteBuffers).stream().map(buffer -> "postion=" + buffer.position() + ", limit=" + buffer.limit()).forEach(System.out::println);
            }

            //flip all buffer s
            Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());

            //Read and display data to the client
            long byteWirte = 0;
            while (byteWirte < messageLength) {
                long l = socketChannel.write(byteBuffers); //
                byteWirte += l;
            }

            //clear all buffer s
            Arrays.asList(byteBuffers).forEach(buffer-> {
                buffer.clear();
            });

            System.out.println("byteRead:=" + byteRead + " byteWrite=" + byteWirte + ", messagelength" + messageLength);
        }




    }
}

Selectors are also called multiplexers

Tips: one Selector corresponds to multiple channels A thread corresponds to a Selector. The Selector is used to listen to Channel events. Only when an event occurs will it be processed. There is no or no blocking. What should this thread do. The high efficiency of redis single thread is also the problem of IO multiplexing.

Schematic diagram:

socket: indicates the client. No Channel is written in the middle (corresponding to read-write). Multiple channels correspond to a Selector. Similarly, it corresponds to a thread.

characteristic:

1. Netty's NioEventLoop aggregates Selector selectors.

2. If the current channel is idle, the thread will switch to other channels to perform tasks.

Selector class related methods

The difference between SelectedKeys() and Select(): SelectedKeys() is an acquisition, which will be returned as long as it is registered on the Selector, and Select() returns the Selectionkey with an event

Supplement:

When the Selector's select is listening, if no event occurs, it is in the blocking state. If an event occurs, it is not in the blocking state.

The select() method of the Selector will listen to the channel where the event occurs. Once an event occurs, add its corresponding selectionkey to the internal set and return it. The internal set corresponds to the following figure. HashSet<SelectionKey>

Another Channel is associated with the SelectionKey

selector.select() / / blocking is also a blocking method

selector.select(1000);// Block for 1000 ms and return after 1000 ms

selector.wakeup();// Wake up selector

selector.selectNow();// No blocking, return immediately

When does select not block

The following are all cases where the event does not block

  • When the event occurs
    • When a client initiates a connection request, an accept event will be triggered
    • When the client sends data, the read event will be triggered when the client closes normally or abnormally. In addition, if the sent data is larger than the buffer, multiple read events will be triggered
    • If the channel is writable, the write event will be triggered
    • When nio bug occurs under linux
  • Call selector wakeup()
  • Call selector close()
  • interrupt of the thread where the selector is located

Can the incident not be handled after it occurs

After an event occurs, you can either process it or cancel it. You can't do nothing. Otherwise, the event will still be triggered next time. This is because the nio bottom layer uses horizontal triggering

Schematic diagram of NIO non blocking network programming

1. When the client connects, the SocketChannel will be obtained through the ServerSocketChannel of the server, and the SocetChannel of the client can be obtained through the accept() method of ServerSocketChannel

Tip: before registering the client with the Selector, first register the ServsocketChannel of the server with the Selector, which is also the register () method called.

2. The Selector listens for events by calling the select() method of the Selector. The method returns the number of channels with events.

3. Register socketchannels on the selector, register(Selector sel, int ops). Multiple socketchannels can be registered on a selector

4. After registration, a SelectionKey will be returned, and its SelectionKey will also be put into the HashMap collection and associated with the Selector (Collection)

5. Then, the selectionkey of each client is obtained (the channel with events)

6. When the socketchannel is inversely obtained through the SelectionKey, the SelectionKey calls the method channel() to obtain the corresponding Channel Of course, you can also get the Selector through the SelectionKey, just call Selector()

7. Business processing can be completed through the obtained Channel

SelectionKey

There are four kinds of registration relationships between Selector and network channel:

SelectionKey related methods

ServerSocketChannel (mainly for connection)

ServerSocketChannel mainly listens to new client Socket connections on the server side

SocketChannel (mainly for data reading and writing)

SocketChannel, the network IO channel, is specifically responsible for reading and writing operations. NIO writes the data in the buffer to the channel, or reads the data in the channel to the buffer.

Case 1

Why does the following code use keyiterator remove()

Friendly tip: frankly, he won't take the initiative to help us delete it. If he doesn't delete it, he will always be put in the collection. If there is no deletion, we can still get the null pointer exception, because the SelectionKey has been used by us before.

After the select event occurs, the relevant keys will be put into the selectedKeys collection, but will not be removed from the selectedKeys collection after processing. We need to code and delete them ourselves. for example

  • The accept event was triggered for the first time and was not removed
  • The read event is triggered the second time, but there is the last one in the selectedKeys. During processing, because no real serverSocket is connected, null pointer exception will be caused

Function of cancel

cancel cancels the channel registered on the selector and deletes the key from the keys collection. The subsequent events will not be monitored

Friendly tips: the following cases are listed to distinguish between selectors Keys () and selector selectedKeys()

1,selector.keys() mainly gets all the events registered on the Selector. Including server registered

2,selector.selectedKeys() indicates the number of events that have occurred, that is, the number of events that are occurring.

Set<SelectionKey> keys=selector.keys();
Set<SelectionKey> selectionKeys = selector.selectedKeys();

Case requirements:

1) Write a NIO entry case to realize simple data communication between server and client (non blocking)

2) Objective: to understand NIO non blocking network programming mechanism

Server code:

package com.atguigu.nio;

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

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

    // Create serversocketchannel - > ServerSocket

    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

    // Get a Selecor object
    Selector selector = Selector.open();

    // Bind a port 6666 to listen on the server side
    serverSocketChannel.socket().bind(new InetSocketAddress(6666));
    // Set to non blocking
    serverSocketChannel.configureBlocking(false);

    // Register the serverSocketChannel with the selector, and the concerned event is OP_ACCEPT
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

    System.out.println("Registered selectionkey quantity=" + selector.keys().size()); // 1

    // Loop waiting for client connection
    while (true) {

      // Here, we wait for 1 second. If no event occurs, we return the call of select, indicating the number of events
      if (selector.select(1000) == 0) { // No events occurred
        System.out.println("The server waited for 1 second and there was no connection");
        continue;
      }

      // If the returned is > 0, the relevant selectionKey collection is obtained
      // 1. If the returned > 0, it indicates that the event of interest has been obtained
      // 2. selector.selectedKeys() returns a collection of events of interest
      //   Reverse channel acquisition through selectionKeys
      Set<SelectionKey> selectionKeys = selector.selectedKeys();
      System.out.println("selectionKeys quantity = " + selectionKeys.size());

      // Traverse set < selectionkey >, and use iterator to traverse
      Iterator<SelectionKey> keyIterator = selectionKeys.iterator();

      while (keyIterator.hasNext()) {
        // Get SelectionKey
        SelectionKey key = keyIterator.next();
        // Handle the events according to the channel corresponding to the key
        if (key.isAcceptable()) { // If OP_ACCEPT, there is a new client connection
          // The client generates a SocketChannel
          SocketChannel socketChannel = serverSocketChannel.accept();
          System.out.println("Client connection successfully generated a socketChannel " + socketChannel.hashCode());
          // Set SocketChannel to non blocking
          socketChannel.configureBlocking(false);
          // Register the socketChannel with the selector, and the attention event is OP_READ, and give to the socketChannel at the same time
          // Associate a buf fer, which is on the server side
          socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));

          //                 selector.keys() in keys() represents all
          // selector.selectedKeys() in selectedKeys() listens for the number of events in the registered channel
          System.out.println("After the client connects, the registered selectionkey quantity=" + selector.keys().size()); // 2,3,4..
        }
        if (key.isReadable()) { // Occurrence of OP_READ

          // Reverse obtain the corresponding channel through the key
          SocketChannel channel = (SocketChannel) key.channel();

          // Get the buffer associated with the channel
//          attachment gets the data associated with it
          ByteBuffer buffer = (ByteBuffer) key.attachment();
          channel.read(buffer);
          System.out.println("form client " + new String(buffer.array()));
        }

        // Manually move the current selectionKey from the collection to prevent repeated operations
        keyIterator.remove();
      }
    }
  }
}

Client code:

package com.atguigu.nio;

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

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

        //Get a network channel
        SocketChannel socketChannel = SocketChannel.open();
        //Set non blocking
        socketChannel.configureBlocking(false);
        //Provide server-side ip and port
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        //Connect to the server. If the connection is not successful
        if (!socketChannel.connect(inetSocketAddress)) {

            while (!socketChannel.finishConnect()) {
                System.out.println("Because the connection takes time, the client will not block and can do other work..");
            }
        }

        //... If the connection is successful, send the data
        String str = "hello, Shang Silicon Valley~";
        //Wraps a byte array into a buffer
//        This is the buffer of the client. Wrap stores the data in the cache intelligently. It was set manually before. In the past, you need to specify the size of the buffer yourself. With it, you can be more intelligent
        ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
        //Send data and write buffer data to channel
        socketChannel.write(buffer);
        System.in.read();

    }
}

Case 2 group chat

1) Write a NIO group chat system to realize simple data communication (non blocking) between server and client

2) Realize multi crowd chat

3) Server side: it can monitor users online and offline, and realize message forwarding function

4) Client: it can send messages to all other users through the channel without blocking, and can accept messages sent by other users (forwarded by the server)

 

Server code

package com.atguigu.nio.groupchat;

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

public class GroupChatServer {
    //Define properties
    private Selector selector;
    private ServerSocketChannel listenChannel;
    private static final int PORT = 6667;

    //constructor 
    //Initialization work
    public GroupChatServer() {

        try {

            //Get selector
            selector = Selector.open();
            //ServerSocketChannel
            listenChannel =  ServerSocketChannel.open();
            //Binding port
            listenChannel.socket().bind(new InetSocketAddress(PORT));
            //Set non blocking mode
            listenChannel.configureBlocking(false);
            //Register the listenChannel with the selector
            listenChannel.register(selector, SelectionKey.OP_ACCEPT);

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

    }

    //monitor
    public void listen() {

        System.out.println("Listen Thread : " + Thread.currentThread().getName());
        try {

            //Cyclic processing
            while (true) {

                int count = selector.select();
                if(count > 0) {//Event handling

                    //Traversal to get selectionKey collection
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        //Remove the selectionkey
                        SelectionKey key = iterator.next();

                        //Listen to accept
                        if(key.isAcceptable()) {
                            SocketChannel sc = listenChannel.accept();
                            sc.configureBlocking(false);
                            //Register the sc with seletor
                            sc.register(selector, SelectionKey.OP_READ);

                            //Tips
                            System.out.println(sc.getRemoteAddress() + " go online ");

                        }
                        if(key.isReadable()) { //The channel sends a read event, that is, the channel is in a readable state
                            //Processing read (special write method..)

                            readData(key);

                        }
                        //Delete the current key to prevent repeated processing
                        iterator.remove();
                    }

                } else {
                    System.out.println("wait for....");
                }
            }

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

        }finally {
            //Exception handling occurred

        }
    }

    //Read client messages
    private void readData(SelectionKey key) {

        //Get the associated chanle
        SocketChannel channel = null;

        try {
           //Get channel
            channel = (SocketChannel) key.channel();
            //Create buffer
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            int count = channel.read(buffer);
            //Process according to the value of count
            if(count > 0) {
                //Convert the data in the cache into a string
                String msg = new String(buffer.array());
                //Output the message from the console
                System.out.println("form client: " + msg);

                //Forward messages to other clients (get rid of yourself) and write a method to deal with it
                sendInfoToOtherClients(msg, channel);
            }

        }catch (IOException e) {
            try {
                System.out.println(channel.getRemoteAddress() + " Offline..");
                //Unregister
                key.cancel();
                //Close channel
                channel.close();
            }catch (IOException e2) {
                e2.printStackTrace();;
            }
        }
    }

    //Forward messages to other customers (channels)
    private void sendInfoToOtherClients(String msg, SocketChannel self ) throws  IOException{

        System.out.println("Server forwarding message...");
        System.out.println("The server forwards data to the client thread: " + Thread.currentThread().getName());
        //Traverse all socketchannels registered on the selector and exclude self
        for(SelectionKey key: selector.keys()) {

            //Retrieve the corresponding SocketChannel through the key
            Channel targetChannel = key.channel();

            //Exclude yourself
            if(targetChannel instanceof  SocketChannel && targetChannel != self) {

                //transformation
                SocketChannel dest = (SocketChannel)targetChannel;
                //Store msg in buffer
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                //Write buffer data to channel
                dest.write(buffer);
            }
        }

    }

    public static void main(String[] args) {

        //Create server object
        GroupChatServer groupChatServer = new GroupChatServer();
        groupChatServer.listen();
    }
}

//You can write a Handler
class MyHandler {
    public void readData() {

    }
    public void sendInfoToOtherClients(){

    }
}

Client code

package com.atguigu.nio.groupchat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

public class GroupChatClient {

    //Define related properties
    private final String HOST = "127.0.0.1"; // Server ip
    private final int PORT = 6667; //Server port
    private Selector selector;
    private SocketChannel socketChannel;
    private String username;

    //Constructor to complete initialization
    public GroupChatClient() throws IOException {

        selector = Selector.open();
        //Connect server
        socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT));
        //Set non blocking
        socketChannel.configureBlocking(false);
        //Register the channel with the selector
        socketChannel.register(selector, SelectionKey.OP_READ);
        //Get username
        username = socketChannel.getLocalAddress().toString().substring(1);
        System.out.println(username + " is ok...");

    }

    //Send message to server
    public void sendInfo(String info) {

        info = username + " Say:" + info;

        try {
            socketChannel.write(ByteBuffer.wrap(info.getBytes()));
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

    //Read the message replied from the server
    public void readInfo() {

        try {

            int readChannels = selector.select();
            if(readChannels > 0) {//There are channels available

                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {

                    SelectionKey key = iterator.next();
                    if(key.isReadable()) {
                        //Get the relevant channel
                       SocketChannel sc = (SocketChannel) key.channel();
                       //Get a Buffer
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //read
                        sc.read(buffer);
                        //Convert the read buffer data into a string
                        String msg = new String(buffer.array());
                        System.out.println(msg.trim());
                    }
                }
                iterator.remove(); //Delete the current selectionKey to prevent repeated operations
            } else {
                //System.out.println("no channel available...);

            }

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

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

        //Start our client
        GroupChatClient chatClient = new GroupChatClient();

        //Start a thread every 3 seconds to read the data sent from the server
        new Thread() {
            public void run() {

                while (true) {
                    chatClient.readInfo();
                    try {
                        Thread.currentThread().sleep(3000);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        //Send data to the server
        Scanner scanner = new Scanner(System.in);

        while (scanner.hasNextLine()) {
            String s = scanner.nextLine();
            chatClient.sendInfo(s);
        }
    }


}

Boundary of processing message (brief introduction)

That is, the problem of sticking package, which will be discussed in detail later. Just a brief introduction

Due to the uncertainty of the network, a message is split into incomplete, or a message is complete but has more messages that do not belong to itself at the end.

 

Solution:

  • Idea 1: fixed message length, the same packet size, and the server reads according to the predetermined length. The disadvantage is a waste of bandwidth
  • Train of thought 2: split by separator. The disadvantage is low efficiency
  • Idea 3: TLV format, i.e. Type, Length and Value data. When the Type and Length are known, it is convenient to obtain the message size and allocate the appropriate buffer. The disadvantage is that the buffer needs to be allocated in advance. If the content is too large, it will affect the server throughput
    • Http 1.1 is a TLV format
    • Http 2.0 is an LTV format

This idea is a little like the internal structure of Java class file

Topics: Java socket