Java BIO model (providing single-threaded and multi-threaded code examples)

Directory

    • 1. Introduction to BIO features
    • 2. BIO code implementation
      • 2.1. Client code preparation
      • 2.2. Server-side single-thread processing
        • 2.2.1. Server code
        • 2.2.2. Blocking code analysis
        • 2.2.3. Problems
      • 2.3. Server-side multi-thread processing
        • 2.3.1. Server code
        • 2.3.2. Problems

1. Introduction to BIO features

  • BIO (blocking I/O): Synchronous blocking IO, each I/O operation (such as reading or writing) will cause the thread to be blocked until the operation is completed.
  • The BIO method is suitable for architectures with a relatively small and fixed number of connections. This model is suitable for lower concurrency requirements. Each connection usually requires an independent thread. Thread pool management can also be used, which was the only option before JDK1.4. The program is simple and easy to understand.
  • BIO operates based on byte streams and character streams.
  • Use classes in the java.io package, such as InputStream and OutputStream, which provide blocking I/O operations.

2. BIO code implementation

2.1. Client code preparation

Here we prepare a general client that will send two messages to the server with an interval of 2 seconds for later testing.

public class BioClient{<!-- -->
    public static void main(String[] args) {<!-- -->
        for (int i=0;i<1;i + + ) {<!-- -->
            // Multi-threaded processing is used here to adapt to the multi-threaded server
            new Thread(()->{<!-- -->
                BioClient bioClient = new BioClient();
                try {<!-- -->
                    bioClient.run();
                } catch (Exception e) {<!-- -->
                    e.printStackTrace();
                }
            }).start();
        }

    }

    public void run() throws Exception{<!-- -->
        Socket socket = null;
        InputStream inputStream = null;
        OutputStream outputStream = null;
        BufferedReader br = null;
        try {<!-- -->
            socket = new Socket("127.0.0.1", 9998);
            System.out.println(getTime() + "Establish a connection with the server");
            Thread.sleep(2000);
            outputStream = socket.getOutputStream();
            PrintStream ps = new PrintStream(outputStream);
            System.out.println(getTime() + "Send message to the server: hello");
            ps.println("hello");
            Thread.sleep(2000);
            System.out.println(getTime() + "Send messages to the server every 2 seconds: & amp;end");
            ps.println(" & amp;end");
            ps.flush();

            inputStream = socket.getInputStream();
            br = new BufferedReader(new InputStreamReader(inputStream));
            String s = null;
            System.out.println(getTime() + "Blocking and waiting for data sent from the server to the client");
            while ((s = br.readLine()) != null) {<!-- -->
                System.out.println(getTime() + Thread.currentThread().getName() + "Data received from the server:" + s);
            }
        } finally {<!-- -->
            br.close();
            inputStream.close();
            outputStream.close();
            socket.close();
        }
    }

    public static String getTime(){<!-- -->
        return "time=" + System.currentTimeMillis()/1000 + "\t";
    }
}

2.2. Server-side single-thread processing

2.2.1, server code
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class BioServer {<!-- -->
    public static void main(String[] args) throws IOException {<!-- -->

        ServerSocket serverSocket = new ServerSocket(9998);
        while (true) {<!-- -->
            // Block waiting for socket connection
            Socket accept = serverSocket.accept();
            System.out.println(getTime() + "Establish a connection port=" + accept.getPort());
            InputStream inputStream = accept.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
            //Block after connection and wait for connection data input
            String s = null;
            while ((s = br.readLine()) != null) {<!-- -->
                System.out.println(getTime() + Thread.currentThread().getName() + "Received data: " + s);
                // When receiving the &end message, send a message to the client and close the stream to jump out of the current loop
                if (" & amp;end".equals(s)) {<!-- -->
                    PrintStream ps = new PrintStream(accept.getOutputStream());
                    ps.println("Go to bed early");
                    ps.flush();
                    System.out.println(getTime() + "Send a message to the client when the &end message is received");
                    br.close();
                    inputStream.close();
                    accept.close();
                    break;
                }
            }
            System.out.println(getTime() + "One connection processing is completed, waiting for the next connection");
            System.out.println("--------------------------------------------- ------------------");
        }
    }

    public static String getTime() {<!-- -->
        return "time=" + System.currentTimeMillis() / 1000 + "\t";
    }
}
2.2.2. Blocking code analysis

