java Network Programming Practice-Native NIO Non-Blocking Communication Network Programming Practice

Posted by nike121 on Mon, 22 Jun 2020 00:19:57 +0200

Preface

Last mentioned to improve our RPC framework, this week take the time to explore the native NIO non-blocking network programming ideas that JDK provides to us.NIO libraries were introduced in JDK 1.4.NIO makes up for the deficiency of the original I/O by providing high-speed, block-oriented I/O in standard Java code.


Main differences between BIO and NIO

1. Flow-oriented and buffer-oriented

The first big difference between Java NIO and BIO is that BIO is stream-oriented and NIO is buffer-oriented.Java IO stream-oriented means that one or more bytes are read from the stream at a time until all bytes are read, and they are not cached anywhere.In addition, it cannot move the data in the stream back and forth.If you need to move the data read from the stream back and forth, you need to cache it in a buffer first.The Java NIO buffer-oriented approach is slightly different.Data is read into a buffer that it processes later and can be moved back and forth as needed.This increases the flexibility of the process.However, you also need to check that the buffer contains all the data you need to process.Also, make sure that when more data is read into the buffer, it does not overwrite data that has not been processed in the buffer.

2. Blocking and non-blocking

Various streams of the Java BIO are blocked.This means that when a thread calls read() or write(), the thread is blocked until some data is read or written completely.The thread cannot do anything else during this time.

The Java NIO's non-blocking mode allows a thread to send requests to read data from a channel, but it only gets data that is currently available and nothing if no data is currently available.Instead of keeping the thread blocked, the thread can continue to do other things until the data becomes readable.The same is true for non-blocking writing.A thread requests to write some data to a channel, but does not have to wait for it to write completely. The thread can do something else at the same time.Threads typically use non-blocking IO idle time to perform IO operations on other channels, so a single thread can now manage multiple input and output channels.

3. NIO-specific Selector mechanism

The Java NIO selector allows a single thread to monitor multiple input channels. You can register multiple channels to use one selector, and then use a separate thread to "select" channels: those channels already have inputs that can be processed, or select channels that are ready for writing.This selection mechanism makes it easy for a single thread to manage multiple channels.

 

Today, based on the above understanding, we will implement the network programming of an end-to-end non-blocking IO.


Actual Design

Client Section

/**
* @author andychen https://blog.51cto.com/14815984
* @description: NIO Client Core Processor
*/
public class NioClientHandler implements Runnable {
   //Server Host
   private final String host;
   //Service Port
   private final int port;
   /**Define NIO selectors: used to register and monitor events
    * Select the type of event to listen for: OP_READ Read Event / OP_WRITE Write Event
    * OP_CONNECT Client Connection Event / OP_ACCEPT Server Receives Channel Connection Events
    */
   private Selector selector = null;
   //Define Client Connection Channels
   private SocketChannel channel = null;
   //Is the running state activated
   private volatile boolean activated=false;
   public NioClientHandler(String host, int port) {
       this.port = port;
       this.host = host;
       this.init();
   }

   /**
    * Processor Initialization
    * Responsible for connection preparation
    */
   private void init(){
       try {
           //Create and open selector
           this.selector =  Selector.open();
           //Establish and open listening channel
           this.channel = SocketChannel.open();
           /**
            * Set channel communication mode to non-blocking, NIO defaults to blocking
            */
           this.channel.configureBlocking(false);
           //Activate Running State
           this.activated = true;
       } catch (IOException e) {
           e.printStackTrace();
           this.stop();
       }
   }

   /**
    * Connect to Server
    */
   private void connect(){
       try {
           /**
            * Connect to the server because the communication mode was set to be non-blocking
            * This immediately returns whether the TCP handshake has been established.
            */
           if(this.channel.connect(new InetSocketAddress(this.host, this.port))){
               //After the connection is established, read event concerns are registered on the channel, and the client triggers processing as soon as it receives data
               this.channel.register(this.selector, SelectionKey.OP_READ);
           }
           else{
               //If the connection handshake is not established, continue to focus on the connection event on the channel and continue with subsequent processing logic once the connection is established
               this.channel.register(this.selector, SelectionKey.OP_CONNECT);
           }
       } catch (IOException e) {
           e.printStackTrace();
           this.stop();
       }
   }

