Analysis of IO model based on Netty principle

1, Introduction and application scenarios of Netty

1.1 introduction to netty

Netty is a Java open source framework provided by JBOSS and is now an independent project on Github. Netty is an asynchronous, event driven network application framework for rapid development of high-performance, high reliability network IO programs. It is mainly aimed at high concurrency applications facing the Client side under the TCP protocol, or applications with a large amount of data continuous transmission under the Peer-to-Peer scenario. Netty is essentially a NIO framework, which is applicable to a variety of application scenarios related to server communication.

1.2 application scenarios of netty

1.2.1 Internet industry

In distributed systems, remote service calls are required between nodes, and high-performance RPC frameworks are essential. As an asynchronous high-performance communication framework, Netty is often used by these RPC frameworks as a basic communication component. Typical applications are: the RPC framework of Alibaba distributed service framework Dubbo uses the Dubbo protocol for inter node communication. By default, the Dubbo protocol uses Netty as the basic communication component to realize internal communication between process nodes.

1.2.2 game industry

Whether it is mobile game server or large-scale online games, Java language has been more and more widely used. As a high-performance basic communication component, Netty provides TCP/UDP and HTTP protocol stacks to facilitate customization and development of private protocol stacks and account login to the server. Map servers can easily communicate with each other in high performance through Netty.

1.2.3 big data field

The RPC framework of Avro, the classic Hadoop high-performance communication and serialization component, uses Netty for cross-border point communication by default. Its NettyService is based on the secondary encapsulation implementation of Netty framework.

1.2.4 other open source projects use Netty

website: https://netty.io/wiki/related-projects.html

2, 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.

2.1 basic description of model

Java supports three network programming model I/O modes: BIO, NIO and AIO.

  1. Java BIO: synchronous and blocking (traditional blocking). The server implementation mode is one connection and one thread. That is, when the client has a connection request, the server needs to start a thread to process it. If the connection does not do anything, it will cause unnecessary thread overhead.

  1. 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 are registered with the Selector (multiplexer), and the Selector polls the connection for I/O requests.

  1. Java AIO(NIO.2): asynchronous and non blocking. AIO introduces the concept of asynchronous channel, adopts the Proactor mode, simplifies programming, and starts the thread only after an effective request is made. Its characteristic is that the operating system notifies the server program to start the thread for processing after it is completed. It is generally applicable to applications with a large number of connections and a long connection time.

2.2 usage scenario analysis

  1. BIO mode is applicable to the architecture with a relatively small number of connections and a fixed number of connections. This mode has high requirements on server resources, and concurrency is limited to applications. It was the only option before JDK1.4, but the program is simple and easy to understand.
  2. NIO mode is applicable to the architecture with a large number of connections and relatively short connections (light operation), such as chat server, bullet screen system, inter server communication, etc. The programming is complicated, and JDK1.4 starts to support it.
  3. AIO mode is used in architectures with a large number of connections and long connections (re operation), such as photo album servers, which fully call the OS to participate in concurrent operations. The programming is complex, and JDK7 starts to support it.

2.3 comparison

BIO NIO AIO
IO model Synchronous blocking Synchronous nonblocking (multiplexing) Asynchronous non blocking
Programming difficulty simple complex complex
reliability difference good good
throughput low high high

Examples

  1. Synchronous blocking: when you go to the barber's for a haircut, you wait for the barber until it's your turn to have a haircut.
  2. Synchronous non blocking: when you go to the barber's to have a haircut, you find someone else in front of you. Tell the barber to do other things first and come back later to see if it's your turn
  3. Asynchronous non blocking: call the barber and ask him to come to the barber's house to do other things. The barber will come home to cut your hair

3, Java BIO programming

3.1 basic introduction to bio

  1. Java BIO is the traditional Java I/O programming, and its related classes and interfaces are in java io.
  2. BIO(BlockingI/O): synchronous blocking. The server implementation mode is one connection and one thread. That is, when the client has a connection request, the server needs to start a thread to process it. If the connection does not do anything, it will cause unnecessary thread overhead. It can be improved through the thread pool mechanism (multiple clients connect to the server).
  3. BIO mode is applicable to the architecture with a relatively small number of connections and a fixed number of connections. This mode has high requirements on server resources, and concurrency is limited to applications. It is the only option before JDK1.4, and the program is simple and easy to understand.

