preface

Article content output source: Pull hook education Java high salary training camp;

BIO, NIO and AIO are often asked in interviews. I know a general idea, but I can’t explain it. Just at the training camp the teacher talked about this part, so I sorted it out.

concept

BIO/NIO/AIO these are just some forms of input/output streams for data transmission. That is, they are essentially input and output streams. There are only synchronous asynchronous, blocking and non-blocking issues.

Synchronous asynchronous

Synchronization and asychronize refer to the interaction between an application and the kernel.

Synchronization: User processes wait for OR train I/O operations to check whether I/O operations are ready.

Example: We boil water and wait for it to boil and drink it. From the time we start to boil the water, we wait or wait a while to see if the water is boiling. That’s synchronization.

Asynchronous: When an asynchronous process call is made, the caller does not get the result immediately. Instead, after the call has been made, the called notifies the caller through status, notifications, or callback functions to handle the call. When asynchronous I/O is used, Java delegates I/O read and write operations to the OS and sends the address and size of the data buffer to the OS, which supports asynchronous I/O operations

Example: We boil water, using a kettle with a hint. Once we start boiling water, we don’t have to wait around or check it later, we can do other things. The kettle will tell us when the water is ready and we know it is ready to drink.

Blocking non-blocking

Blocking and non-blocking are different ways for a process to access data, depending on the ready state of the IO operation. Simply put, it is an implementation of read and write operation methods. Blocking reads and writes wait, while non-blocking reads and writes understand to return a status value.

Let’s boil water again:

I boil water, and I always wait by, waiting for the water to boil. This is synchronous blocking. Because this is the only thing I’m doing.

I boil the water, check in every now and then to see if it’s boiling. This is synchronous nonblocking. Because I only have to look at it for a while while I can do other things.

I boil the water, I boil the kettle, I don’t have to look. But I didn’t do anything else. I just waited. This is called asynchronous blocking.

I boil the water, I boil the kettle, I don’t have to look. I’m doing something else. Just let me know when the water’s ready. This is asynchronous non-blocking.

BIO

Now that we’ve seen synchronous asynchrony, blocking, and non-blocking, let’s look at BIO, NIO, and AIO, respectively.

BIO: Blocked I/O synchronization. B for blocking. ‘ ‘

That is, there is one thread for each request. Each thread waits for a stream of IO for input. If it is not received, it remains blocked. Process the IO stream until it is retrieved.


We can write an example. The simplest socket. Synchronous blocking mode.

We create a server and perform the following steps on the server.

Create a ServerSocket.

2. Waiting for the request information sent by the client in an infinite loop.

Parse the received information.

4. Send the return message.

The code is as follows:


public class Server {