   /**
    * Selector Event Iterative Processing
    * @param keys Selector Event KEY
    */
   private void eventIterator(Set<SelectionKey> keys){
       SelectionKey key = null;
       //Iterators are used here because key s are removed when iteration is required
       Iterator<SelectionKey> it = keys.iterator();
       while (it.hasNext()){
           key = it.next();
           //Remove event key first to avoid multiple processing
           it.remove();
           //Handle Iteration Events
           this.proccessEvent(key);
       }
   }

   /**
    * Handle what happened
    * @param key Selector Event KEY
    */
   private void proccessEvent(SelectionKey key){
       //Handle only valid event types
       if(key.isValid()){
           try {
               //Handle on Event Channel
               SocketChannel socketChannel = (SocketChannel) key.channel();
               /**Handle connection ready events
               * */
               if(key.isConnectable()){
                   //Detect connection completion to avoid NotYetConnectedException exception
                   if(socketChannel.finishConnect()){
                       System.out.println("Has completed connection with server..");
                       /**
                        * Read events are of particular concern to the channel, and write events of NO are not.
                        * Reason: Write buffers are considered idle most of the time and are frequently selected by selectors (which wastes CPU resources).
                        *       Therefore, they should not be registered frequently;
                        * Only after a portion of the data has been flushed out of the buffer when the written data exceeds the free space of the write buffer,
                        * Notify the application to write when space is available;
                        * Write events should be closed immediately after the application has finished writing
                        */
                        socketChannel.register(this.selector, SelectionKey.OP_READ);
                   }else{//If a connection is not established here it is generally considered a network or other reason to temporarily exit
                       this.stop();
                   }
               }
               /**
                * Handle Read Events
                */
               if(key.isReadable()){
                   //Create a memory buffer where JVM heap memory is used
                   ByteBuffer buffer = ByteBuffer.allocate(Constant.BUF_SIZE);
                   //Read data from channel to buffer
                   int length = socketChannel.read(buffer);
                   if(0 < length){
                       /**
                        * Read-write conversion, NIO fixed paradigm
                        */
                       buffer.flip();
                       //Get buffer free space
                       int size = buffer.remaining();
                       byte[] bytes = new byte[size];
                       //Read Buffer
                       buffer.get(bytes);
                       //Get Buffer Data
                       String result = new String(bytes,"utf-8");
                       System.out.println("Recevied server message: "+result);
                   }else if(0 > length){
                       //Cancel focus on current event, close channel
                       key.cancel();
                       socketChannel.close();
                   }
               }
           } catch (Exception e) {
               key.cancel();
               if(null != key.channel()){
                   try {
                       key.channel().close();
                   } catch (IOException ex) {
                       ex.printStackTrace();
                   }
               }
               e.printStackTrace();
           }
       }
   }

   /**
    * Write data to peer
    * @param data
    */
   public void write(String data){
       try {
           byte[] bytes = data.getBytes();
           ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
           //Put data in write buffer
           buffer.put(bytes);
           buffer.flip();
           this.channel.write(buffer);
       } catch (IOException e) {
           e.printStackTrace();
       }
   }

   /**
    * Stop running
    */
   public void stop(){
       this.activated = false;
       System.exit(-1);
   }