3.2 BIO working mechanism

Sorting out BIO programming process

  1. Start a ServerSocket on the server side.
  2. The client starts the Socket to communicate with the server. By default, the server needs to establish a thread for each client to communicate with.
  3. After the client sends a request, it first asks the server whether there is a thread response. If not, it will wait or be rejected.
  4. If there is a response, the client thread will wait for the request to end before continuing execution.

3.3 BIO application examples

  1. Use the BIO model to write a server, listen to port 6666, and start a thread to communicate with the client when there is a client connection.
  2. It is required to use the thread pool mechanism to improve, and multiple clients can be connected.
  3. The server can receive the data sent by the client (telnet).
package com.test.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("Server started");
        while (true) {
            System.out.println("Thread information id = " + Thread.currentThread().getId() + "name = " + Thread.currentThread().getName());
            //Listen and wait for client connection
            System.out.println("Waiting for connection....");
            final Socket socket = serverSocket.accept();
            System.out.println("Connect to a client");
            //Create a thread and communicate with it (write a method separately)
            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....");
                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 for");
            try {
                socket.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

3.4 BIO problem analysis

  1. Each request needs to create an independent thread to Read, process and Write data with the corresponding client.
  2. When the number of concurrency is large, a large number of threads need to be created to process connections, which takes up a large amount of system resources.
  3. After the connection is established, if the current thread has no data readable temporarily, the thread will block the Read operation, resulting in a waste of thread resources.

4, Java NIO programming

4.1 NIO basic introduction

The full name of Java NIO is java non blocking IO, which refers to the new API provided by JDK. Starting from JDK1.4, Java has provided a series of improved I / O new features, collectively referred to as NIO (NewIO), which are synchronous and non blocking. NIO related classes are placed in java NIO package and sub package, and the original java Many classes in the IO package are rewritten.
NIO has three core parts: Channel, Buffer and Selector. NIO is Buffer oriented or block oriented programming. The data is read to a Buffer that will be processed later, and can be moved back and forth in the Buffer when necessary, which increases the flexibility in the processing process. Using it can provide a non blocking high scalability network.
The non blocking mode of Java NIO enables a thread to send requests or read data from a channel, but it can only get the currently available data. If there is no data available, it will not get anything, instead of keeping the thread blocked. Therefore, the thread can continue to do other things until the data can be read. The same is true for non blocking writes. A thread requests to write some data to a channel, but does not need to wait for it to write completely. The thread can do other things at the same time.
Popular understanding: NIO can handle multiple operations with one thread. Suppose there are 10000 requests coming. According to the actual situation, 50 or 100 threads can be allocated to process them. Unlike the previous blocking IO, 10000 must be allocated.
HTTP 2.0 uses the multiplexing technology to process multiple requests simultaneously on the same connection, and the number of concurrent requests is several orders of magnitude larger than HTTP 1.1.

package com.test.nio;

import java.nio.IntBuffer;

public class BasicBuffer {
    public static void main(String[] args) {
        //Give an example to illustrate the use of Buffer (brief description)
        //Create a Buffer with a size of 2, which can store 2 int s
        IntBuffer intBuffer = IntBuffer.allocate(2);

        //Store data to the buffer
        //intBuffer.put(10);
        //intBuffer.put(11);
        for (int i = 0; i < intBuffer.capacity(); i++) {
            intBuffer.put(i * 2);
        }
        //How to read data from buffer
        //buffer conversion, read / write switching (!!!)
        intBuffer.flip();
        while (intBuffer.hasRemaining()) {
            System.out.println(intBuffer.get());
        }
    }
}

4.2 comparison between NiO and BIO

  1. BIO processes data in stream mode, while NIO processes data in block mode. The efficiency of block I/O is much higher than that of stream I/O.
  2. BIO is blocking, NIO is non blocking.
  3. BIO operates based on byte stream and character stream, while NIO operates based on Channel and Buffer. Data is always read from the Channel to the Buffer, or written from the Buffer to the Channel. The Selector is used to listen to events of multiple channels (such as connection requests, data arrival, etc.), so a single thread can listen to multiple client channels.

4.3 schematic diagram of three core principles of NiO

Relationship among NIO Selector, Channel and Buffer

  1. Each Channel corresponds to a Buffer.
  2. A Selector corresponds to a thread, and a thread corresponds to multiple channels (connections).
  3. The figure shows that three channels are registered to the Selector / / program
  4. The Channel to which a program switches is determined by events. Event is an important concept.
  5. The Selector will switch on each channel according to different events.
  6. Buffer is a memory block, and there is an array at the bottom.
  7. Data is read and written through the Buffer. This is related to BIO. BIO is either an input stream or an output stream, and cannot be bidirectional. However, NIO's Buffer can be read or written. The flip method switching Channel is bidirectional, and the underlying operating system can be returned. For example, Linux, the underlying operating system Channel is bidirectional.

4.4 Buffer

4.4.1 basic introduction

Buffer: a buffer is essentially a memory block that can read and write data. It can be understood as a container object (including an array). This object provides a set of methods to use memory blocks more easily. The buffer object has built-in mechanisms to track and record the state changes of the buffer. The Channel provides a Channel for reading data from files and networks, but the read or written data must pass through the buffer, as shown in the figure: [examples later]

4.4.2 Buffer class and its subclasses

  1. In NIO, Buffer is a top-level parent class. It is an abstract class. The hierarchy diagram of the class:

  1. The Buffer class defines four attributes that all buffers have to provide information about the data elements they contain:

  1. List of Buffer class related methods

4.4.3 ByteBuffer

It can be seen from the above that for basic data types in Java (except boolean), there is a Buffer type corresponding to them. The most commonly used type is the ByteBuffer class (binary data). The main methods of this class are as follows:

4.5 Channel

4.5.1 basic introduction

  1. NIO channels are similar to streams, but some differences are as follows:
  • Channels can read and write at the same time, while streams can only read or write
  • The channel can read and write data asynchronously
  • The channel can read data from the buffer or write data to the buffer:
  1. Streams in BIO are unidirectional. For example, FileInputStream objects can only read data, while channels in NIO are bidirectional and can be read or written.
  2. Channel is an interface in NIO public interface channel extensions closed{}
  3. Common Channel classes include FileChannel, DatagramChannel, ServerSocketChannel, and SocketChannel. [ServerSocketChannel is similar to ServerSocket, SocketChannel is similar to Socket]
  4. FileChannel is used to read and write file data, DatagramChannel is used to read and write UDP data, ServerSocketChannel and SocketChannel are used to read and write TCP data.

4.5.2 FileChannel class

FileChannel is mainly used to perform IO operations on local files. Common methods include

  • public int read(ByteBuffer dst), read data from the channel and put it into the buffer
  • public int write(ByteBuffer src) writes buffer data to the channel
  • public long transferFrom(ReadableByteChannel src, long position, long count) copies data from the target channel to the current channel
  • public long transferTo(long position, long count, WritableByteChannel target) copies data from the current channel to the target channel

4.5.3 application example 1 - local file write data

Example requirements:

  1. Use ByteBuffer and FileChannel to write "helloworld" to file01 Txt
  2. Create when file does not exist
package com.test.nio;

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

public class NIOFileChannel01 {

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

        String str = "helloworld";
        //Create an output stream - > Channel
        FileOutputStream fileOutputStream = new FileOutputStream("d:\\file01.txt");
        //Get the corresponding FileChannel through fileOutputStream
        //The real type of this fileChannel is FileChannelImpl
        FileChannel fileChannel = fileOutputStream.getChannel();
        //Create a buffer ByteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //Put str into byteBuffer
        byteBuffer.put(str.getBytes());
        //flip byteBuffer
        byteBuffer.flip();
        //Write byteBuffer data to fileChannel
        fileChannel.write(byteBuffer);
        fileOutputStream.close();
    }
}

4.5.4 application example 2 - local file read data

Using ByteBuffer and FileChannel, set file01 Txt is read into the program and displayed on the console screen

package com.test.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 input stream for 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 channel data into Buffer
        fileChannel.read(byteBuffer);
        //Convert byte data of byteBuffer to String
        System.out.println(new String(byteBuffer.array()));
        fileInputStream.close();
    }
}