    public static void main(String[] args) {
   try {  ServerSocket socket = new ServerSocket(8080);   while(true) { System.out.println("waiting......"); Socket accept = socket.accept(); // Synchronously blocked System.out.println("beging......");  new Thread(()->{  byte[] bytes = new byte[1024];  try {  int len = accept.getInputStream().read(bytes); // Synchronously blocked String cilentMessage=new String(bytes,0,len);  System.out.println(cilentMessage);  String backMessage="server accepted message successful. message is "+cilentMessage;  accept.getOutputStream().write(backMessage.getBytes());  accept.getOutputStream().flush();  } catch (IOException e) {  e.printStackTrace();  }  }).start();  }  } catch (IOException e) {  e.printStackTrace();  }  } } Copy the code

Next, let’s write a client. The client does these things.

1. Continuously read the console input, one line at a time.

2. Create a socket connection to connect to the server.

3. Send messages to the server.

4. Receive messages sent by the server.

5. Parse the message sent by the server.

6. Close the socket connection.

The code is as follows:


public class Client {


    public static void main(String[] args) {
  try {  System.out.println("client begin......");  Scanner scanner = new Scanner(System.in);  while (scanner.hasNext()){  Socket socket = new Socket("127.0.0.1", 8080); String message=scanner.nextLine();  socket.getOutputStream().write(message.getBytes());  socket.getOutputStream().flush();  System.out.println("server back message......");  byte[] bytes = new byte[1024];  int len = socket.getInputStream().read(bytes);  String backMessage=new String(bytes,0,len);  System.out.println(backMessage);  socket.close();  }  } catch (IOException e) {  e.printStackTrace();  }  } } Copy the code

Start the server and client respectively, and the test results are as follows:

image-20200713113231211

BIO is the simplest way to interact with IO. The implementation is also very simple, synchronous blocking, that is, when no message is received, it will always wait and do nothing else. Therefore, it is not efficient and will not be used in general projects.

NIO

NIO: synchronizes no-blocking IO (I/O).

The server implementation mode is one request one channel, that is, the connection request sent by the client will be registered with the multiplexer, and the multiplexer will only start a thread to process the IO request after polling the connection.

So let’s start with some basic concepts:

channel

The most important concept introduced by NIO is the Channel. A Channel is an index data Channel, that is, the Channel through which data is transmitted. Data can be read from a buffer to a channel or from a channel to a buffer.

The buffer

The buffer buffer is like a buffer that temporarily stores data, which can be read from a buffer to a channel or from a channel to a buffer.

The selector

A selector is a bridge between threads and channels. With selectors, many IO channels can be monitored and maintained with a single thread.


So what’s special about NIO?

As we saw above, a single thread can handle multiple channels. There’s one connection for each channel, so when you create a connection, you register it with a multiplexer, which is a selector. And then the selector performs a channel that transmits the data every period of time by round-hunting.

One example is that every child in kindergarten needs to be accompanied by a teacher to go to the toilet. NIO is to assign a teacher to the whole kindergarten. The teacher will regularly ask the children who want to go to the toilet to raise their hands, and then lead the children to go to the toilet. This prevents the teacher from having to go to the bathroom for every child. Simplify the process by multiplexing.

We will now also write a NIO example, again divided into client side and server side. Let’s start with the server side.

The service side

The server performs the following operations:

1. Declare multiplexer

2. Define the read and write cache

3. Write initialization methods

4. Write startup classes

5. Write execution methods

The main way to initialize init is

1. Enable the multiplexer

2. Open the channel

3. Set non-blocking

4. Bind ports

5. Register channel

The overall code is as follows:


public class NIOServer extends Thread{

//1. Declare the multiplexer    private Selector selector;
//2. Define the read/write buffer private ByteBuffer readBuffer = ByteBuffer.allocate(1024);  private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);  //3. Define a constructor to initialize the port public NIOServer(int port) {  init(port);  }  //4. The main method starts the thread public static void main(String[] args) {  new Thread(new NIOServer(8888)).start();  }  / / 5. Initialization private void init(int port) {   try {  System.out.println("The server is starting......"); //1) Enable the multiplexer this.selector = Selector.open(); //2) Enable the service channel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //3) Set to non-blocking serverSocketChannel.configureBlocking(false); //4) Bind ports serverSocketChannel.bind(new InetSocketAddress(port)); / * ** selectionKey. OP_ACCEPT -- Receives a connection continuation event, indicating that the server is listening for a client connection and is ready to accept it* selectionKey. OP_CONNECT -- Connection ready event indicating that the client has successfully established a connection to the server* selectionKey. OP_READ -- Read ready event, indicating that the channel has readable data and is ready to read.* selectionkey. OP_WRITE -- Write ready event indicating that data is ready to be written to the channel (channels are currently available for write operations)* ///5) Register and mark the service connection status as ACCEPT serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);  System.out.println("Server startup complete");  } catch (IOException e) {  e.printStackTrace();  }  }   public void run() { while (true) { try { //1. Execute this method when at least one channel is selected this.selector.select(); //2. Obtain the selected channel number set Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator(); / / 3. Traversal keys while (keys.hasNext()) {  SelectionKey key = keys.next(); //4. The current key needs to be removed from the driven knife set. If it is not removed, the corresponding logic will be executed next time, causing service disorder keys.remove(); //5. Check whether the channel is valid if (key.isValid()) {  try { //6. Check whether the connection can be established if (key.isAcceptable()) {  accept(key);  }  } catch (CancelledKeyException e) { // The connection is abnormally disconnected key.cancel();  }   try { //7. Check whether it is readable if (key.isReadable()) {  read(key);  }  } catch (CancelledKeyException e) { // The connection is abnormally disconnected key.cancel();  }   try { //8. Check whether it is writable if (key.isWritable()) {  write(key);  }  } catch (CancelledKeyException e) { // The connection is abnormally disconnected key.cancel();  }  }  }  } catch (IOException e) {  e.printStackTrace();  }  }  }   private void accept(SelectionKey key) {  try { //1. The current channel is registered with selector ServerSocketChannel in init ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); //2. Block method, client initiates request return. SocketChannel channel = serverSocketChannel.accept(); ServerSocketChannel is set to non-blocking channel.configureBlocking(false); //4. Set the channel flag of the corresponding client. Set the secondary channel to readable channel.register(this.selector, SelectionKey.OP_READ);  } catch (IOException e) {  e.printStackTrace();  }  }  // Use channels to read data private void read(SelectionKey key) {  try{ // Clear the cache this.readBuffer.clear(); // Get the current channel object SocketChannel channel = (SocketChannel) key.channel(); // Read the channel's data (data sent by the customer) into the cache. int readLen = channel.read(readBuffer); // If there is no data in the channel if(readLen == -1 ){ // Close the channel key.channel().close(); // Close the connection key.cancel();  return;  } // There are cursors in Buffer, cursors do not reset, we need to call flip reset. Otherwise, the readings are inconsistent this.readBuffer.flip(); // Create a valid byte length array byte[] bytes = new byte[readBuffer.remaining()]; // The data in the read buffer is stored in a byte array readBuffer.get(bytes);  System.out.println("Received from client"+ channel.getRemoteAddress() + ":"+ new String(bytes,"UTF-8")); // Register the channel, marked as a write operation channel.register(this.selector,SelectionKey.OP_WRITE);   }catch (Exception e){   }  }  // Write to the channel private void write(SelectionKey key) { // Clear the cache this.readBuffer.clear(); // Get the current channel object SocketChannel channel = (SocketChannel) key.channel(); // Input data Scanner scanner = new Scanner(System.in);   try {  System.out.println("About to send data to client...");  String line = scanner.nextLine(); // Write the input data to Buffer writeBuffer.put(line.getBytes("UTF-8")); // Reset the cache cursor writeBuffer.flip();  channel.write(writeBuffer);  channel.register(this.selector,SelectionKey.OP_READ);  } catch (Exception e) {  e.printStackTrace();  }  } } Copy the code

In the run method, the selected channel number is obtained and traversed to determine whether the status is acceptable, readable, or writable. Perform different operations respectively.

The client

The client side is relatively simple.

1. Open the channel

2. Connect to the server

3. Write data to the channel

Parse the data in the channel.


public class NIOClient {
    public static void main(String[] args) {
// Create remote address        InetSocketAddress address  = new InetSocketAddress("127.0.0.1", 8888); SocketChannel channel = null; // Define the cache ByteBuffer buffer = ByteBuffer.allocate(1024);  try { // Open the channel channel = SocketChannel.open(); // Connect to the remote server channel.connect(address);  Scanner sc = new Scanner(System.in);  while (true) { System.out.println("Client is about to send data to server...");  String line = sc.nextLine();  if(line.equals("exit")) { break;  } // Console input data is written to cache buffer.put(line.getBytes("UTF-8")); // Reset the buffer cursor buffer.flip(); // Data is sent to data channel.write(buffer); // Clear the cached data buffer.clear();  // Read the data returned by the server int readLen = channel.read(buffer);  if(readLen == -1){  break;  } // Reset the buffer cursor buffer.flip();  byte[] bytes = new byte[buffer.remaining()]; // Read data into a byte array buffer.get(bytes);  System.out.println("Received data from server:"+ new String(bytes,"UTF-8"));  buffer.clear();  }  } catch (IOException e) {  e.printStackTrace();  } finally {  if(null ! = channel){ try {  channel.close();  } catch (IOException e) {  e.printStackTrace();  }  }  }  } }  Copy the code

test

The server receives the message from the client and sends the message to the client.


Client: Sends messages to the server and receives messages from the server.


AIO

Asynchronous non-blocking IO. A representative asynchronize

When a stream is available for reading, the operating system sends it to the buffer of the read method and notifies the application. For write operations, the operating system notifies the application when the stream of the write method is written. Therefore, both read and write are asynchronous, and the callback function is called upon completion.

Usage scenario: The architecture with a large number of connections and long connections (heavy operation), such as album server. Focus on the call OS to participate in concurrent operations, programming is more complex.

So let’s create an example as well. The main difference from AIO is asynchronous non-blocking, that is, reading and writing through child threads.

The service side

On the server side we do a few things.

1. Declare multiplexer selector.

2. Define read/write buffers

Initialize the selector and channel

4. The main thread starts.

Let’s start by writing a ServerMain as a startup class.

public class ServerMain {

// Define a selector    private Selector selector;

 / * ** To initialize, do the following:* 1. Enable the multiplexer* 2. Enable the service channel* 3. Set it to non-blocking* 4. Bind ports* 5. Mark the selector status as acceptable, indicating that channels can be registered with the selector.* / private void init() {  try {  System.out.println("init......"); // Enable the multiplexer selector = Selector.open(); // Open the channel ServerSocketChannel channel = ServerSocketChannel.open(); // Set it to non-blocking channel.configureBlocking(false); // Bind ports channel.bind(new InetSocketAddress(8080)); // mark the selector status as acceptable/ * ** selectionKey. OP_ACCEPT -- Receives a connection continuation event, indicating that the server is listening for a client connection and is ready to accept it* selectionKey. OP_CONNECT -- Connection ready event indicating that the client has successfully established a connection to the server* selectionKey. OP_READ -- Read ready event, indicating that the channel has readable data and is ready to read.* selectionkey. OP_WRITE -- Write ready event indicating that data is ready to be written to the channel (channels are currently available for write operations)* / channel.register(selector, SelectionKey.OP_ACCEPT);   System.out.println("init finished......");  } catch (IOException e) {  e.printStackTrace();  }  }  // Define the startup method.  public static void main(String[] args) {  ServerMain main = new ServerMain();  main.init();  main.process();  }   private void process() {/ / round while(true) { try { // Search the selector every two seconds. Thread.sleep(2*1000); // The number of channels selected. At least one channel is selected. int count = selector.select();  System.out.println("channel count is "+count);   Set<SelectionKey> selectionKeys = selector.selectedKeys();  Iterator<SelectionKey> iterator = selectionKeys.iterator();  while(iterator.hasNext()){ / / get the key SelectionKey key = iterator.next(); // Fetch the key from the iterator iterator.remove();  new Thread(new Server(selector,key)).start();  }   } catch (Exception e) {  e.printStackTrace();  }  }  } } Copy the code

The main method does two things; Initialize and create child threads.

Initialize the

1. Enable the multiplexer2. Enable the service channel3. Set it to non-blocking4. Bind ports5. Mark the selector status as acceptable, indicating that channels can be registered with the selector.Copy the code

Creating child threads

1. Round search every 2s to obtain the number of channels from the selector.2. Obtain the selected channel number set3, traverseDelete the current key from the iterator5. Create child threadsCopy the code

Child threads are the ones that actually do the receiving, reading and writing. We create a Server class as follows:

public class Server extends Thread{

// Define a selector    private Selector selector;

// Define the read/write buffer// Define the read/write buffer private ByteBuffer readBuffer=ByteBuffer.allocate(1024);  private ByteBuffer writeBuffer=ByteBuffer.allocate(1024);   private SelectionKey key;   public Server(Selector selector,SelectionKey key){  this.selector=selector;  this.key=key;  }   @Override  public void run() {  try {  process();  } catch (Exception e) {  e.printStackTrace();  }  }    private void process() throws Exception {  try{ // Check whether the key is valid if (key.isValid()) { // If acceptable if (key.isAcceptable()) {  System.out.println(Thread.currentThread().getName()+"accept");  accept(key);  }  // If readable if(key.isReadable()){  System.out.println(Thread.currentThread().getName()+"read");  read(key);  }  // if writable if(key.isWritable()){  System.out.println(Thread.currentThread().getName()+"write");  write(key);  }  }  }catch (CancelledKeyException e){  key.cancel();  }  }  / * ** Write data to the channel. Write data to the channel from buffer. * @param key * / private void write(SelectionKey key) throws IOException {  writeBuffer.clear();   SocketChannel channel = (SocketChannel) key.channel();   String message = channel.toString(); // Write to the buffer writeBuffer.put(message.getBytes("UTF-8")); // Reset the cache cursor writeBuffer.flip(); // Write to channel from buffer channel.write(writeBuffer);   // Re-mark as readable channel.register(selector,SelectionKey.OP_READ);  }  / * ** Use channels to read data. The main idea is to read data from the channel into the read cache. * @param key * / private void read(SelectionKey key) throws IOException {  readBuffer.clear();   SocketChannel channel = (SocketChannel)key.channel();   int len = channel.read(readBuffer);  // If the channel has no data if(len==-1){ // Close the channel key.channel().close(); / / off key key.cancel();  return;  }  // There are cursors in Buffer, cursors do not reset, we need to call flip reset. Otherwise, the readings are inconsistent readBuffer.flip(); // Create a valid byte length array byte[] bytes = new byte[readBuffer.remaining()]; // The data in the read buffer is stored in a byte array readBuffer.get(bytes);   String clientMessage = new String(bytes, "UTF-8");  System.out.println("accepted client message are "+clientMessage);  // Register the channel, marked as a write operation channel.register(selector,SelectionKey.OP_WRITE);  }  / * ** Set the channel to accept client data and make the channel readable. * @param key * / private void accept(SelectionKey key) throws IOException { //1. Obtain the channel ServerSocketChannel socketChannel = (ServerSocketChannel) key.channel(); // Block the method to get the client request SocketChannel channel = socketChannel.accept();  // Set it to non-blocking channel.configureBlocking(false); // Set the channel flag of the corresponding client, used when setting the secondary channel to be readable channel.register(selector,SelectionKey.OP_READ);  }  } Copy the code

The read and write cache is set up to get the selector and key from the parent class. The key is used to determine whether the current channel status is received, read, or write. Corresponding to different methods.

A readable operation reads data from a channel into a buffer.

Writable operation that reads data from a buffer into a channel and returns it to the client.

The client

The client is relatively simple and mainly does the following things:

1. Open a channel

2. Connect to the server

3. Write data to the channel

4. Receive the data returned by the channel.

Let’s write a client class.

public class Client extends Thread{

    private int index;


 public Client(int index){  this.index=index;  }   @Override  public void run() {  process(index);  }   public void process(int i) {   InetSocketAddress inetSocketAddress = new InetSocketAddress(8080);   ByteBuffer buffer = ByteBuffer.allocate(1024);  // Open the channeltry (SocketChannel channel = SocketChannel.open();) {// Connect to remote server channel.connect(inetSocketAddress);   buffer.clear();  String meaage = "client send message...."+i; // Write to the cache buffer.put(meaage.getBytes("UTF-8"));  buffer.flip(); // Write to the channel channel.write(buffer);   buffer.clear();  // Read data from the server int len = channel.read(buffer);  if(len==-1){  return;  }   buffer.flip();  byte[] bytes = new byte[buffer.remaining()];   buffer.get(bytes);  String serverMessage = new String(bytes, "UTF-8");  System.out.println(i+" accepted message "+serverMessage);   } catch (IOException e) {  e.printStackTrace();  }   } } Copy the code

Let’s write a main class that simulates the effect of generating multiple channels simultaneously.

public class ClientMain {

    public static void main(String[] args) {

        for(int i=0; i<10; i++){ if(i%3==0){  try {  Thread.sleep(1000*1);  } catch (InterruptedException e) {  e.printStackTrace();  }  }  new Client(i).start();  }   } } Copy the code

test

Okay, let’s start the test, starting the server.


Once started, no channels are blocked.

Next we start the client.

At this point, the server can receive the channel and then retrieve it every two seconds to operate.

Information received by the client from the server.


conclusion

Interested partners can hands-on practice, more impression yo

This article is formatted using MDNICE