What's the difference between BIO and NIO? Why use - Netty, Java Engineer Interview assault Season 3 download

In order to let you better understand Netty and the reason for its birth, let's start with traditional network programming!

Let's start with BIO

Traditional blocking communication flow

The early Java network related API(java.net package) used socket (socket) for network communication, but only supported the use of blocking functions.

To communicate over the Internet, at least one pair of sockets is required:

  1. The Server Socket running on the server side.
  2. Client Socket running on the client side

The Socket network communication process is shown in the following figure:

The Socket network communication process is divided into the following four steps:

  1. Establish a server and listen for client requests
  2. When the client requests, the server establishes a connection with the client
  3. Data can be transferred between the two ends
  4. close resource

Corresponding to the server and client, it is as follows.

Server side:

  1. Create a ServerSocket object and bind the address (ip) and port number (port): server.bind(new InetSocketAddress(host, port))
  2. Listen for client requests through the accept() method
  3. After the connection is established, read the request information sent by the client through the input stream
  4. Send response information to the client through output
  5. Close related resources

client:

  1. Create a Socket object and connect to the address (ip) and port number (port) of the specified server: socket.connect(inetSocketAddress)
  2. After the connection is established, send the request information to the server through the output
  3. Get the information of the server response through the input stream
  4. Close related resources

A simple demo

In order to facilitate understanding, I wrote a simple code to help you understand.

Server:

public class HelloServer {
    private static final Logger logger = LoggerFactory.getLogger(HelloServer.class);

    public void start(int port) {
        //1. Create a ServerSocket object and bind a port
        try (ServerSocket server = new ServerSocket(port);) {
            Socket socket;
            //2. Listen for client requests through the accept() method. This method will block until a connection is established
            while ((socket = server.accept()) != null) {
                logger.info("client connected");
                try (ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                     ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) {
                   //3. Read the request information sent by the client through the input stream
                    Message message = (Message) objectInputStream.readObject();
                    logger.info("server receive message:" + message.getContent());
                    message.setContent("new content");
                    //4. Send response information to the client through output
                    objectOutputStream.writeObject(message);
                    objectOutputStream.flush();
                } catch (IOException | ClassNotFoundException e) {
                    logger.error("occur exception:", e);
                }
            }
        } catch (IOException e) {
            logger.error("occur IOException:", e);
        }
    }

    public static void main(String[] args) {
        HelloServer helloServer = new HelloServer();
        helloServer.start(6666);
    }
}

The accept () method of ServerSocket is a blocking method, that is, when ServerSocket calls accept () to wait for the connection request from the client, it will block and continue to execute the code until it receives the connection request sent by the client. Therefore, we need to start a thread for each Socket connection (which can be done through the thread pool).

The above server code is only for demonstration and does not consider the concurrent connection of multiple clients.

client:

/**
 * @author shuang.kou
 * @createTime 2020 16:56:00, May 11
 */
public class HelloClient {

    private static final Logger logger = LoggerFactory.getLogger(HelloClient.class);

    public Object send(Message message, String host, int port) {
        //1 \. Create a Socket object and specify the address and port number of the server
        try (Socket socket = new Socket(host, port)) {
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            //2. Send request information to the server through output
            objectOutputStream.writeObject(message);
            //3. Obtain the information of the server response through the input stream
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            return objectInputStream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            logger.error("occur exception:", e);
        }
        return null;
    }

    public static void main(String[] args) {
        HelloClient helloClient = new HelloClient();
        helloClient.send(new Message("content from client"), "127.0.0.1", 6666);
        System.out.println("client receive message:" + message.getContent());
    }
}

Sent message entity class:

/**
 * @author shuang.kou
 * @createTime 2020 17:02:00, May 11
 */
@Data
@AllArgsConstructor
public class Message implements Serializable {

    private String content;
}

First run the server and then the client. The console output is as follows:

Server:

[main] INFO github.javaguide.socket.HelloServer - client connected
[main] INFO github.javaguide.socket.HelloServer - server receive message:content from client

client:

client receive message:new content

Resource consumption is a serious problem

Obviously, the code fragment I demonstrated above has a very serious problem: it can only handle the connection of one client at the same time. If you need to manage multiple clients, you need to create a separate thread for the client we request. As shown in the figure below:

The corresponding Java code may be as follows:

new Thread(() -> {
   // Create socket connection
}).start();

However, this will lead to a very serious problem: waste of resources.

We know that threads are very valuable resources. If we use one thread for each connection, it will lead to better and better threads. It's best to reach the limit, and then we can't create threads to process requests. If it is not handled well, it may even go down directly.

Many people will ask: is there any way to improve it?

Although thread pool can be improved, it has not fundamentally solved the problem

Of course! A simple and practical improvement is to use thread pools. The thread pool can also make the creation and recovery cost of threads relatively low, and we can specify the maximum number of threads that can be created in the thread pool, so as not to create too many threads and unreasonably consume machine resources.

ThreadFactory threadFactory = Executors.defaultThreadFactory();
ExecutorService threadPool = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(100), threadFactory);
threadPool.execute(() -> {
     // Create socket connection
 });

But no matter how you optimize and change. It can not change the fact that its bottom layer is still the BIO model of synchronous blocking, so it can not fundamentally solve the problem.

In order to solve the above problems, NIO, a synchronous non blocking I/O model, is introduced in Java 1.4.

Look at NIO again

Netty actually gets a high-performance framework based on Java NIO technology. It is necessary to be familiar with the basic concepts of NIO for learning and better understanding netty!

First met NIO

NIO is a synchronous non blocking I/O model. NIO framework is introduced in Java 1.4, corresponding to java.nio package, and provides abstractions such as channel, selector and Buffer.

N in NIO can be understood as non blocking. It is no longer New (it has been out for a long time).

NIO supports buffer oriented and channel based I/O operation methods.

NIO provides two different Socket channel implementations of SocketChannel and ServerSocketChannel corresponding to Socket and ServerSocket in the traditional BIO model. Both channels support blocking and non blocking modes:

  1. Blocking mode: basically not used. It is just like the traditional network programming, which is relatively simple, but the performance and reliability are not good. For low load and low concurrency applications, you can barely use it to improve development speed and better maintainability
  2. Non blocking mode: Contrary to blocking mode, non blocking mode is very friendly for high load and high concurrency (Network) applications, but programming is troublesome, which is criticized by most people. Therefore, it led to the birth of Netty.

Interpretation of NIO core components

NIO includes the following core components:

  • Channel
  • Buffer
  • Selector
  • Selection Key

Improve development speed and better maintainability
2. Non blocking mode: Contrary to blocking mode, non blocking mode is very friendly for high load and high concurrency (Network) applications, but programming is troublesome, which is criticized by most people. Therefore, it led to the birth of Netty.

Interpretation of NIO core components

NIO includes the following core components:

  • Channel
  • Buffer
  • Selector
  • Selection Key

Tags: Java Back-end Interview Programmer

Posted on Thu, 02 Sep 2021 17:54:40 -0400 by Todd88