4.5.5 application example 3 - use a Buffer to read and write files

Use FileChannel and the methods read and write to copy a text file 1 Txt under the project

package com.test.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 read
            //Here's 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) { //Means finished reading
                break;
            }
            //Write the data in the buffer to filechannel02--2 txt
            byteBuffer.flip();
            fileChannel02.write(byteBuffer);
        }
        //Close related flows
        fileInputStream.close();
        fileOutputStream.close();
    }
}

4.5.6 application example 4 - copy file transferFrom method

Use FileChannel and method transferFrom to copy an image

package com.test.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 related channels and flows
        sourceCh.close();
        destCh.close();
        fileInputStream.close();
        fileOutputStream.close();
    }
}

4.5.7 precautions and details about Buffer and Channel

  1. ByteBuffer supports typed put and get. What data type is put in, get should use the corresponding data type to get it. Otherwise, there may be a BufferUnderflowException exception. [examples]
package com.test.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('test');
        buffer.putShort((short) 4);
        //take out
        buffer.flip();
        System.out.println(buffer.getInt());
        System.out.println(buffer.getLong());
        System.out.println(buffer.getChar());
        System.out.println(buffer.getShort());
    }
}
  1. You can convert a normal Buffer to a read-only Buffer [example]
package com.test.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
    }
}
  1. NIO also provides MappedByteBuffer, which allows files to be modified directly in memory (memory outside the heap), and NIO completes the synchronization to files. [examples]
