Java nionionio non blocking network communication

Posted by reyes99 on Thu, 06 Feb 2020 06:05:39 +0100

1. Blocking and non blocking

  • Traditional IO flows are blocking. That is, when a thread calls read() or write(), the thread is blocked until some data is read or written, and the thread cannot perform other tasks during this period. Therefore, when the network communication is completed for IO operation, the threads will block, so the server must provide each client with an independent thread for processing. When the server needs to process a large number of clients, the performance drops sharply.
  • Java NIO is non blocking. When a thread reads and writes data from a channel, if no data is available, the thread can perform other tasks. Threads usually use the free time of non blocking IO to perform IO operations on other channels, so a single thread can manage multiple input and output channels. Therefore, NIO allows the server side to use one or a limited number of threads to handle all clients connected to the server side at the same time.

2. Selector

2.1 basic concepts

The selector is a multiplexer of the SelectableChannel object. The selector can monitor the IO status of multiple selectablechannels at the same time. That is to say, a single thread can manage multiple channels by using the selector. Selector is the core of non blocking io.

SelectableChannel is an abstract class, which can be reused by Selector. The implementation class is shown in the figure below

SelectableChannel
AbstractSelectableChannel
SocketChannel
ServerSocketChannel
DatagramChannel
Pipe.SinkChannel
Pipe.SourceChannel

To be used with a selector, an instance of this class must first be registered via the register method. This method returns a new SelectionKey object representing the registration of the channel and selector.

Once the selector is registered, the channel remains registered until it is unregistered. This includes any resources that are unassigned to the channel through the selector.

2.2 application of selector
  • Create Selector: create a Selector by calling the Selector.open() method.
  • Register channel with selector: SelectableChannel.register(Selector sel, int ops)

When calling register(Selector sel, int ops) to register a selector for a channel, the listener event of the selector for the channel needs to be specified by the second parameter ops.

Event types that can be listened to (can be represented by four constants of SelectionKey):

  • Read: selectionkey.op'read (1)
  • Write: selectionkey.op'write (4)
  • Connection: selectionkey.op'connect (8)
  • Receive: selectionkey.op'accept (16)

If you listen to more than one event during registration, you can use the bitwise OR operator to connect

Common methods of Selector

Method Description
Set keys() All SelectionKey collections. Represents the Channel registered on the Selector
selectedKeys() The SelectionKey collection selected. Returns the selected key set for this Selector
int select() Monitor all registered channels. When there are IO operations to be processed among them, the method returns and adds the deserved SelectionKey to the selected SelectionKey collection. The method returns the number of these channels.
int select(long timeout) select() operation with timeout time can be set
int selectNow() Execute an immediate return select() operation, which does not block the thread
Selector wakeup() Make an unreturned select() method return immediately
void close() Close the selector
2.3 SelectionKey

SelectionKey: indicates the registration relationship between SelectableChannel and Selector. Each time a channel is registered with a Selector, an event (selection key) is selected. The selection key contains two sets of operations represented as integer values. Each bit of the operation set represents a type of optional operation supported by the channel of the key
Do.

Method Description
int interestOps() Get collection of events of interest
int readyOps() Gets the collection of operations that the channel is ready for
SelectableChannel channel() Get registration channel
Selector selector() Return selector
boolean isReadable() Check whether the read event in Channal is ready
boolean isWritable() Check whether write events in Channal are ready
boolean isConnectable() Check whether the connection is ready in the Channel
boolean isAcceptable() Check whether reception is ready in Channel

3 SocketChannel and ServerSocketChannel

SocketChannel in Java NIO is a channel that connects to a TCP network socket.

Operation steps:

  • Open SocketChannel
  • Read and write data
  • Close SocketChannel

The ServerSocketChannel in Java NIO is a channel that can listen for new incoming TCP connections, just like the ServerSocket in standard IO.

3.1 example of implementation file passing from client to server (blocking)
public class TestBlockingNIO {

    public static void main(String[] args) throws Exception{
        client();
    }

    public static void client() throws Exception{
        System.out.println("client start");
        //1. Access
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));
        FileChannel inChannel = FileChannel.open(Paths.get("e:\\demo\\study.txt"), StandardOpenOption.READ);

        //2. Allocate buffer of specified size
        ByteBuffer buf = ByteBuffer.allocate(1024);

        //3. Read the local file and send it to the server
        while (inChannel.read(buf) != -1){
            buf.flip();
            socketChannel.write(buf);
            buf.clear();
        }
        
         //Receive the feedback from the server (it is unnecessary to use this code, which is mainly for testing blocking)
        //Block
        socketChannel.shutdownOutput();
        int len = 0;
        while ((len = socketChannel.read(buffer)) != -1) {
            buffer.flip();
            System.out.println(new String(buffer.array(), 0, len));
            buffer.clear();
        }


        //4. Close the channel
        inChannel.close();
        socketChannel.close();
        System.out.println("client stop");
    }


}

class Server{
    public static void main(String[] args) throws Exception{
        server();
    }
    public static void server() throws Exception{
        System.out.println("server start");
        //1. Access
        ServerSocketChannel serverSocket = ServerSocketChannel.open();

        FileChannel outChannel = FileChannel.open(Paths.get("e:\\demo\\copy\\2.txt")
                ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);

        //2. Binding connection
        serverSocket.bind(new InetSocketAddress(9898));

        //3. Get the channel of the client
        SocketChannel socketChannel = serverSocket.accept();

        //4. Allocate buffer of specified size
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        //5. Receive the client's data and save it locally
        while(socketChannel.read(buffer) != -1){
            buffer.flip();
            outChannel.write(buffer);
            buffer.clear();
        }
        
        //Send feedback to the client (you may not need this code, which is mainly for testing blocking)
        buffer.put("Server receives client data successfully".getBytes());
        buffer.flip();
        socketChannel.write(buffer);

        //6. Close the channel
        socketChannel.close();
        outChannel.close();
        serverSocket.close();

        System.out.println("server stop");
    }
}

