Once the VIP service at the beginning of the network, users rarely, server completely stress-free, when the technology has not advanced now, usually with a thread to handle a request tracking. Because this is the easiest. In fact, everyone knows that the code implementation is that there is a ServerSocket on the server that listens on a certain port. After receiving the connection from the client, it creates a Socket and hands it to a thread for subsequent processing. The thread mainly reads the data transmitted from the client from the Socket, then performs business processing, and writes the result to the Socket and transmits it back to the client. Due to the network, after the Socket is created, it is not always possible to read data from it. It may take a while for the thread to block. When writing data to a Socket, it may also block the thread. Here is an example. The main logic is as follows:Client: Create 20 sockets and connect to the server, then create 20 threads, each thread is responsible for a Socket. Server side: Receive these 20 connections, create 20 Sockets, then create 20 threads, each thread is responsible for a Socket. In order to simulate the server-side Socket can not immediately read the data after creation, let the client’s 20 threads sleep a random number of seconds between 5-10. The client’s 20 threads will send data to the server in the period from the 5th to the 10th, and the 20 threads on the server will continue to receive data.

public class BioServer {

  static AtomicInteger counter = new AtomicInteger(0);
  static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); 
  
  public static void main(String[] args) {
    try {
      ServerSocket ss = new ServerSocket();
      ss.bind(new InetSocketAddress("localhost", 8080));
      while (true) {
        Socket s = ss.accept();
        processWithNewThread(s);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  
  static void processWithNewThread(Socket s) {
    Runnable run = () -> {
      InetSocketAddress rsa = (InetSocketAddress)s.getRemoteSocketAddress();
      System.out.println(time() + "->" + rsa.getHostName() + ":" + rsa.getPort() + "->" + Thread.currentThread().getId() + ":" + counter.incrementAndGet());
      try {
        String result = readBytes(s.getInputStream());
        System.out.println(time() + "->" + result + "->" + Thread.currentThread().getId() + ":" + counter.getAndDecrement());
        s.close();
      } catch (Exception e) {
        e.printStackTrace();
      }
    };
    new Thread(run).start();
  }
  
  static String readBytes(InputStream is) throws Exception {
    long start = 0;
    int total = 0;
    int count = 0;
    byte[] bytes = new byte[1024];
    //start read time
    long begin = System.currentTimeMillis();
    while ((count = is.read(bytes)) > -1) {
      if (start < 1) {
        //first read data time
        start = System.currentTimeMillis();
      }
      total += count;
    }
    //finish time
    long end = System.currentTimeMillis();
    return "wait=" + (start - begin) + "ms,read=" + (end - start) + "ms,total=" + total + "bs";
  }

  static String time() {
    return sdf.format(new Date());
  }
}

public class Client {

  public static void main(String[] args) {
    try {
      for (int i = 0; i < 20; i++) {
        Socket s = new Socket();
        s.connect(new InetSocketAddress("localhost", 8080));
        processWithNewThread(s, i);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  static void processWithNewThread(Socket s, int i) {
    Runnable run = () -> {
      try {
        //sleep random ,simulatation data is not ready
        Thread.sleep((new Random().nextInt(6) + 5) * 1000);
        //more data for more read time
        s.getOutputStream().write(prepareBytes());
        //let server finish data read
        Thread.sleep(1000);
        s.close();
      } catch (Exception e) {
        e.printStackTrace();
      }
    };
    new Thread(run).start();
  }
  
  static byte[] prepareBytes() {
    byte[] bytes = new byte[1024*1024*1];
    for (int i = 0; i < bytes.length; i++) {
      bytes[i] = 1;
    }
    return bytes;
  }
}

 

Time->IP:Port->thread Id: thread count
15:11:52->127.0.0.1:55201->10:1
15:11:52->127.0.0.1:55203->12:2
15:11:52->127.0.0.1:55204->13:3
15:11:52->127.0.0.1:55207->16:4
15:11:52->127.0.0.1:55208->17:5
15:11:52->127.0.0.1:55202->11:6
15:11:52->127.0.0.1:55205->14:7
15:11:52->127.0.0.1:55206->15:8
15:11:52->127.0.0.1:55209->18:9
15:11:52->127.0.0.1:55210->19:10
15:11:52->127.0.0.1:55213->22:11
15:11:52->127.0.0.1:55214->23:12
15:11:52->127.0.0.1:55217->26:13
15:11:52->127.0.0.1:55211->20:14
15:11:52->127.0.0.1:55218->27:15
15:11:52->127.0.0.1:55212->21:16
15:11:52->127.0.0.1:55215->24:17
15:11:52->127.0.0.1:55216->25:18
15:11:52->127.0.0.1:55219->28:19
15:11:52->127.0.0.1:55220->29:20

time->wait data time,read data time,read total->thread Id:current thread count
15:11:58->wait=5012ms,read=1022ms,total=1048576bs->17:20
15:11:58->wait=5021ms,read=1022ms,total=1048576bs->13:19
15:11:58->wait=5034ms,read=1008ms,total=1048576bs->11:18
15:11:58->wait=5046ms,read=1003ms,total=1048576bs->12:17
15:11:58->wait=5038ms,read=1005ms,total=1048576bs->23:16
15:11:58->wait=5037ms,read=1010ms,total=1048576bs->22:15
15:11:59->wait=6001ms,read=1017ms,total=1048576bs->15:14
15:11:59->wait=6016ms,read=1013ms,total=1048576bs->27:13
15:11:59->wait=6011ms,read=1018ms,total=1048576bs->24:12
15:12:00->wait=7005ms,read=1008ms,total=1048576bs->20:11
15:12:00->wait=6999ms,read=1020ms,total=1048576bs->14:10
15:12:00->wait=7019ms,read=1007ms,total=1048576bs->26:9
15:12:00->wait=7012ms,read=1015ms,total=1048576bs->21:8
15:12:00->wait=7023ms,read=1008ms,total=1048576bs->25:7
15:12:01->wait=7999ms,read=1011ms,total=1048576bs->18:6
15:12:02->wait=9026ms,read=1014ms,total=1048576bs->10:5
15:12:02->wait=9005ms,read=1031ms,total=1048576bs->19:4
15:12:03->wait=10007ms,read=1011ms,total=1048576bs->16:3
15:12:03->wait=10006ms,read=1017ms,total=1048576bs->29:2
15:12:03->wait=10010ms,read=1022ms,total=1048576bs->28:1

 

You can see that the server does create a thread for each connection, creating a total of 20 threads. The client enters hibernation for about 5-10 seconds, the data on the simulated connection is not ready, the server-side thread is waiting, and the waiting time is about 5-10 seconds. The client ends the hibernation, writes 1M data to the connection, and the server starts to read the data. The entire reading process takes about 1 second. As you can see, the server-side worker thread spends time on the two processes of ” waiting for data ” and ” reading data .” There are two bad things about this: First, if there are many clients that initiate requests at the same time, the server side will create a lot of threads, which may cause a crash because the upper limit is exceeded. Second, most of the time in each thread is blocked, nothing to do, resulting in a huge waste of resources. At the beginning, it was said that there were very few netizens in that era, so it is impossible to have a large number of requests coming over at the same time. As for the waste of resources, it is wasted. Anyway, idle is also idle. A simple little example: the hotel has 10 tables and is equipped with 10 waiters. As soon as a guest arrives, the lobby manager takes the guest to a table and arranges a waiter to accompany him. Even if the guest does not need service for the time being, the waiter is always standing next to him. It may be a waste, but it is not. This is a VIP service. In fact, VIP mapping is a one-to-one model, mainly on “dedicated” or “private”.

The true multiplexing technology multiplexing technique originally meant that in terms of communication, multiple signals or data (from a macro perspective) are intertwined and transmitted using the same transmission channel. The purpose of this is to make full use of the transmission capacity of the channel on the one hand, and save time and effort to save money on the other hand. In fact, this concept is very “living”. You can give an example by yourself: a small channel of water is flowing, pouring a large amount of table tennis into one end and filtering it with a net at the other end to separate the table tennis from the water. This is a multiplex of “earth”. First, “mix” multiple signals or data at the transmitting end, then transmit on the channel, and finally “separate” the signal or data that you need at the receiving end. . I believe everyone can see that the focus here is to deal with “mixing” and “separation”. There are different ways to deal with different signals or data. For example, the previous cable TV was an analog signal, that is, an electromagnetic wave. A family generally has only one signal line, but it can connect multiple TVs at the same time. Each TV can be changed at any time without affecting each other. This is because waves of different frequencies can be mixed and separated. (Of course, it may not be very accurate, just understand the meaning.) For example, the city’s high-speed rail station generally has several platforms for high-speed rail (simultaneous) docking, but there is only one high-speed railway track between cities, how to ensure so many high-speed rail safety Running? Obviously it is time-sharing, and every high-speed rail has its own moment. More than a high-speed rail exits at different times is equivalent to mixing, and entering the station at different times is equivalent to separation. To summarize, multipath refers to a variety of different signals or data or other things, and multiplexing refers to sharing the same physical link or channel or carrier. It can be seen that the multiplexing technique is a one-to-many model, and the party of “multiple” reuses the side of “one”.

In fact, one-to-many models are mainly embodied in “public” or “shared”.

You look at it first, I will come back to one-on-one service. It is typical to have money and willfulness. Although it is timely and thoughtful, it is not everyone’s enjoyment. After all, it is still more than a “silk”, then come to share Service. So in reality, more of the situation is, after the guest sat down, he will give him a menu, let him look at it first, anyway, it is impossible to order the meal immediately, the waiter will go to other things. From time to time, there will be a waiter passing by the guests and find that the guests have not ordered yet, they will take the initiative to ask if they need to order now. If necessary, the waiter will write a menu for you, and if not, the waiter will continue to move forward. In this case, the overall operation of the hotel is also very good, but the number of waiters is much smaller. Now serving 10 tables, 4 waiters are more than enough. (The savings are all pure profits.) Because 10 tables of guests need to serve at the same time, it will almost never happen. Most of the cases are staggered. If there is one, then it will be better, and it is not 120/119. Back in the code, the situation is very similar, and it can be handled by the same theory. After the connection is established, find a place to put it there, you can leave it alone for a while, and there is no data to read at this time. But the data will come sooner or later, so from time to time to ask each connection there is no data, if any, read the data, if not, continue to ignore it. In fact, this model has long been in Java, Java NIO, where the capital letter “N” is the word “New”, meaning “new”, mainly to distinguish it from the “one-to-one” above. Let’s make a look at it first. Now we need to refine the process of Socket interaction. The client first requests the connection, connect, the server then accepts the connection, accept, then the client writes the data to the connection, write, and then the server reads the data from the connection, read.Like the call scene, the caller dials, connect, called, answer, accept, caller, speak, called, listen. The caller called the called party, indicating that the caller was looking for the called party, so the called party was concerned about connecting the phone and listening to the other party. The client initiates a request to the server, indicating that the client is looking for something on the server side, so the server is concerned with accepting the request and reading the data sent by the other party. Here, accepting the request and reading the data is called an operation of interest to the server. In Java NIO, the operation that accepts the request is represented by OP_ACCEPT, and the operation of reading the data is represented by OP_READ. I decided to go through the restaurant scene first, so that the first time I met Java NIO students are not so confused. It is to directionalally organize the regular scenes, a little bit deliberate, and understand the meaning. 1. Specially set up an “running leg” waiter with a single job responsibilities, which is to ask if the guest needs service. 2, standing at the door to receive guests, was originally the work of the lobby manager, but he did not want to stare at the door, so he entrusted to the errand waiter, you help me stare, someone came to tell me. So the errand waiter had a mission to stalk the lobby manager. Finally came the guest, the errand waiter quickly told the lobby manager. 3. The lobby manager took the guest to the seat and said to the errand attendant that the guest must be the main meal next time, but now I am looking at the menu, I don’t know when I can be optimistic, so you come over from time to time to ask if you need it. Order, if necessary, then call a “order” waiter to write a menu to the guest. Then the errand waiter added another task, that is, staring at the table guests, from time to time to ask, if you need service, then order the waiter to come over and serve. 4, the errand waiter in a query, the guests finally decided to order, the run-ahead waiter quickly found a catering waiter to write a menu for the guests. 5, in this way, the errand waiter must stare at the new guests coming out of the door, but also stare at the guests who have already sat in the door. New guests are coming, and the lobby manager is notified to the reception. The guest who sat down decided to order the meal and informed the ordering waiter to write the menu.Things continue to circulate in this way, everything is fine. The role is clear, the responsibilities are single, and the coordination is very good. The lobby manager and the catering attendant are the providers or implementers of the demand. The errand waiter is the finder of the demand and identifies the type of demand. The receptionist needs to be handed over to the lobby manager, who needs to order the meal to the catering attendant.

Java NIO is coming.

The code is written very fixed and can be used in conjunction with the explanations that follow. This is easy to understand, as follows:

public class NioServer {

  static int clientCount = 0;
  static AtomicInteger counter = new AtomicInteger(0);
  static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); 
  
  public static void main(String[] args) {
    try {
      Selector selector = Selector.open();
      ServerSocketChannel ssc = ServerSocketChannel.open();
      ssc.configureBlocking(false);
      ssc.register(selector, SelectionKey.OP_ACCEPT);
      ssc.bind(new InetSocketAddress("localhost", 8080));
      while (true) {
        selector.select();
        Set<SelectionKey> keys = selector.selectedKeys();
        Iterator<SelectionKey> iterator = keys.iterator();
        while (iterator.hasNext()) {
          SelectionKey key = iterator.next();
          iterator.remove();
          if (key.isAcceptable()) {
            ServerSocketChannel ssc1 = (ServerSocketChannel)key.channel();
            SocketChannel sc = null;
            while ((sc = ssc1.accept()) != null) {
              sc.configureBlocking(false);
              sc.register(selector, SelectionKey.OP_READ);
              InetSocketAddress rsa = (InetSocketAddress)sc.socket().getRemoteSocketAddress();
              System.out.println(time() + "->" + rsa.getHostName() + ":" + rsa.getPort() + "->" + Thread.currentThread().getId() + ":" + (++clientCount));
            }
          } else if (key.isReadable()) {
            
            key.interestOps(key.interestOps() & (~ SelectionKey.OP_READ));
            processWithNewThread((SocketChannel)key.channel(), key);
          }
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  static void processWithNewThread(SocketChannel sc, SelectionKey key) {
    Runnable run = () -> {
      counter.incrementAndGet();
      try {
        String result = readBytes(sc);
        
        key.interestOps(key.interestOps() | SelectionKey.OP_READ);
        System.out.println(time() + "->" + result + "->" + Thread.currentThread().getId() + ":" + counter.get());
        sc.close();
      } catch (Exception e) {
        e.printStackTrace();
      }
      counter.decrementAndGet();
    };
    new Thread(run).start();
  }
  
  static String readBytes(SocketChannel sc) throws Exception {
    long start = 0;
    int total = 0;
    int count = 0;
    ByteBuffer bb = ByteBuffer.allocate(1024);
    
    long begin = System.currentTimeMillis();
    while ((count = sc.read(bb)) > -1) {
      if (start < 1) {
        
        start = System.currentTimeMillis();
      }
      total += count;
      bb.clear();
    }
   
    long end = System.currentTimeMillis();
    return "wait=" + (start - begin) + "ms,read=" + (end - start) + "ms,total=" + total + "bs";
  }
  
  static String time() {
    return sdf.format(new Date());
  }
}

Its general process is as follows: 1. Define a selector, Selector. It is equivalent to setting up an errand waiter. 2. Define a server-side socket channel, ServerSocketChannel, and configure it to be non-blocking. It is equivalent to hiring a lobby manager . 3. Register the socket channel to the selector and set the operation of interest to OP_ACCEPT. The equivalent of the lobby manager said to the errand waiter, help me stare at the door, a guest came to tell me. 4, enter the endless loop, the selector from time to time to choose. Equivalent to the errand waiter to ask and go around again and again . 5. The selector finally selects the channel and finds that the channel needs to be Acceptable. Equivalent to the errands waiter finally found the guests outside the door, the guests need to receive . 6. The server-side socket accepts this channel and starts processing. The equivalent of the errand waiter called the lobby manager and the lobby manager began to receive the reception . 7. Configure the newly accepted channel to be non-blocking and register it on the selector. The channel is interested in OP_READ. The equivalent of the lobby manager took the guest to the seat, gave the guest a menu, and entrusted the guest to the errand waiter, saying that the guest must be the main meal next time, you ask from time to time . 8. The selector continues to be selected from time to time. The equivalent of the errand waiter continues to ask and wander from time to time . 9, the selector finally chose the channel, this time found that the channel needs Readable. Equivalent to the errands waiter finally found a table of guests have a need, it is necessary to order. 10. Give this channel to a new worker thread to handle. Equivalent to the errand waiter called the order waiter, the order waiter began to write menus for the guests . 11. After the worker thread is processed, it is recycled and can be processed by other channels. It is equivalent to ordering the waiter to write a menu, then go, and can write menus for other guests . 12. The selector continues the repeated selection work, not knowing when it is a head. Equivalent to the errands waiter to continue to repeat the inquiry, wandering, do not know where the future is .

I believe you have seen it, the lobby manager is equivalent to the server-side socket, the errand waiter is equivalent to the selector, and the ordering waiter is equivalent to the Worker thread. Start the server-side code, use the same client code, and send 20 requests in the same routine. The result is as follows:


16:34:39->127.0.0.1:56105->1:1
16:34:39->127.0.0.1:56106->1:2
16:34:39->127.0.0.1:56107->1:3
16:34:39->127.0.0.1:56108->1:4
16:34:39->127.0.0.1:56109->1:5
16:34:39->127.0.0.1:56110->1:6
16:34:39->127.0.0.1:56111->1:7
16:34:39->127.0.0.1:56112->1:8
16:34:39->127.0.0.1:56113->1:9
16:34:39->127.0.0.1:56114->1:10
16:34:39->127.0.0.1:56115->1:11
16:34:39->127.0.0.1:56116->1:12
16:34:39->127.0.0.1:56117->1:13
16:34:39->127.0.0.1:56118->1:14
16:34:39->127.0.0.1:56119->1:15
16:34:39->127.0.0.1:56120->1:16
16:34:39->127.0.0.1:56121->1:17
16:34:39->127.0.0.1:56122->1:18
16:34:39->127.0.0.1:56123->1:19
16:34:39->127.0.0.1:56124->1:20


16:34:45->wait=1ms,read=1018ms,total=1048576bs->11:5
16:34:45->wait=0ms,read=1054ms,total=1048576bs->10:5
16:34:45->wait=0ms,read=1072ms,total=1048576bs->13:6
16:34:45->wait=0ms,read=1061ms,total=1048576bs->14:5
16:34:45->wait=0ms,read=1140ms,total=1048576bs->12:4
16:34:46->wait=0ms,read=1001ms,total=1048576bs->15:5
16:34:46->wait=0ms,read=1062ms,total=1048576bs->17:6
16:34:46->wait=0ms,read=1059ms,total=1048576bs->16:5
16:34:47->wait=0ms,read=1001ms,total=1048576bs->19:4
16:34:47->wait=0ms,read=1001ms,total=1048576bs->20:4
16:34:47->wait=0ms,read=1015ms,total=1048576bs->18:3
16:34:47->wait=0ms,read=1001ms,total=1048576bs->21:2
16:34:48->wait=0ms,read=1032ms,total=1048576bs->22:4
16:34:49->wait=0ms,read=1002ms,total=1048576bs->23:3
16:34:49->wait=0ms,read=1001ms,total=1048576bs->25:2
16:34:49->wait=0ms,read=1028ms,total=1048576bs->24:4
16:34:50->wait=0ms,read=1008ms,total=1048576bs->28:4
16:34:50->wait=0ms,read=1033ms,total=1048576bs->27:3
16:34:50->wait=1ms,read=1002ms,total=1048576bs->29:2
16:34:50->wait=0ms,read=1001ms,total=1048576bs->26:2

The server accepts 20 connections, creates 20 channels, and registers them on the selector, without the need for extra threads. When a channel already has data, it will be processed by a thread. Therefore, the time for the thread to “wait for data” is 0, and the time for “reading data” is still about 1 second. Because 20 channels are data-continued, the server side runs up to 6 threads at the same time. In other words, it can be used with a thread pool containing 6 threads. Contrast and conclusion: Dealing with the same 20 requests, one requires 20 threads, and one requires 6 threads, saving 70% of the threads. In this example, the two interested operations share a selector, and the selector runs in the main thread, and the worker thread is the new thread. In fact, there is no requirement for the number of selectors, which thread the selector is running in, or whether a new thread is used to process the request. It should be determined according to the actual situation. For example, redis, a thread related to processing a request, the selector runs inside, the program that handles the request also runs inside, so this thread is both an I/O thread and a worker thread. Of course, you can also use two selectors, one to process OP_ACCEPT and one to handle OP_READ, which are run in two separate I/O threads. For operations that can be done quickly, you can do it directly in the I/O thread. For very time-consuming operations, you must use the Worker thread pool to handle it.

This processing mode is called multiplexed I/O. Multiplexing refers to multiple Socket channels. Multiplexing refers to managing them with only one thread.

A little more analysis of the one-to-one form, a table with a waiter, a Socket assigned a thread, the fastest response, after all, is a VIP, but the efficiency is very low, the waiter is standing most of the time, most of the thread Time is waiting. In the form of multiplexing, all tables share an errand waiter. All Sockets share a selector thread, and the response speed is definitely slower. After all, it is one-to-many. But the efficiency is improved, the order waiter will only pass when the meal is needed, and the worker thread will start working when the data is ready. From VIP to multiplexing, there is a big difference in form. The essence is the one-to-one to one-to-many transition. In fact, it sacrifices the response speed and brings about the improvement of efficiency, but the comprehensive performance is still obtained. Great improvement.

As far as the hotel is concerned, there are several tables with an errand waiter and several tables with a catering waiter. After a period of operation, there will be an optimal solution. As far as the program is concerned, it takes a few selector threads, several working threads, and after evaluation and testing, there will be an optimal solution. Once the optimal solution is reached, it is no longer possible to upgrade, which is also limited by the one-to-many form of multiplexing. Just like the one-to-one formal restrictions. People’s pursuit is endless. How to continue to improve multiplexing? The answer must be subversive, that is, to abandon multiplexing and adopt a completely new form. Taking the restaurant as an example, how to continue to reduce the number of waiters and improve efficiency in the case of optimal solutions? Some friends may have guessed that it is to abandon the waiter service guest mode and change the hotel into a cafeteria. When the guest enters the door, give him the cutlery and tell him the rules of time, no waste, etc., and then leave it alone. Guests choose their own meals, eat them themselves, leave themselves, no need to wait for the waiter, so no longer need a waiter. (Except for the desk.) This mode corresponds to the program, which is actually AIO, and it has been in Java for a long time. Hey, the Java AIO code is very fixed and can be used in conjunction with the explanations that follow. This is easy to understand, as follows:

public class AioServer {

  static int clientCount = 0;
  static AtomicInteger counter = new AtomicInteger(0);
  static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); 
  
  public static void main(String[] args) {
    try {
      AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
      assc.bind(new InetSocketAddress("localhost", 8080));
      
      assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {

        @Override
        public void completed(AsynchronousSocketChannel asc, Object attachment) {
          
          assc.accept(null, this);
          try {
            InetSocketAddress rsa = (InetSocketAddress)asc.getRemoteAddress();
            System.out.println(time() + "->" + rsa.getHostName() + ":" + rsa.getPort() + "->" + Thread.currentThread().getId() + ":" + (++clientCount));
          } catch (Exception e) {
          }
          readFromChannelAsync(asc);
        }

        @Override
        public void failed(Throwable exc, Object attachment) {
          
        }
      });
      
      synchronized (AioServer.class) {
        AioServer.class.wait();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  static void readFromChannelAsync(AsynchronousSocketChannel asc) {
    
    ByteBuffer bb = ByteBuffer.allocate(1024*1024*1 + 1);
    long begin = System.currentTimeMillis();
    
    asc.read(bb, null, new CompletionHandler<Integer, Object>() {
      
      int total = 0;

      @Override
      public void completed(Integer count, Object attachment) {
        counter.incrementAndGet();
        if (count > -1) {
          total += count;
        }
        int size = bb.position();
        System.out.println(time() + "->count=" + count + ",total=" + total + "bs,buffer=" + size + "bs->" + Thread.currentThread().getId() + ":" + counter.get());
        if (count > -1) {
          asc.read(bb, null, this);
        } else {
          try {
            asc.close();
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
        counter.decrementAndGet();
      }

      @Override
      public void failed(Throwable exc, Object attachment) {
        
      }
    });
    long end = System.currentTimeMillis();
    System.out.println(time() + "->exe read req,use=" + (end -begin) + "ms" + "->" + Thread.currentThread().getId());
  }
  
  static String time() {
    return sdf.format(new Date());
  }
}

Its general processing is as follows: 1. Initialize an AsynchronousServerSocketChannel object and start listening. 2. Register a “Complete Processor” accept connection callback through the accept method, that is, CompletionHandler, which is used to receive related operations after the connection. 3. When the client connects, it is accepted by the system, and the AsynchronousSocketChannel object is created, then the callback is triggered, and the object is passed into the callback, and the callback is executed in the Worker thread. 4. In accepting the connection callback, use the accept method again to register the same completion handler object for the system to accept the next connection. It is this kind of registration that can only be used once, so it is necessary to keep registering continuously, and people are designed like this. 5. In accepting the connection callback, use the read method of the AsynchronousSocketChannel object to register another accept data callback for the relevant operation after receiving the data. 6. When the client data comes over, it is accepted by the system and put into the specified ByteBuffer. Then the callback is triggered, and the number of data bytes received this time is passed to the callback. The callback will be in the Worker thread. carried out. 7. In accepting data callbacks, if the data is not accepted, you need to use the read method again to register the same object once, for the system to accept the next data. This is the same as the above routine. 8, the client’s data may be transmitted to the server side multiple times, so the acceptance of the data callback will be executed multiple times until the data is accepted. The data received multiple times is the complete data, this must be handled well. 9, on the ByteBuffer, either large enough to be able to hold the complete client data, so that the data received multiple times can be added directly. Either move the data in the ByteBuffer to another location and store it, then empty the ByteBuffer to let the system load the next accepted data.

Note: If there is not enough space in the ByteBuffer, the system will not load the data, which will cause the client data to always be read, and it is very likely to enter an infinite loop. Start the server-side code, use the same client code, and send 20 requests in the same routine. The result is as follows:


17:20:47->127.0.0.1:56454->15:1

17:20:47->exe read req,use=3ms->15
17:20:47->127.0.0.1:56455->15:2
17:20:47->exe read req,use=1ms->15
17:20:47->127.0.0.1:56456->15:3
17:20:47->exe read req,use=0ms->15
17:20:47->127.0.0.1:56457->16:4
17:20:47->127.0.0.1:56458->15:5
17:20:47->exe read req,use=1ms->16
17:20:47->exe read req,use=1ms->15
17:20:47->127.0.0.1:56460->15:6
17:20:47->127.0.0.1:56459->17:7
17:20:47->exe read req,use=0ms->15
17:20:47->127.0.0.1:56462->15:8
17:20:47->127.0.0.1:56461->16:9
17:20:47->exe read req,use=1ms->15
17:20:47->exe read req,use=0ms->16
17:20:47->exe read req,use=0ms->17
17:20:47->127.0.0.1:56465->16:10
17:20:47->127.0.0.1:56463->18:11
17:20:47->exe read req,use=0ms->18
17:20:47->127.0.0.1:56466->15:12
17:20:47->exe read req,use=1ms->16
17:20:47->127.0.0.1:56464->17:13
17:20:47->exe read req,use=1ms->15
17:20:47->127.0.0.1:56467->18:14
17:20:47->exe read req,use=2ms->17
17:20:47->exe read req,use=1ms->18
17:20:47->127.0.0.1:56468->15:15
17:20:47->exe read req,use=1ms->15
17:20:47->127.0.0.1:56469->16:16
17:20:47->127.0.0.1:56470->18:17
17:20:47->exe read req,use=1ms->18
17:20:47->exe read req,use=1ms->16
17:20:47->127.0.0.1:56472->15:18
17:20:47->127.0.0.1:56473->19:19
17:20:47->exe read req,use=2ms->15
17:20:47->127.0.0.1:56471->17:20
17:20:47->exe read req,use=1ms->19
17:20:47->exe read req,use=1ms->17


17:20:52->count=65536,total=65536bs,buffer=65536bs->14:1
17:20:52->count=65536,total=65536bs,buffer=65536bs->14:1
17:20:52->count=65536,total=65536bs,buffer=65536bs->14:1
17:20:52->count=230188,total=295724bs,buffer=295724bs->12:1
17:20:52->count=752852,total=1048576bs,buffer=1048576bs->14:3
17:20:52->count=131072,total=196608bs,buffer=196608bs->17:2

。。。。。。。。。。。。。。。。。。。。。。

17:20:57->count=-1,total=1048576bs,buffer=1048576bs->15:1
17:20:57->count=-1,total=1048576bs,buffer=1048576bs->15:1
17:20:57->count=-1,total=1048576bs,buffer=1048576bs->15:1
17:20:57->count=-1,total=1048576bs,buffer=1048576bs->15:1
17:20:58->count=-1,total=1048576bs,buffer=1048576bs->15:1
17:20:58->count=-1,total=1048576bs,buffer=1048576bs->15:1
17:20:58->count=-1,total=1048576bs,buffer=1048576bs->15:1

After the system accepts the connection, a callback is executed in the worker thread. And the read method is executed in the callback, the time is 0, because only the callback that accepts the data is registered. After the system receives the data, it puts the data into the ByteBuffer and performs a callback in the worker thread. And the callback can directly use the data in the ByteBuffer. The callback that accepts the data is executed multiple times, and the data received multiple times adds up to exactly the data sent by the client. Because the system is a callback that is triggered after the data is received, the server can run the callback at the same time for up to 3 threads. In other words, the thread pool contains 3 threads. Contrast and conclusion: Dealing with the same 20 requests, one requires 20 threads, one requires 6 threads, one requires 3 threads, and saves 50% of threads.

Note: There is no need to compare the results of this comparison. This is just to illustrate the problem. Haha.

Comparison of three treatment methods

The first is blocking IO, there are two blocking points, the process waiting for data ready and the process of reading data. The second is blocking IO, there is a blocking point, the process of reading data. The third is non-blocking IO, there is no blocking point, when the worker thread starts, the data has been prepared by the system and can be used directly.

It can be seen that this is a process of gradually eliminating the blocking point. Let’s talk about various IOs: there is only one thread, accepting a connection, reading data, processing the business, writing back the result, and accepting the next connection, which is synchronous blocking. There is almost no such usage. A thread and a thread pool. After the thread accepts the connection, it throws it to the thread in the thread pool and accepts the next connection. This is asynchronous blocking. Corresponding to example one. A thread and a thread pool, the thread runs the selector, performs a select operation, takes the ready connection and throws it to the thread in the thread pool, and then performs the next select operation, which is multiplexing, which is asynchronous blocking. Corresponding to example two. A thread and a thread pool, the thread registers an accept callback, the system helps us accept the connection, triggers the callback to execute in the thread pool, and then registers the read callback when executed. The system helps us to accept the data before triggering the callback in the thread. Executed in the pool, is AIO, which is asynchronous non-blocking. Corresponding to example three.

Redis is also multiplexed, but it only has one thread executing the select operation, processing the ready connection, and the whole is serialized, so there is no natural concurrency problem. It can only be classified as synchronous blocking.

BIO is blocking IO, which can be synchronous blocking or asynchronous blocking. AIO is asynchronous IO, only asynchronous non-blocking. So there is no such thing as synchronous non-blocking, because synchronization must be blocked.

Note: The above statement is based on the user program/thread position.

It is recommended to download the code and run it yourself.

Https://github.com/coding-new-talking/java-code-demo.git

Orignal link:https://www.cnblogs.com/lixinjie/p/a-post-tell-clearly-about-io-multiplexing-and-async-io.html