package com.test.nio;

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

/**
 * Description 1 Mappedbytebuffer allows files to be modified directly in memory (off heap memory), and the operating system does not need to copy them 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: start position that can be modified directly
         * Parameter 3:5: is the size mapped to memory (not the index position), that is, 1 How many bytes of TXT are mapped to memory
         * The range that can be directly modified is 0-5
         * Actual type DirectByteBuffer
         */
        MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);

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

        randomAccessFile.close();
        System.out.println("Modified successfully~~");
    }
}
  1. Read / write operations are all performed through one Buffer. NIO also supports reading / write operations through multiple buffers (i.e., Buffer arrays), i.e., Scattering and Gathering [examples]
package com.test.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) {
                long l = socketChannel.read(byteBuffers);
                byteRead += l; //Cumulative bytes read
                System.out.println("byteRead = " + byteRead);
                //Using stream printing, see the position and limit of the current buffer
                Arrays.asList(byteBuffers).stream().map(buffer -> "position = " + 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 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);
        }
    }
}

4.6 Selector

4.6.1 basic introduction

  1. NIO of Java uses non blocking IO mode. You can use one thread to process multiple client connections, and you will use the Selector.
  2. The Selector can detect whether an event occurs on multiple registered channels (Note: multiple channels can be registered to the same Selector in the form of events). If an event occurs, it will obtain the event and then process each event accordingly. In this way, only one single thread can manage multiple channels, that is, multiple connections and requests. [schematic diagram]
  3. Only when the connection / channel has a real read / write event, can it be read / written, which greatly reduces the system overhead, and it is not necessary to create a thread for each connection and maintain multiple threads.
  4. It avoids the overhead caused by context switching between multiple threads.

4.6.2 schematic diagram and feature description of selector

The description is as follows:

  1. Netty's IO thread NioEventLoop aggregates selectors (selectors, also known as multiplexers), which can handle hundreds of client connections simultaneously.
  2. When a thread reads and writes data from a client Socket channel, if no data is available, the thread can perform other tasks.
  3. Threads usually use the idle time of non blocking IO to perform IO operations on other channels, so a single thread can manage multiple input and output channels.
  4. Since read and write operations are non blocking, this can fully improve the running efficiency of IO threads and avoid thread suspension caused by frequent I/O blocking.
  5. One I/O thread can handle N client connections and read / write operations concurrently, which fundamentally solves the traditional synchronous blocking I/O connection thread model, and greatly improves the performance, elastic scalability and reliability of the architecture.