There are two places in BIO where threads will be blocked. The first one is in serverSocket.accept();. When no connection is established, it will wait until there is a connection. When a connection comes in, it will start executing the subsequent process. This is what I did In the example, after the connection is established, the input stream will be obtained to read the message sent by the client. When the br.readLine() method is called to actually read the data, if the client has not sent the message yet but only established the connection, This step will also be blocked. After the client establishes the socket connection, it will pause for 2 seconds before sending the message to the server, so it will block for 2 seconds. When the client pauses, it will send a message to the server. When the message is sent to On the server side, the server’s br.readLine() method reads the data and starts executing the subsequent process. Because it is read cyclically, br will be executed again after the first message is read. .readLine() method. At this time, it will block and wait for client messages. After the client sends the first message, it will send another message to the server every 2 seconds. The message content is & amp;end, the server’s br.readLine() method reads the second piece of data and determines whether the message content is & amp;end. If it is & amp;end sends a Go to bed early to the client. After the sending is completed, all streams and connections will be closed and the loop will be jumped out, waiting for the next connection.

2.2.3. Problems
  • The BIO single thread can only handle one connection at a time. When the br.readLine() method is called to read data, it will be blocked. The subsequent logic will only be executed when the connection is disconnected or the data is read once. That is to say, if the connection is not disconnected and the client never sends a message to the server, the server will always be blocked. After reading the data once, the code here will continue to read in a loop until it reads & amp; end will jump out of the loop by itself. If the loop is not broken out and the connection is not disconnected, other connections will be processed.
  • BIO generally cannot use one thread to process multiple connections. In fact, it is possible. Imagine that we store the obtained sockets in an array and process them every time two sockets are received. In this way, one thread can process two socket connections. , but it will definitely not be done because serverSocket.accept(); will block. If only one connection comes, then this connection will never be processed.
  • In response to these problems, single thread cannot handle it, so let’s use multi-thread processing and continue the analysis.

2.3. Server-side multi-thread processing

2.3.1, server code
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class BioConcurrentServer {<!-- -->
    public static void main(String[] args) throws IOException {<!-- -->
        ServerSocket serverSocket = new ServerSocket(9998);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));

        while (true) {<!-- -->
            // Block waiting for socket connection
            Socket accept = serverSocket.accept();
            System.out.println(getTime() + "Establish a connection port=" + accept.getPort());

            // Hand over the socket connection to the thread pool for processing
            threadPoolExecutor.execute(()->{<!-- -->
                try {<!-- -->
                    System.out.println(getTime() + "Start processing socket messages");
                    run(accept);
                } catch (Exception e) {<!-- -->
                    throw new RuntimeException(e);
                }
            });
        }
    }

    public static void run(Socket accept) throws Exception {<!-- -->
        InputStream inputStream = accept.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));

        //Block after connection and wait for connection data input
        String s = null;
        while ((s = br.readLine()) != null) {<!-- -->
            System.out.println(getTime() + Thread.currentThread().getName() + "Received data: " + s);
            // When receiving the &end message, send a message to the client and close the stream to jump out of the current loop
            if (" & amp;end".equals(s)) {<!-- -->
                PrintStream ps = new PrintStream(accept.getOutputStream());
                ps.println("Go to bed early");
                ps.flush();
                System.out.println(getTime() + "Send a message to the client when the &end message is received");
                br.close();
                inputStream.close();
                accept.close();
                break;
            }
        }
        System.out.println(getTime() + "One connection processing is completed, waiting for the next connection");
        System.out.println("--------------------------------------------- ------------------");

    }

    public static String getTime() {<!-- -->
        return "time=" + System.currentTimeMillis() / 1000 + "\t";
    }
}
2.3.2. Problems
  • After receiving the client connection, use the thread pool to process Read / Write, so that multiple connections can be processed at the same time. It seems that there is no problem, but think about it carefully if the client just establishes a connection. If you send a message to the server, will the server always block when the child thread calls br.readLine()? Then it will always occupy the thread. This also explains why there is usually one connection in BIO. Just need a thread.