Basic knowledge of java (returning to nature) -- chapter2 (IO and NIO)

Posted by mabus on Sun, 16 Jan 2022 07:00:57 +0100

Traditional BIO mode
For the traditional IO model, one Server interfaces with N clients. After the clients are connected, an execution thread is allocated to each client.
characteristic:
① After each client connection arrives, a thread will be allocated to the client. It is used for reading and writing data, encoding and decoding and business calculation.
② There is a linear relationship between the number of connections created by the server and the number of threads that can be created by the server

Disadvantages (depending on characteristics):
① The concurrency of the server depends on the number of threads that can be created by the server
② The server thread not only needs IO to read and write data, but also needs business computing
③ The server first obtains the client connection, then reads the data, and finally writes the data. These processes are blocked. In the case of poor network, it reduces the utilization of threads and reduces the throughput of the server.

IO exercise 1
Single sending and single receiving: the client sends one line of data and the server receives one line of data. The rules of the communication architecture shall be consistent with the communication mechanism between the server and the client

public class client {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1",9999);
            OutputStream os = socket.getOutputStream();
            //Because when you accept it, you accept a line of characters to print
            //It is better to use print stream
            PrintStream ps = new PrintStream(os);
            ps.println("Hello world");
            ps.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

public class Server {
    public static void main(String[] args) {
        try {
            //1. Create a ServerSocket object to be used for server port registration
            ServerSocket ss = new ServerSocket(9999);
            //2. Create a server socket
            Socket server = ss.accept();
            //3. Create a server input stream
            InputStream is = server.getInputStream();
            //Create a character input stream to read the character data passed in by the client
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            if ((msg = br.readLine()) != null){
                System.out.println("Information received by the server" + msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

IO exercise 2
Multiple sending and multiple receiving: the client sends multiple messages and the server receives multiple messages (by creating threads)
Disadvantages:
① Each Socket will create a thread after receiving it. Thread competition and context switching affect performance
② Each thread will occupy stack space and CPU resources
③ Not every Socket performs IO operations, which is meaningless thread processing
④ When the client concurrency increases, the server presents linear thread overhead. The greater the access, the system will overflow the thread stack and fail to create the thread, which will eventually lead to process downtime or deadlock, so it can not provide services to the outside world

package javabasic.moreclient;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author Lyyyys
 * @version 1.0
 * @date 2021/7/12 9:21
 */
public class Client {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1",9999);
            OutputStream os = socket.getOutputStream();
            PrintStream ps = new PrintStream(os);
            Scanner scanner = new Scanner(System.in);
            while (true){
                System.out.printf("client:");
                String msg = scanner.nextLine();
                ps.println(msg);
                ps.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

package javabasic.moreclient;

import javabasic.iodemo.Server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Lyyyys
 * @version 1.0
 * @date 2021/7/12 9:08
 */

/*
* Requirements: the server receives the request information sent by multiple clients
* Idea: each time the server receives a request from a client, it establishes a thread to communicate
* */
public class server {
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(9999);
            while (true){
                Socket socket = ss.accept();
                ServerThreadSocket threadSocket = new ServerThreadSocket(socket);
                threadSocket.run();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

package javabasic.moreclient;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;

/**
 * @author Lyyyys
 * @version 1.0
 * @date 2021/7/12 9:15
 */
public class ServerThreadSocket extends Thread{
    private Socket socket;

    public ServerThreadSocket(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.printf("The message received by the server is" + msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


IO exercise 3: improve IO exercise 2
Multiple sending and multiple receiving: the client sends multiple messages and the server receives multiple messages (pseudo asynchronous io is realized by creating a message queue of thread pool)

package javabasic.fakenio;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author Lyyyys
 * @version 1.0
 * @date 2021/7/12 10:09
 */
public class client {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1",9999);
            OutputStream os = socket.getOutputStream();
            PrintStream ps = new PrintStream(os);
            Scanner scanner = new Scanner(System.in);
            while (true){
                System.out.printf("client:");
                String msg = scanner.nextLine();
                ps.println(msg);
                ps.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

package javabasic.fakenio;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author Lyyyys
 * @version 1.0
 * @date 2021/7/12 9:58
 */
public class HandlerSocketPool {
    private ExecutorService executorService;
    /*
    *public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
    * */
    public HandlerSocketPool(int maximumPoolSize,int queueSize){
        executorService = new ThreadPoolExecutor(3,maximumPoolSize,120
                , TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(queueSize));
    }

    public void execute(Runnable target){
        executorService.execute(target);
    }
}

package javabasic.fakenio;

import jdk.net.Sockets;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Lyyyys
 * @version 1.0
 * @date 2021/7/12 9:56
 */
/*
* Implementation of pseudo asynchronous io communication
* */
public class server {
    public static void main(String[] args) {
        try {
            //1. Create a server registration port
            ServerSocket ss = new ServerSocket(9999);
            //2. Create thread pool object
            HandlerSocketPool pool = new HandlerSocketPool(10,12);
            while (true){
                //3. After receiving the request from the client, connect the object
                Socket socket = ss.accept();
                ServerRunnableTarget target = new ServerRunnableTarget(socket);
                pool.execute(target);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

package javabasic.fakenio;

import jdk.net.Sockets;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;

/**
 * @author Lyyyys
 * @version 1.0
 * @date 2021/7/12 10:06
 */
public class ServerRunnableTarget implements Runnable{
    private Socket socket;

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

    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println("The data received by the server is" + msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

IO exercise 4
Based on the case of file upload in BIO mode, the server uses to create threads linearly according to the number of connections
Idea: first read the file suffix with the data stream, and then read the data of the file.

package javabasic.fileupload;

import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

/**
 * @author Lyyyys
 * @version 1.0
 * @date 2021/7/12 10:27
 */
/*
* Requirements: the client sends any type of file data to the server to save
*
* */
public class Client {
    public static void main(String[] args) {
        FileInputStream fis = null;
        DataOutputStream dos = null;
        try {
            fis = new FileInputStream("D:\\Photo\\01.txt");
            Socket socket = new Socket("127.0.0.1",9999);
            OutputStream os = socket.getOutputStream();
            dos = new DataOutputStream(os);
            dos.writeUTF(".txt");
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = fis.read(buffer)) > 0){
                dos.write(buffer,0,len);
            }
            dos.flush();
            socket.shutdownOutput();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(dos != null){
                try {
                    dos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

package javabasic.fileupload;

import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Lyyyys
 * @version 1.0
 * @date 2021/7/12 10:27
 */

/*
* Requirements: the server can receive files of any type of data from the client and save them to the disk of the server
* */
public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(9999);
            while (true){
                Socket socket = ss.accept();
                SocketThread thread = new SocketThread(socket);
                thread.run();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

package javabasic.fileupload;

import java.io.DataInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.UUID;

/**
 * @author Lyyyys
 * @version 1.0
 * @date 2021/7/12 11:24
 */
public class SocketThread extends Thread{
    private Socket socket;

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

    @Override
    public void run() {
        FileOutputStream fos = null;
        DataInputStream dis = null;
        try {
            InputStream is = socket.getInputStream();
            dis = new DataInputStream(is);
            String suffix = dis.readUTF();
            fos = new FileOutputStream("D:\\Photo\\server\\" + UUID.randomUUID() + suffix);
            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len = dis.read(buffer)) > 0){
                fos.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

Common methods of IO practice

public class Demo1 {


    public static void main(String[] args) {


    }

    //Recursively list all files in a directory:
    private static void listAllFiles(File dir){
        if(dir == null || !dir.exists()){
            return;
        }
        if(dir.isFile()){
            System.out.println(dir.getName());
            return;
        }

        for (File file:dir.listFiles()) {
            listAllFiles(file);
        }

    }


    //Copy file
    public static void copyFile(String src,String dest) throws IOException {

        FileInputStream in = new FileInputStream(src);
        FileOutputStream out = new FileOutputStream(dest);

        byte[] buffer = new byte[20*1024];

        while (in.read(buffer,0,buffer.length) != -1){
            out.write(buffer);
        }

        in.close();
        out.close();
    }

    //Realize line by line output of the content of the text file
    public static void readFileContent(String filePath) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(filePath));
        String line ="";

        while(br.readLine() != null){
            System.out.println(line = br.readLine());
        }

        //Why only one window needs to be closed, because bufferReader adopts decorator mode
        //Calling the close method of BufferReader will also call the close method of FileReader
        br.close();
    }

}

Three key points of NIO

1, Passage
Channels differ from flows in that
Streams can only move in one direction (a stream must be a subclass of InputStream or OutputStream), and channels are bidirectional and can be used for reading, writing, or both. Channels include the following types:
① FileChannel: read and write data from files;
② Datagram channel: read and write data in the network through UDP;
③ SocketChannel: read and write data in the network through TCP;
④ ServerSocketChannel: it can listen to new TCP connections and create a SocketChannel for each new connection.

2, Buffer
All data sent to a channel must be put into the buffer first. Similarly, any data read from the channel must be read into the buffer first. In other words, the channel will not be read or written directly, but will pass through the buffer first. A buffer is essentially an array, but it is not just an array. Buffers provide structured access to data and can also track the read / write process of the system.

public static ByteBuffer allocate(int capacity)  
public static ByteBuffer allocateDirect(int capacity)  

The memory overhead generated by the first allocation method is in the JVM, while the overhead generated by the other allocation method is outside the JVM, so it is system level memory allocation. When a java program receives external data, it is first obtained by the system memory, and then copied from the system memory to the JVM memory for use by the Java program.
Difference: the data volume is small. The first one is faster because it is directly allocated to the JVM memory, but the latter is better when the data volume is large, because the first one needs to be copied from the system memory to the JVM memory.

public class demo2 {
    public static void main(String[] args) throws IOException {
        FileInputStream fio = new FileInputStream("src");
        //Gets the channel of the input byte stream
        FileChannel fiochannel = fio.getChannel();
        FileOutputStream fos = new FileOutputStream("dist");
        //Gets the channel of the output byte stream
        FileChannel foschannel = fos.getChannel();
        //Allocate 1024 bytes for the buffer
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);

        while (true){
            //Read data from the input channel into the buffer
            int r = fiochannel.read(byteBuffer);
            if(r == -1)
                break;
            //Buffer switching read and write
            byteBuffer.flip();
            //The output channel writes the contents of the buffer
            foschannel.write(byteBuffer);
            //Empty the contents of the buffer
            byteBuffer.clear();
        }
    }
}

3, Selector
NIO implements the Reactor model in IO multiplexing. A Thread uses a Selector selector to listen for events on multiple channels through polling, so that one Thread can handle multiple events.

ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);

By configuring the monitored Channel channel as non blocking, when the IO event on the Channel has not arrived, it will not enter the blocking state and wait all the time, but continue to poll other channels to find the Channel where the IO event has arrived for execution
When registering the channel to the selector, you also need to specify the specific events to register, mainly including the following categories:

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;

① SelectionKey.OP_ACCEPT -- receive connection continuation event, indicating that the server has listened to the client connection and the server can receive the connection
② SelectionKey.OP_CONNECT -- connection ready event, indicating that the connection between the client and the server has been established successfully
③ SelectionKey.OP_READ -- read ready event, indicating that the channel has readable data and can be read (the channel has data and can be read)
④ SelectionKey.OP_WRITE -- write ready event, indicating that data can be written to the channel (the channel can be used for write operation at present)

1. When registering selectionkey with the channel OP_ After the read event, if the client write s data to the cache, it will isReadable()=true when polling next time;
2. When registering selectionkey with the channel OP_ After the write event, you will find that isWritable() in the current polling thread is always true, if it is not set to other events

4.IO multiplexing
NIOClient instance

package javabasic.niodemo;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

/**
 * @author Lyyyys
 * @version 1.0
 * @date 2021/7/18 11:39
 */
public class NIOClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8888);
        OutputStream out = socket.getOutputStream();
        String s = "hello world";
        //The transmission format of front and rear IO is the same, byte
        out.write(s.getBytes());
        out.close();
    }
}

NIOServer instance

package javabasic.niodemo;

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

/**
 * @author Lyyyys
 * @version 1.0
 * @date 2021/7/18 11:39
 */
public class NIOServer {
    public static void main(String[] args) throws Exception {
        //1. Create selector
        Selector selector = Selector.open();
        //2. Register the channel on the selector
        ServerSocketChannel sschannel = ServerSocketChannel.open();
        sschannel.register(selector, SelectionKey.OP_ACCEPT);
        sschannel.configureBlocking(false);

        //Set the address of the bound ServerSocket
        ServerSocket ssocket = sschannel.socket();
        InetSocketAddress address = new InetSocketAddress("127.0.0.1",8888);
        ssocket.bind(address);

        //4. Event cycle
        while (true){
            //3. Listening events
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            if(iterator.hasNext()){
                SelectionKey key = iterator.next();
                if(key.isAcceptable()){
                    ServerSocketChannel sschannel1 = (ServerSocketChannel) key.channel();

                    //The server creates a SocketChannel for each connection
                    SocketChannel sChannel = sschannel1.accept();
                    sChannel.configureBlocking(false);
                    sChannel.register(selector,SelectionKey.OP_READ);
                }else if(key.isReadable()){
                    SocketChannel sChannel = (SocketChannel) key.channel();
                    System.out.println(readDataFromSocketChannel(sChannel));
                    sChannel.close();
                }
                iterator.remove();
            }
        }

    }

    private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        StringBuffer data = new StringBuffer();

        while (true){
            int n = sChannel.read(buffer);
            if(n == -1){
                break;
            }
            buffer.flip();
            int limit = buffer.limit();
            char[] dst = new char[limit];
            for (int i = 0; i < limit; i++) {
                dst[i] = (char) buffer.get(i);
            }
            data.append(dst);
            buffer.clear();
        }
        return data.toString();
    }
}

Topics: NIO