4.6.3 Selector class related methods

4.6.4 precautions

  1. The ServerSocketChannel function in NIO is similar to ServerSocket, and the SocketChannel function is similar to Socket.
  2. Description of Selector related methods
  • selector.select(); // block
  • selector.select(1000); // Block for 1000 ms, return after 1000 ms
  • selector.wakeup(); // Wake up selector
  • selector.selectNow(); // No blocking, return immediately

4.7 schematic diagram of NiO non blocking network programming

NIO non blocking network programming related (Selector, SelectionKey, ServerScoketChannel and SocketChannel) relationship sorting diagram

Description of the above figure:

  1. When the client connects, it will get the SocketChannel through ServerSocketChannel.
  2. The Selector listens to the select method and 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, which will be associated with the Selector (Collection).
  5. Further, each SelectionKey is obtained (an event occurs).
  6. After obtaining SocketChannel in reverse through SelectionKey, use channel().
  7. Business processing can be completed through the obtained channel.

4.8 NIO non blocking network programming quick start

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

4.9 SelectionKey

  1. SelectionKey indicates the registration relationship between a Selector and a network channel. There are four types:
    • int OP_ACCEPT: a new network connection can be accepted. The value is 16
    • int OP_CONNECT: indicates that the connection has been established. The value is 8
    • int OP_READ: represents a read operation, and the value is 1
    • int OP_WRITE: represents a write operation. The value is 4

In the source code:

public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
  1. SelectionKey related methods

4.10 ServerSocketChannel

  1. ServerSocketChannel listens for new client Socket connections on the server side
  2. Relevant methods are as follows

4.11 SocketChannel

  1. 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.
  2. The relevant methods are as follows

4.12 NIO network programming application example group chat system

Example requirements:

  1. Write a NIO group chat system to realize simple data communication between server and client (non blocking)
  2. Realize multi group chat
  3. Server side: it can monitor the online and offline users, and realize the message forwarding function
  4. Client: through the Channel, messages can be sent to all other users without blocking, and messages sent by other users can be accepted (transmitted by the server)
  5. Objective: to further understand NIO non blocking network programming mechanism
  6. Schematic analysis and code

code:
//Server:

package com.test.nio.groupchat;

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

public class GroupChatServer {

    //Define attributes
    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();
        }
    }

    public void listen() {
        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 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
    public void readData(SelectionKey key) {
        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 to a string
                String msg = new String(buffer.array());
                //Output this message
                System.out.println("form client:" + msg);
                //Forward messages to other clients (remove yourself) and write a method to handle them
                sendInfoToOtherClients(msg, channel);
            }
        } catch (IOException e) {
            try {
                System.out.println(channel.getRemoteAddress() + "Offline..");
                //Deregister
                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...");
        //Traverse all socketchannels registered to the selector and exclude self
        for (SelectionKey key : selector.keys()) {
            //Get 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 the channel
                dest.write(buffer);
            }
        }
    }

    public static void main(String[] args) {
        //Create server object
        GroupChatServer groupChatServer = new GroupChatServer();
        groupChatServer.listen();
    }
}

//Client:

package com.test.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;

public class GroupChatClient {

    //Define related attributes
    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 to the server
        socketChannel = SocketChannel.open(new InetSocketAddress(HOST, 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 messages 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 available channel...");
            }
        } 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);
        }
    }
}

4.13 NIO and zero copy

4.13.1 basic introduction to zero copy

  1. Zero copy is the key to network programming, and many performance optimizations are inseparable.
  2. In Java programs, the common zero copies are mmap (memory mapping) and sendFile. So, what kind of design are they in the OS? We analyze the two zero copies of mmap and sendFile
  3. In addition, let's take a look at how to use zero copy in NIO

4.13.2 traditional IO data reading and writing

A piece of code for Java traditional IO and network programming

File file = new File("test.txt");
RandomAccessFile raf = new RandomAccessFile(file, "rw");

byte[] arr = new byte[(int) file.length()];
raf.read(arr);

Socket socket = new ServerSocket(8080).accept();
socket.getOutputStream().write(arr);

4.13.3 traditional IO model