   /**
    * Client Communication Business Kernel Implementation
    */
   @Override
   public void run() {
       //Set up a server connection
       this.connect();
       //Continuously monitor events
       while (this.activated){
           try {
               //Listen for events to occur, return directly if they occur; otherwise block until the event occurs
               this.selector.select();
           } catch (IOException e) {
               e.printStackTrace();
               this.stop();
           }
           //Get the type of event that occurred
           Set<SelectionKey> keys = this.selector.selectedKeys();
           //Iterate event handling
           this.eventIterator(keys);
       }
       //Close selector
       if(null != this.selector){
           try {
               this.selector.close();
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
       this.stop();
   }
}

/**
* @author andychen https://blog.51cto.com/14815984
* @description: NIO Client Launcher
*/
public class NioClientStarter {
   private static NioClientHandler clientHandler = null;

   /*Start Running Client*/
   public static void main(String[] args) {
       try {
           clientHandler = new NioClientHandler(Constant.SERV_HOST, Constant.SERV_PORT);
           new Thread(clientHandler).start();
       } catch (Exception e) {
           e.printStackTrace();
       }
       /**
        * Send real-time data from console to peer
        */
       Scanner scanner = new Scanner(System.in);
       while (true){
           String data = scanner.next();
           if(null != data && !"".equals(data)){
               clientHandler.write(data);
           }
       }
   }
}


Service End Section

/**
* @author andychen https://blog.51cto.com/14815984
* @description: NIO Server-side Core Processor
*/
public class NioServerHandler  implements Runnable{
   private final int port;
   //Define selector
   private Selector selector = null;
   /**
    * Defining a service-side channel: a client-like approach
    */
   private ServerSocketChannel channel = null;
   //Is Server Run Activated
   private volatile boolean activated = false;
   public NioServerHandler(int port) {
       this.port = port;
       this.init();
   }

   /**
    * Initialize Processor
    * Responsible for preparing for running monitoring and receiving
    */
   private void init(){
       try {
           //Create and open selector
           this.selector = Selector.open();
           //Create and open listening channel
           this.channel = ServerSocketChannel.open();
           /**
            * Set channel communication mode to non-blocking (NIO defaults to blocking)
            */
           this.channel.configureBlocking(false);
           //Bind listening service ports
           this.channel.socket().bind(new InetSocketAddress(this.port));
           /**
            * Events of first interest registered on a server-side channel
            */
           this.channel.register(this.selector, SelectionKey.OP_ACCEPT);
           //Set Run State Activation
           this.activated = true;
       } catch (IOException e) {
           e.printStackTrace();
           this.stop();
       }
   }

   /**
    * Out of Service
    */
   public void stop(){
       this.activated = false;
       try {
           //Close selector
           if(null != this.selector){
               if(this.selector.isOpen()){
                   this.selector.close();
               }
               this.selector = null;
           }
           //Close Channel
           if(null != this.channel){
               if(this.channel.isOpen()){
                   this.channel.close();
               }
               this.channel = null;
           }
       } catch (IOException e) {
           e.printStackTrace();
       }
       System.exit(-1);
   }

   /**
    * Processing events iteratively
    * @param keys The type of event that occurred
    */
   private void eventIterator(Set<SelectionKey> keys){
       //SelectionKey key = null;
       Iterator<SelectionKey> it = keys.iterator();
       while (it.hasNext()){
           SelectionKey key = it.next();
           /**
            * Remove from iterator first to avoid repetition later
            */
           it.remove();
           //Handle Events
           this.proccessEvent(key);
       }
   }

   /**
    *
    * @param key Select the event KEY to execute
    */
   private void proccessEvent(SelectionKey key){
       //Processing only for valid event KEY
       if(key.isValid()){
           try {
               /**
                * Handle Channel Receive Data Events
                */
               if(key.isAcceptable()){
                   /**
                    * Note that the channel to receive events here is the server-side channel
                    */
                   ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                   //Receive Client Socket
                   SocketChannel channel = serverChannel.accept();
                   //Set it as non-blocking
                   channel.configureBlocking(false);
                   //Then register the read events for this channel
                   channel.register(this.selector, SelectionKey.OP_READ);
                   System.out.println("Build connection with client..");
               }
               /**
                * Handle Read Events
                */
               if(key.isReadable()){
                   System.out.println("Reading client data...");
                   SocketChannel channel = (SocketChannel) key.channel();
                   //Open up memory space to receive data
                   ByteBuffer buffer = ByteBuffer.allocate(Constant.BUF_SIZE);
                   //Read data into buffer
                   int length = channel.read(buffer);
                   if(0 < length){
                       //Read-Write Switching
                       buffer.flip();
                       //More Buffer Data to Build Converted Byte Array
                       byte[] bytes = new byte[buffer.remaining()];
                       //Read byte data from buffer
                       buffer.get(bytes);
                       //Decode data
                       String data = new String(bytes, "utf-8");
                       System.out.println("Recevied data: "+data);
                       //Send receive response to peer
                       String answer = "Server has recevied data:"+data;
                       this.reply(channel, answer);
                   }else if(0 > length){
                       //Cancel Handled Events
                       key.cancel();
                       channel.close();
                   }
               }
               /**
                * Handle write events
                */
               if(key.isWritable()){
                   SocketChannel channel = (SocketChannel) key.channel();
                   //Get buffer for write event
                   ByteBuffer buffer = (ByteBuffer) key.attachment();
                   //If there is data in the buffer, brush to the opposite end
                   if(buffer.hasRemaining()){
                        int length = channel.write(buffer);
                        System.out.println("Write data "+length+" byte to client.");
                   }else{
                       //Continue listening for read events if there is no data
                       key.interestOps(SelectionKey.OP_READ);
                   }
               }
           } catch (IOException e) {
               key.cancel();
               e.printStackTrace();
           }
       }
   }

   /**
    * Answer to End
    * @param msg Answer message
    */
   private void reply(SocketChannel channel, String msg){
       //Message Encoding
       byte[] bytes = msg.getBytes();
       //Open write buffer
       ByteBuffer buffer = ByteBuffer.allocate(Constant.BUF_SIZE);
       //Write data to buffer
       buffer.put(bytes);
       //Switch to Read Events
       buffer.flip();
       /**
        * In order to avoid write-empty or write-overflow buffers, write event listening is established while retaining the previous read listening.
        * buffer passed in as a listening attachment for write operations
        */
       try {
           channel.register(this.selector, SelectionKey.OP_WRITE |SelectionKey.OP_READ, buffer);
       } catch (ClosedChannelException e) {
           e.printStackTrace();
       }
   }
   /**
    * Server listens for running core business implementations
    */
   @Override
   public void run() {
       while (this.activated){
           try {
               /**
                * Run to this method to block until an event occurs before returning
               * */
               this.selector.select();
               //Get monitored events
               Set<SelectionKey> keys = this.selector.selectedKeys();
               //In iterators, handle different events
               this.eventIterator(keys);
           } catch (IOException e) {
               e.printStackTrace();
               this.stop();
           }
       }
   }
}

/**
* @author andychen https://blog.51cto.com/14815984
* @description: NIO Network Programming Server Start Class
*/
public class NioServerStart {

   /**
    * Run Server Side Monitoring
    * @param args
    */
   public static void main(String[] args) {
       String serverTag = "server: "+Constant.SERV_PORT;
       NioServerHandler serverHandler = null;
       try {
           serverHandler = new NioServerHandler(Constant.SERV_PORT);
           new Thread(serverHandler, serverTag).start();
           System.out.println("Starting "+serverTag+" listening...");
       } catch (Exception e) {
           e.printStackTrace();
           if(null != serverHandler){
               serverHandler.stop();
           }
       }
   }
}


Multiple Verification Results



summary

From the above facts, we can see that NIO network programming implementation is slightly more complex than BIO.Buffer-oriented mechanisms are indeed much more flexible than flow-oriented mechanisms; services run more smoothly than blocked IO; and unique selector mechanisms allow NIOs to support larger concurrencies, but with slightly higher learning and development costs, they can be used selectively in projects.

At present, the excellent IO framework which is used a lot is not Netty. Many excellent RPC framework's bottom level is also based on Netty expansion and development.Next time, we'll show you the beauty of Netty's web programming.

Topics: Programming Java network Netty