Start the server first and then the client

3.2 using NIO to realize non blocking server and client
  1. Client
public class TestNonBlockingNIO {

    public static void main(String[] args) throws Exception{
        client();
    }

    /**
     * Client
     * @throws Exception
     */
    public static void client() throws Exception{
        // 1. Access
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));

        //2. Switch non blocking mode
        socketChannel.configureBlocking(false);

        //3. Allocate buffer of specified size
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        //4. Send data to the server
        Scanner scan = new Scanner(System.in);
        while(scan.hasNext()){
            String str = scan.next();
            buffer.put((new Date().toString() + "\n" + str).getBytes());
            buffer.flip();
            socketChannel.write(buffer);
            buffer.clear();
        }

        //5. Close the channel
        socketChannel.close();

    }
}
  1. Server side
public class NonBlockingNIOServer{
    public static void main(String[] args) throws Exception{
        server();
    }

    /**
     * Server side
     * @throws Exception
     */
    public static void server() throws Exception{
        //1. Access
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //2. Switch non blocking mode
        serverSocketChannel.configureBlocking(false);

        //3. Binding connection
        serverSocketChannel.bind(new InetSocketAddress(9898));

        //4. Get selector
        Selector selector = Selector.open();

        //5. Register the channel to the selector and specify the listening event
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //6. Polling for events ready on the selector
        while(selector.select() > 0){
            //7. Get all registered selection keys in the current selector (ready listening events)
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //8. Iterative acquisition
            Iterator<SelectionKey> it = selectionKeys.iterator();
            while (it.hasNext()){
                SelectionKey selectionKey = it.next();
                //9. Judge what event is ready
                if(selectionKey.isAcceptable()){
                    //10. If "ready to receive", get client connection
                    SocketChannel socketChannel = serverSocketChannel.accept();

                    //11. Switch non blocking mode
                    socketChannel.configureBlocking(false);

                    //12. Register channel to selector
                    socketChannel.register(selector,SelectionKey.OP_READ);
                }else if(selectionKey.isReadable()){
                    // 13. Get the channel of "read ready" status on the current selector
                    SocketChannel socketChannel = (SocketChannel)selectionKey.channel();

                    //14. Read data
                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    int len = 0;
                    while((len = socketChannel.read(buffer)) > 0){
                        buffer.flip();
                        System.out.println(new String(buffer.array(),0,len));
                        buffer.clear();
                    }
                }
            }

            //15. Deselect key
            it.remove();
        }
    }
}

Start the server first, then the client. It can start multiple clients at the same time, which is actually a simple chat room

4 DatagramChannel

Datagram channel in Java NIO is a channel that can send and receive UDP packets.

Operation steps:

  1. Open datagram channel
  2. Receive / send data

Sample code

package com.zjx.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Scanner;

/**
 * @author zhaojiaxing
 * @date 2020-02-04 22:15
 */
public class TestNonBlockingNIO2 {
    public static void main(String[] args) throws Exception{
         send();
    }

    public static void send() throws IOException{
        DatagramChannel dc = DatagramChannel.open();

        dc.configureBlocking(false);

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        Scanner scanner = new Scanner(System.in);

        while (scanner.hasNext()){
            String str = scanner.next();
            buffer.put(str.getBytes());
            buffer.flip();
            dc.send(buffer,new InetSocketAddress("127.0.0.1",9898));
            buffer.clear();
        }
        dc.close();
    }

}

class TestServer{

    public static void main(String[] args) throws Exception{
        receive();
    }

    public static void receive() throws IOException{
        DatagramChannel dc = DatagramChannel.open();

        dc.configureBlocking(false);

        dc.bind(new InetSocketAddress(9898));

        Selector selector = Selector.open();

        dc.register(selector, SelectionKey.OP_READ);

        while(selector.select() > 0){
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();

            while (it.hasNext()){
                SelectionKey selectionKey = it.next();

                if(selectionKey.isReadable()){
                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    dc.receive(buffer);
                    buffer.flip();
                    System.out.println(new String(buffer.array(),0,buffer.limit()));
                    buffer.clear();
                }
            }
            it.remove();
        }
    }
}

5. Pipe

The Java NIO pipeline is a one-way data connection between two threads. Pipe has a source channel and a sink channel. The data is written to the sink channel and read from the source channel.

Use example

 public static void main(String[] args) throws Exception{
        //1. Access pipeline
        Pipe pipe = Pipe.open();

        //2. Write data in buffer to pipeline
        ByteBuffer buf = ByteBuffer.allocate(1024);

        Pipe.SinkChannel sinkChannel = pipe.sink();
        buf.put("Send data through a one-way pipe".getBytes());
        buf.flip();
        sinkChannel.write(buf);

        //3. Read data in buffer
        Pipe.SourceChannel sourceChannel = pipe.source();
        buf.flip();
        int len = sourceChannel.read(buf);
        System.out.println(new String(buf.array(), 0, len));

        sourceChannel.close();
        sinkChannel.close();
    }
91 original articles published, 93 praised, 210000 visitors+
Private letter follow

Topics: Java network socket