DMA:direct memory access direct memory copy (without CPU)

4.13.4 mmap optimization

  1. mmap maps files to kernel buffers through memory mapping. Meanwhile, user space can share kernel space data. In this way, the number of copies from kernel space to user space can be reduced during network transmission. As shown below
  2. mmap diagram

4.13.5 sendFile optimization

  1. In Linux2.1, the sendFile function is provided. Its basic principle is as follows: data directly enters the SocketBuffer from the kernel buffer without going through the user state. At the same time, because it is completely independent of the user state, it reduces a context switch
  2. Schematic and summary

  1. Tip: zero copy from the operating system point of view, there is no cpu copy
  2. In the 2.4 version of Linux, some modifications have been made to avoid copying from the kernel buffer to the Socketbuffer and directly to the protocol stack, thus once again reducing data copying. See the following figure and summary for details:

  1. In fact, the cpu copies the kernel buffer - > socket buffer once. However, the copied information is very small, such as low lenght and offset consumption, which can be ignored

4.13.6 re understanding of zero copy

  1. We say zero copy from the perspective of the operating system. Because there is no duplicate data between kernel buffers (only the kernel buffer has a copy of data).
  2. Zero copy not only brings less data replication, but also other performance advantages, such as less context switching, less CPU cache pseudo sharing, and no CPU checksum calculation.

4.13.7 differences between MMAP and sendFile

  1. mmap is suitable for reading and writing small amounts of data, and sendFile is suitable for transferring large files.
  2. mmap requires 4 context switches and 3 data copies; sendFile requires 3 context switches and at least 2 data copies.
  3. sendFile can use DMA mode to reduce CPU copy, but mmap cannot (it must be copied from the kernel to the Socket buffer).

4.13.8 NIO zero copy cases

Case requirements:

  1. Use traditional IO methods to pass a large file
  2. Use NIO zero copy method to transfer a large file
  3. See how long the two delivery methods take
package com.test.nio.zerocopy;

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

//The server
public class NewIOServer {

    public static void main(String[] args) throws Exception {
        InetSocketAddress address = new InetSocketAddress(7001);
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(address);

        //Create buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(4096);

        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            int readcount = 0;
            while (-1 != readcount) {
                try {
                    readcount = socketChannel.read(byteBuffer);
                } catch (Exception ex) {
                    // ex.printStackTrace();
                    break;
                }
                //
                byteBuffer.rewind(); //Rewind position = 0 mark void
            }
        }
    }
}
package com.test.nio.zerocopy;

import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

public class NewIOClient {

    public static void main(String[] args) throws Exception {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("localhost", 7001));
        String filename = "protoc-3.6.1-win32.zip";
        //Get a file channel
        FileChannel fileChannel = new FileInputStream(filename).getChannel();
        //Ready to send
        long startTime = System.currentTimeMillis();
        //Under linux, the next transferTo method can complete the transfer
        //The next time transferTo is called in windows, it can only send 8m, so it is necessary to transfer files in segments, and
        //Position during transmission = > after class thinking
        //transferTo bottom layer uses zero copy
        long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
        System.out.println("Total bytes sent = " + transferCount + " time consuming: " + (System.currentTimeMillis() - startTime));
        //close
        fileChannel.close();
    }
}

5, Basic introduction to Java AIO

  1. JDK7 introduces asynchronous I/O, or AIO. In I/O programming, two modes are commonly used: Reactor and Proactor. NIO in Java is Reactor. When an event is triggered, the server will be notified and handle it accordingly
  2. AIO, namely NIO2.0, is called asynchronous non blocking IO. AIO introduces the concept of asynchronous channel and adopts the Proactor mode to simplify the programming. The thread can only be started after an effective request is made. Its feature is that the operating system notifies the server program to start the thread for processing after it is completed. It is generally applicable to applications with a large number of connections and a long connection time
  3. At present, AIO has not been widely used. Netty is also based on NIO, not AIO. Therefore, AIO will not be explained in detail. For details, please refer to AIO principle of Java new generation network programming model and AIO introduction of Linux system

Tags: Netty

Posted by Toot4fun on Wed, 01 Jun 2022 14:59:39 +0530