[java] BIO, NIO, AIO learning

Keywords: Java udp

BIO

BIO(blocking I/O): Synchronization is blocked, and the server implementation mode is to connect a thread, that is, when a client has a connection request, the server side needs to start a thread to process it. If this connection does nothing and causes unnecessary thread overhead, it can be improved through the thread pool mechanism (to enable multiple clients to connect to the server)

Combing BIO Programming Process

  1. The server starts a ServerSocket, registers the port, and calls the accpet method to listen for client Socket connections.
  2. The client starts the Socket to communicate with the server, and by default the server needs to set up a thread to communicate with each client

Based on communication in BIO mode, client-server is fully synchronized and fully coupled

bio basic network programming

  • Single Client: Single Receipt and Issuance
// Client
public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",9999);
        OutputStream os = socket.getOutputStream();
        PrintStream ps = new PrintStream(os);
        ps.println("hello world! Hello, server!");
    }
}


// Server
public class Server {
    public static void main(String[] args){
        System.out.println("=====Server 1 Start=====");
        ServerSocket ss;
        {
            try {
                ss = new ServerSocket(9999);
                // Listening Client
                Socket socket = ss.accept();

                InputStream is = socket.getInputStream();
                BufferedReader bis = new BufferedReader(new InputStreamReader(is));
                String msg;
                if ((msg = bis.readLine()) != null ){
                    System.out.println("Server received:" + msg);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  • Single client: multiple and multiple messages
// Client
public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",9999);
        OutputStream os = socket.getOutputStream();
        Scanner sc = new Scanner(System.in);

        while (true){
            System.out.print("Client 2:");
            String msg = sc.nextLine();
            PrintStream ps = new PrintStream(os);
            ps.println(msg);
            ps.flush();
        }
    }
}

// Server
public class Server {
    public static void main(String[] args){
        System.out.println("=====Server 2 Start=====");
        ServerSocket ss;
        {
            try {
                ss = new ServerSocket(9999);
                // Listening Client
                Socket socket = ss.accept();

                InputStream is = socket.getInputStream();
                BufferedReader bis = new BufferedReader(new InputStreamReader(is));
                String msg;
                while ((msg = bis.readLine()) != null ){
                    System.out.println("Server received:" + msg);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  • Multi-client mode
// Thread Tasks
public class serverThreadReader extends Thread{
    private Socket socket;
    public serverThreadReader(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            BufferedReader bis = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = bis.readLine()) != null ){
                System.out.println("Server Received["+socket.toString()+"]: " + msg);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        super.run();
    }
}

// Server
public class Server {
    public static void main(String[] args){
        System.out.println("=====Server 3 Start=====");
        ServerSocket ss;
        {
            try {
                ss = new ServerSocket(9999);
                // Listening Client
                while (true){
                    Socket socket = ss.accept();
                    // Create a separate thread to process client requests
                    new serverThreadReader(socket).start();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

// Client
public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",9999);
        OutputStream os = socket.getOutputStream();
        Scanner sc = new Scanner(System.in);
        while (true){
            System.out.print("Client 3:");
            String msg = sc.nextLine();
            PrintStream ps = new PrintStream(os);
            ps.println(msg);
            ps.flush();
        }
    }
}

Pseudo-Asynchronous I/O Programming

  • When the concurrent access of the client increases, the server side will incur 1:1 thread overhead. The larger the number of accesses, the system will overflow the thread stack, and the thread creation will fail, which will eventually cause the process to crash or die, thus making it impossible to provide services to the outside world.

  • A pseudo-asynchronous I/O communication framework, implemented using thread pools and task queues, encapsulates the client's Socket as a Task (which implements the java.lang.Runnable thread task interface) and handles it to the back-end thread pool when the client accesses.

  • JDK's thread pool maintains a message queue and N active threads to process Socket tasks in the message queue. Since the thread pool can set the size of the message queue and the maximum number of threads, its resource usage is controlled and it will not cause resource depletion or downtime regardless of how many concurrent clients access it has.

// Thread pool execution task
public class HandlerSockerServerPool {
    private ExecutorService executorService;
    // Initialize thread pool object when creating class object
    public HandlerSockerServerPool(int maxThreadNum, int queueSize){
        executorService = new ThreadPoolExecutor(3,maxThreadNum,120, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(queueSize));

    }
    public void execute(Runnable target){
        executorService.execute(target);
    }
}

// Thread Tasks
public class ServerRunnableTarget implements Runnable{
    private Socket socket;

    public ServerRunnableTarget(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            BufferedReader bis = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = bis.readLine()) != null ){
                System.out.println("Server Received:"+"["+Thread.currentThread().getName()+"]["+socket.toString()+"]: " + msg);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

// Server
public class Server {
    public static void main(String[] args){
        System.out.println("=====Server 4 Start-Pseudo-asynchronous=====");
        try {
            ServerSocket ss = new ServerSocket(9999);
            HandlerSockerServerPool pool = new HandlerSockerServerPool(3,10);
            while (true){
                Socket socket = ss.accept();
                Runnable target = new ServerRunnableTarget(socket);
                pool.execute(target);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    } 
}

// Client
public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",9999);
        OutputStream os = socket.getOutputStream();
        Scanner sc = new Scanner(System.in);
        while (true){
            System.out.print("Client 4:");
            String msg = sc.nextLine();
            PrintStream ps = new PrintStream(os);
            ps.println(msg);
            ps.flush();
        }
    }
}

File Upload

// Thread Tasks
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            // Get data input stream to read data sent by client
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            // Read file types sent by clients
            String suffix = dis.readUTF();
            System.out.println("The server receives the file type:" + suffix);
            // Define a byte input pipeline
            String fileName = UUID.randomUUID().toString() + suffix;
            OutputStream os = new FileOutputStream(fileName);
            byte[] buffer = new byte[1024];
            int len;
            while ((len = dis.read(buffer))> 0){
                os.write(buffer, 0, len);
            }
            os.close();
            System.out.println(String.format("Server received file saved successfully! file: [%s]",fileName));

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// Server
public class Server {
    public static void main(String[] args){
        System.out.println("=====Server Start: file=====");
        ServerSocket ss = null;
        try {
            ss = new ServerSocket(8888);
            while (true){
                Socket socket = ss.accept();
                new ServerReaderThread(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// Client
public class Client { 
    public static void main(String[] args){
        try {
            Socket socket = new Socket("127.0.0.1", 8888);
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            dos.writeUTF(".png");
            FileInputStream is = new FileInputStream("mv.png");
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer))>0){
                dos.write(buffer,0,len);
            }
            dos.flush();
            socket.shutdownOutput();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Port Forwarding

// Thread Tasks
public class ServerReaderThread extends Thread{

    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {

        try {
            // Get data input stream to read data sent by client
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println("Server Received["+socket.toString()+"]msg: "+msg);
                sendMsgToAllClient(msg);
            }

        } catch (IOException e) {
            System.out.println(String.format("Someone is currently offline:[%s]",
                    socket.toString()));
            Server.allSocketOnLine.remove(socket);
        }
    }

    private void sendMsgToAllClient(String msg) throws IOException {
        for (Socket sk : Server.allSocketOnLine) {
            PrintStream ps = new PrintStream(sk.getOutputStream());
            ps.println(socket.toString() + ":" + msg);
            ps.flush();
        }
    }
}

// Server
public class Server {
    public static List<Socket> allSocketOnLine = new ArrayList<>();
    public static void main(String[] args){
        System.out.println("=====Server Start: Port Forwarding=====");
        ServerSocket ss = null;
        try {
            ss = new ServerSocket(8888);
            while (true){
                Socket socket = ss.accept();
                // Logon Client Add to List
                System.out.println("["+socket.toString()+"]Go online...");
                allSocketOnLine.add(socket);
                new ServerReaderThread(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// Client
// --Receiving message client
public class Client {
    public static void main(String[] args){
        try {
            Socket socket = new Socket("127.0.0.1", 8888);
            while (true){
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String msg;
                while ((msg = br.readLine()) != null){
                    System.out.println("Client receives msg: \n" + msg);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// - Sending message client
public class SendMsgClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",8888);
        OutputStream os = socket.getOutputStream();
        Scanner sc = new Scanner(System.in);
        while (true){
            System.out.print("Client:");
            String msg = sc.nextLine();
            PrintStream ps = new PrintStream(os);
            ps.println(msg);
            ps.flush();
        }
    }
}

NIO

  • Java NIO (New IO)Also known as java non-blocking IO is a new IO API introduced from Java version 1.4 that can replace standard Java IOAPI. NIO has the same function and purpose as the original IO, but it is used in a completely different way. NIO supports buffer-oriented, channel-based IO operations. NIO will read and write files in a more efficient way. NIO can be understood as non-blocking IO.Traditional IO reads and writes can only block execution. Threads cannot do anything else during IO reads and writes, such as calling socket.read(), if the server has no data to transmit, the thread will always block, and sockets can be configured in NIO as non-blocking mode.
  • NIO-related classes are placed under java.nio packages and subpackages, and many of the classes in the original java.io package are overwritten.
  • NIO has three core parts: Channel, Buffer, Selector.
  • Java NIO's non-blocking mode allows a thread to send requests or read data from a channel, but it only gets data that is currently available. If no data is available, it will get nothing, not keep the thread blocked, so the thread can continue to do other things until the data becomes readable.The same is true for non-blocking writes, where a thread requests to write some data to a channel without waiting for it to write completely, and the thread can do something else at the same time.
  • Common sense: NIO can handle multiple operations with one thread. Assuming there are 1000 requests coming, 20 or 80 threads can be allocated to handle them as appropriate. Unlike previous blocking IOs, 1000 are not allocated for non-scoring purposes.

bio versus nio

NIOBIO
Buffer OrientedStream Oriented
Non Blocking IOBlocking IO
Selectors

NIO Three Cores:

NIO has three core parts: Channel, Buffer, Selector.

  • Each channel corresponds to a Buffer
  • One thread corresponds to a Selector, and one Selector corresponds to multiple channels (connections)
  • Which channel the program switches to is determined by the event
  • Selector switches between channels based on different events
  • Buffer is a block of memory, the bottom is an array
  • Data is read and written by Buffer. In BIO, either input stream or output stream cannot be bidirectional, but NIO's Buffer can be read or written.
  • The core of the Java NIO system is Channel and Buffer. Channel means open to IO devices (e.g., files, sockets)Connection. If you need to use a NIO system, you need to get the channels used to connect the IO devices and the buffers used to hold the data. Then you can manipulate the buffers to process the data. In short, Channel is responsible for transmitting and Buffer is responsible for accessing the data.

Buffer

Buffers are essentially a block of memory that can be written to and then read from. This memory is wrapped as a NIO Buffer object and provides a set of methods for accessing the block of memory. The Buffer API is easier to operate and manage than operating directly on arrays.

Buffer class and its subclasses
  • ByteBuffer,CharBuffer ,ShortBuffer ,IntBuffer ,LongBuffer ,FloatBuffer ,DoubleBuffer

  • Get the corresponding buffer object

static xxBuffer.allocate(int capacity) : Create a capacity of capacity Of xxBuffer object
Basic Properties
  • Capacity: As a block of memory, a Buffer has a fixed size, also known as "capacity". Buffer capacity cannot be negative and cannot be changed after it is created.
  • Limit: Indicates the size of operational data in a buffer (data cannot be read or written after a limit). The limit of a buffer cannot be negative and cannot be larger than its capacity.
    • Write mode, limit equal to buffer capacity.
    • In read mode, limit equals the amount of data written.
  • Position: The index of the next data to be read or written. Buffer position cannot be negative and cannot be greater than its limit
  • Mark and reset: A tag is an index that specifies a particular position in a Buffer by the mark() method in the Buffer and can then be restored to that position by calling the reset() method.

Tags, locations, constraints, capacities follow the following invariants: 0 <= mark <= position <= limit <= capacity

Common api
Buffer clear() Empty the buffer and return a reference to it
Buffer flip() To set the bounds of the buffer to the current position and charge the current position with 0
int capacity() Return Buffer Of capacity Size
boolean hasRemaining() Determine if there are elements in the buffer
int limit() Return Buffer Boundaries(limit) Location
Buffer limit(int n) Set buffer bounds to n, And returns a new limit Buffer object for
Buffer mark() Marking Buffers
int position() Returns the current position of the buffer position
Buffer position(int n) The current location where the buffer will be set is n , And returns the modified Buffer object
int remaining() Return position and limit Number of elements between
Buffer reset() Place position Go to previously set mark Location
Buffer rewind() Set position to 0, unset mark
    
    
//---------- Common api for data manipulation
Buffer All subclasses provide two methods for data manipulation: get()put() Method
 Get Buffer Data in
get() : Read a single byte
get(byte[] dst): Bulk read multiple bytes to dst in
get(int index): Read bytes at specified index locations(Will not move position)
    
Put in data to Buffer Middle
put(byte b): Writes a given single byte to the current location of the buffer
put(byte[] src): take src The current position in which bytes are written to the buffer
put(int index, byte b): Writes the specified byte to the index position of the buffer(Will not move position)

Channel

Java NIO's channels are similar to streams, but they differ in that they can read data from and write data to the channel. However, streams (input or output) are usually one-way read and write. Channels can read and write non-blocking channels, channels can support read or write buffers, and channels can support read and write asynchronously.

  • NIO's channels are similar to streams, but there are some differences:
    • Channels can read and write simultaneously, while streams can read or write only
    • Channels can read and write data asynchronously
    • Channels can read or write data from a buffer to a buffer:
  • stream in BlO is one-way, for example, FileInputStream objects can only read data, whereas channels in NIO are bidirectional and can be read or written.
  • Channel is an interface in NIO
public interface channel extends closeable{
Common Channel implementation classes
  • FileChannel: A channel for reading, writing, mapping, and manipulating files.
  • DatagramChannel: A data channel in a UDP read-write network.
  • SocketChannel: Reads and writes data from the network over TCP.
  • ServerSocketChannel: You can listen for new incoming TCP connections and create a SocketChannel for each new incoming connection.

[ServerSocketChanne is like ServerSocket, SocketChannel is like Socket]

Common api
int read(ByteBuffer dst) from Channel Read data to  ByteBuffer
long read(ByteBuffer[] dsts) take Channel The data in is "scattered" to  ByteBuffer[]
int write(ByteBuffer src) take ByteBuffer Write data from to  Channel
long write(ByteBuffer[] srcs) take ByteBuffer[] "Aggregate" data from  Channel
long position() Return the file location for this channel
FileChannel position(long p) Set the file location for this channel
long size() Returns the current size of the file for this channel
FileChannel truncate(long s) Intercept the file for this channel to a given size
void force(boolean metaData) Force all file updates to this channel to be written to the storage device
File Operation
  • Read and write operations
// Write File
@Test
public void write(){
    try {
        FileOutputStream fos = new FileOutputStream("data01.txt");
        // Get channel
        FileChannel channel = fos.getChannel();
        // Define Buffers
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put("hello,world!".getBytes());
        // Change Buffer to Write Mode
        buffer.flip();
        // Write data through channel
        channel.write(buffer);
        channel.close();
        System.out.println("Write data to file!");
    } catch (Exception e) {
        e.printStackTrace();
    }

}

// read file
@Test
public void read() {
    try {
        FileInputStream fis = new FileInputStream("data01.txt");
        // Get channel
        FileChannel channel = fis.getChannel();
        // Define Buffers
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // Read data to buffer
        channel.read(buffer);
        buffer.flip();
        String rs = new String(buffer.array(), 0, buffer.remaining());
        System.out.println("Read file data:"+rs);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
  • File Copy
@Test
public void copy() {
    try {
        File srcFile = new File("1f913498-65f5-4683-98fe-1a9025a2bd87.png");
        File destFile = new File("new_1f913498-65f5-4683-98fe-1a9025a2bd87.png");

        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(destFile);

        FileChannel isChannel = fis.getChannel();
        FileChannel osChannel = fos.getChannel();

        // Allocation Buffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while (true) {
            // Clear the buffer before reading data
            buffer.clear();

            // Start reading data once
            int flag = isChannel.read(buffer);
            if (flag == -1) {
                break;
            }
            // Switch buffer to read mode after reading data
            buffer.flip();
            // Data Write Out
            osChannel.write(buffer);
        }
        isChannel.close();
        osChannel.close();
        System.out.println("Copy complete!");

    } catch (Exception e) {
        e.printStackTrace();
    }
}
  • Distributed Read, Clustered Write
//Dispersion and Aggregation
@Test
public void test() throws IOException {
    RandomAccessFile raf1 = new RandomAccessFile("data01.txt", "rw");
    //1.Get Channel
    FileChannel channel1 = raf1.getChannel();

    //2.Allocate buffer of specified size
    ByteBuffer buf1 = ByteBuffer.allocate(10);
    ByteBuffer buf2 = ByteBuffer.allocate(1024);

    //3.Distributed Read
    ByteBuffer[] bufs = {buf1, buf2};
    channel1.read(bufs);

    for (ByteBuffer byteBuffer : bufs) {
        byteBuffer.flip();
    }

    System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
    System.out.println("-----------------");
    System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));

    //4.Aggregate Write
    RandomAccessFile raf2 = new RandomAccessFile("data02.txt", "rw");
    FileChannel channel2 = raf2.getChannel();
    channel2.write(bufs);
}

// --- Result
hello,worl
-----------------
d!
  • File copy: nio built-in method
@Test
public void transfer() throws Exception {
    // 1. Byte Input Pipeline
    FileInputStream is = new FileInputStream("data01.txt");
    FileChannel isChannel = is.getChannel();
    // 2. Byte Output Stream Pipeline
    FileOutputStream fos = new FileOutputStream("data03.txt");
    FileChannel osChannel = fos.getChannel();
    // 3. Replication
    osChannel.transferFrom(isChannel,isChannel.position(),isChannel.size());
    isChannel.close();
    osChannel.close();
}

@Test
public void transferTo() throws Exception {
    // 1. Byte Input Pipeline
    FileInputStream is = new FileInputStream("data01.txt");
    FileChannel isChannel = is.getChannel();
    // 2. Byte Output Stream Pipeline
    FileOutputStream fos = new FileOutputStream("data04.txt");
    FileChannel osChannel = fos.getChannel();
    // 3. Replication
    isChannel.transferTo(isChannel.position() , isChannel.size() , osChannel);
    isChannel.close();
    osChannel.close();
}

Selector

Selector is a Java NIO component that can examine one or more NIO channels and determine which channels are ready for reading or writing. This allows a single thread to manage multiple channels, thereby managing multiple network connections and improving efficiency.

A selector is a multiplexer of the SelectableChannle object, which can monitor the IO status of multiple SelectableChannel s simultaneously, that is, it allows a single thread to manage multiple Channels. Selector is the core of non-blocking lO

  • Java NOI, in a non-blocking IO fashion. Selector can be used to handle multiple client connections with one thread
  • Selector can detect whether events occur on multiple registered channels (Note: Multiple Channel s can be registered as events to the same Selector), if events occur, get the events and process them accordingly for each event. This allows you to manage multiple channels with a single thread, that is, manage multiple connections and requests.
  • Reading and writing occurs only when the connection/channel has a real read/write event, greatly reducing overhead and eliminating the need to create a thread for each connection and maintaining multiple threads
  • Avoid overhead caused by context switching between threads
Selector application

Create Selector: Create a Selector by calling the Selector.open() method.

Selector selector = Selector.open();

Register channel with selector: SelectableChannel.register(Selector sel, int ops)

//1. Get Channels
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2. Switch non-blocking mode
ssChannel.configureBlocking(false);
//3. Binding Connections
ssChannel.bind(new InetSocketAddress(9898));
//4. Get the selector
Selector selector = Selector.open();
//5. Register the channel with the selector and specify "Listen for Receive Events"
ssChannel.register(selector, SelectionKey.OP_ACCEPT);

When register(Selector sel, int ops) is called to register a channel with the selector, the event that the selector listens to for the channel needs to be specified by the second parameter ops. The type of event that can be listened on (represented by four constants that can use SelectionKey):

  • Read: SelectionKey.OP_READ(1)
  • Write: SelectionKey.OP_WRITE (4)
  • Connection: SelectionKey.OP_CONNECT(8)
  • Receive: SelectionKey.OP_ACCEPT(16)
  • If you are registering to listen for more than one event, you can use the Bit Or operator to connect.
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE 

Principle analysis of NIO non-blocking network communication

Selector implements: an I/O thread can concurrently handle N client connections and read-write operations, which fundamentally resolves the traditional one-way synchronous blocking I/O connection model, and greatly improves the performance, resilience and reliability of the architecture.

Service-side process

When the client connects to the server, the server gets the SocketChannel through ServerSocketChannel:

  • 1. Getting Channels

    ServerSocketChannel ssChannel = ServerSocketChannel.open();
    
  • 2. Switch non-blocking mode

    ssChannel.configureBlocking(false);
    
  • 3. Binding Connections

    ssChannel.bind(new InetSocketAddress(9999));
    
  • 4. Get Selector

    Selector selector = Selector.open();
    
  • 5. Register the channel with the selector and specify "Listen for Receive Events"

    ssChannel.register(selector, SelectionKey.OP_ACCEPT);
    
  • 6. Polling Acquire Selector Events "Ready"

    //"Ready" events on the polling get selector
     while (selector.select() > 0) {
            System.out.println("Round by round");
            //7.Gets all registered Selection Keys (Ready Listening Events) in the current selector
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                //8.Getting ready is an event
                SelectionKey sk = it.next();
                //9.Determine exactly what event is ready
                if (sk.isAcceptable()) {
                    //10.Get Client Connection When Received
                    SocketChannel sChannel = ssChannel.accept();
                    //11.Switch non-blocking mode
                    sChannel.configureBlocking(false);
                    //12.Register the channel on the selector
                    sChannel.register(selector, SelectionKey.OP_READ);
                } else if (sk.isReadable()) {
                    //13.Gets the channel in Ready state on the current selector
                    SocketChannel sChannel = (SocketChannel) sk.channel();
                    //14.Read data
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    int len = 0;
                    while ((len = sChannel.read(buf)) > 0) {
                        buf.flip();
                        System.out.println(new String(buf.array(), 0, len));
                        buf.clear();
                    }
                }
                //15.Unselect Key SelectionKey
                it.remove();
            }
        }
    }
    

Client Process

  • Get Channel

    SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
    
  • Switch non-blocking mode

    sChannel.configureBlocking(false);
    
  • Allocate buffer of specified size

    ByteBuffer buf = ByteBuffer.allocate(1024);
    
  • Send data to server

    Scanner scan = new Scanner(System.in);
    while(scan.hasNext()){
    	String str = scan.nextLine();
    	buf.put((new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(System.currentTimeMillis())
    			+ "\n" + str).getBytes());
    	buf.flip();
    	sChannel.write(buf);
    	buf.clear();
    }
    //Close Channel
    sChannel.close();
    

Getting Started Cases

Requirements: The server receives connection requests from clients and receives events from multiple clients.

/**
 * Client
 */
public class Client {
	public static void main(String[] args) throws Exception {
		//1.Get Channel
		SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
		//2.Switch non-blocking mode
		sChannel.configureBlocking(false);
		//3.Allocate buffer of specified size
		ByteBuffer buf = ByteBuffer.allocate(1024);
		//4.Send data to server
		Scanner scan = new Scanner(System.in);
		while(scan.hasNext()){
			String str = scan.nextLine();
			buf.put((new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(System.currentTimeMillis())
					+ "\n" + str).getBytes());
			buf.flip();
			sChannel.write(buf);
			buf.clear();
		}
		//5.Close Channel
		sChannel.close();
	}
}

/**
 * Server
 */
public class Server {
    public static void main(String[] args) throws IOException {
        //1.Get Channel
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        //2.Switch non-blocking mode
        ssChannel.configureBlocking(false);
        //3.Binding Connection
        ssChannel.bind(new InetSocketAddress(9999));
        //4.Get Selector
        Selector selector = Selector.open();
        //5.Register the channel on the selector and specify "Listen for Receive Events"
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);
        //6."Ready" events on the polling get selector
        while (selector.select() > 0) {
            System.out.println("Round by round");
            //7.Gets all registered Selection Keys (Ready Listening Events) in the current selector
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                //8.Getting ready is an event
                SelectionKey sk = it.next();
                //9.Determine exactly what event is ready
                if (sk.isAcceptable()) {
                    //10.Get Client Connection When Received
                    SocketChannel sChannel = ssChannel.accept();
                    //11.Switch non-blocking mode
                    sChannel.configureBlocking(false);
                    //12.Register the channel on the selector
                    sChannel.register(selector, SelectionKey.OP_READ);
                } else if (sk.isReadable()) {
                    //13.Gets the channel in Ready state on the current selector
                    SocketChannel sChannel = (SocketChannel) sk.channel();
                    //14.Read data
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    int len = 0;
                    while ((len = sChannel.read(buf)) > 0) {
                        buf.flip();
                        System.out.println(new String(buf.array(), 0, len));
                        buf.clear();
                    }
                }
                //15.Unselect Key SelectionKey
                it.remove();
            }
        }
    }
}

Group Chat System

  • Write a NIO group chat system to achieve client-to-client communication needs (non-blocking)
  • Server side: can monitor users online, offline, and realize message forwarding function
  • Client: channel allows non-blocking messages to be sent to all other client users, while receiving messages forwarded by other client users through the server

Server

public class Server {
    //Define Properties
    private Selector selector;
    private ServerSocketChannel ssChannel;
    private static final int PORT = 9999;
    //constructor
    //Initialization Work
    public Server() {
        try {
            // 1. Getting Channels
            ssChannel = ServerSocketChannel.open();
            // 2. Switch to non-blocking mode
            ssChannel.configureBlocking(false);
            // 3. Bind the port of the connection
            ssChannel.bind(new InetSocketAddress(PORT));
            // 4. Get Selector
            selector = Selector.open();
            // 5. Register channels on selectors and start specifying to listen for receive events
            ssChannel.register(selector , SelectionKey.OP_ACCEPT);
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
    //Monitor
    public void listen() {
        System.out.println("Listen Thread: " + Thread.currentThread().getName());
        try {
            while (selector.select() > 0){
                System.out.println("Start a round of event handling~~~");
                // 7. Get ready events in all registered channels in the selector
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                // 8. Start traversing these prepared events
                while (it.hasNext()){
                    // Extract the current event
                    SelectionKey sk = it.next();
                    // 9. Determine exactly what this event is
                    if(sk.isAcceptable()){
                        // 10. Direct access to the current client channel
                        SocketChannel schannel = ssChannel.accept();
                        // 11. Switch to non-blocking mode
                        schannel.configureBlocking(false);
                        // 12. Register this client channel with the selector
                        System.out.println(schannel.getRemoteAddress() + " Go online ");
                        schannel.register(selector , SelectionKey.OP_READ);
                        //Tips
                    }else if(sk.isReadable()){
                        //Processing Read (Write-only method.)
                        readData(sk);
                    }
                    it.remove(); // The current event needs to be removed after processing is complete
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            //Exception handling occurred...
        }
    }

    //Read client messages
    private void readData(SelectionKey key) {
        //Get associated channle
        SocketChannel channel = null;
        try {
           //Get channel
            channel = (SocketChannel) key.channel();
            //Create buffer
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int count = channel.read(buffer);
            //Processing based on count value
            if(count > 0) {
                //Convert data from cache to string
                String msg = new String(buffer.array());
                //Output this message
                System.out.println("form Client: " + msg);
                //Forward messages to other clients (remove yourself) and write a specific way to handle them
                sendInfoToOtherClients(msg, channel);
            }
        }catch (IOException e) {
            try {
                System.out.println(channel.getRemoteAddress() + " Offline..");
                e.printStackTrace();
                //Unregister
                key.cancel();
                //Close Channel
                channel.close();
            }catch (IOException e2) {
                e2.printStackTrace();;
            }
        }
    }

    //Forward messages to other customers (channels)
    private void sendInfoToOtherClients(String msg, SocketChannel self ) throws  IOException{
        System.out.println("Server Forwarding Message...");
        System.out.println("Server forwards data to client thread: " + Thread.currentThread().getName());
        //Traverse all SocketChannel s registered on selector and exclude self
        for(SelectionKey key: selector.keys()) {
            //Remove the corresponding SocketChannel by key
            Channel targetChannel = key.channel();
            //Exclude yourself
            if(targetChannel instanceof  SocketChannel && targetChannel != self) {
                //Transformation
                SocketChannel dest = (SocketChannel)targetChannel;
                //Store msg in buffer
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                //Write buffer's data to channel
                dest.write(buffer);
            }
        }
    }

    public static void main(String[] args) {
        //Create Server Object
        Server groupChatServer = new Server();
        groupChatServer.listen();
    }
}

Client

package com.itheima.chat;

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

public class Client {
    //Define related properties
    private final String HOST = "127.0.0.1"; // ip of server
    private final int PORT = 9999; //Server Port
    private Selector selector;
    private SocketChannel socketChannel;
    private String username;

    //Constructor, complete initialization
    public Client() throws IOException {
        selector = Selector.open();
        //Connect to Server
        socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT));
        //Set up non-blocking
        socketChannel.configureBlocking(false);
        //Register channel with selector
        socketChannel.register(selector, SelectionKey.OP_READ);
        //Get username
        username = socketChannel.getLocalAddress().toString().substring(1);
        System.out.println(username + " is ok...");

    }

    //Send message to server
    public void sendInfo(String info) {
        info = username + " Say:" + info;
        try {
            socketChannel.write(ByteBuffer.wrap(info.getBytes()));
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

    //Read messages replied from the server
    public void readInfo() {
        try {
            int readChannels = selector.select();
            if(readChannels > 0) {//Available channels

                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {

                    SelectionKey key = iterator.next();
                    if(key.isReadable()) {
                        //Get Related Channels
                       SocketChannel sc = (SocketChannel) key.channel();
                       //Get a Buffer
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //read
                        sc.read(buffer);
                        //Convert read buffer data to string
                        String msg = new String(buffer.array());
                        System.out.println(msg.trim());
                    }
                }
                iterator.remove(); //Remove the current selectionKey to prevent duplicate operations
            } else {
                //System.out.println("No available channel...");
            }

        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        //Start our client
        Client chatClient = new Client();
        //Start a thread, every 3 seconds, to read and send data from the server
        new Thread() {
            public void run() {

                while (true) {
                    chatClient.readInfo();
                    try {
                        Thread.currentThread().sleep(3000);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        //Send data to server
        Scanner scanner = new Scanner(System.in);

        while (scanner.hasNextLine()) {
            String s = scanner.nextLine();
            chatClient.sendInfo(s);
        }
    }
}

AIO

  • Java AIO(NIO.2): Asynchronous, non-blocking, server implementation mode is a valid request for a thread, and client I/O requests are processed by the OS before notifying the server application to start the thread.
AIO
 Asynchronous non-blocking, based on NIO Can be called NIO2.0
    BIO                   NIO                              AIO        
Socket                SocketChannel                    AsynchronousSocketChannel
ServerSocket          ServerSocketChannel	       AsynchronousServerSocketChannel

Unlike NIO, when performing read and write operations, only the read or write method of the API needs to be called directly. Both methods are asynchronous. For read operations, the operating system passes readable streams into the buffer of the read method when there is stream readable.For write operations, the operating system actively notifies the application when the stream passed by the write method has been written to

This means that read/write methods are asynchronous and call callback functions on their own initiative when they are finished. In JDK1.7, this section is called NIO.2 and adds the following four asynchronous channels mainly under the Java.nio.channels package:

AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel

summary

BIO,NIO,AIO:

  • Java BIO: Synchronized and blocked, the server implementation mode is a connection thread, that is, the server side needs to start a thread to process when the client has a connection request. If this connection does nothing, it will cause unnecessary thread overhead, which can of course be improved through the thread pool mechanism.
  • Java NIO: Synchronous non-blocking, server implementation mode is a request for a thread, that is, connection requests sent by clients are registered on the multiplexer, which starts a thread to process when the multiplexer polls for I/O requests on the connection.
  • Java AIO(NIO.2): Asynchronous, non-blocking, server implementation mode is a valid request for a thread, and client I/O requests are processed by the OS before notifying the server application to start the thread.

BIO, NIO, AIO Scenario Analysis:

  • The BIO approach is suitable for architectures with relatively small and fixed number of connections. It requires more server resources and is confined to applications. It was the only option before JDK1.4, but the program is intuitive and easy to understand.
  • NIO is suitable for architectures with many connections and short connections (light operation), such as chat servers, which are confined to applications with concurrency, complex programming, and JDK1.4 support.
  • AIO is used in architectures with a large number of connections and long connections (re-operation), such as album servers, fully invoking OS to participate in concurrent operations, complex programming, JDK7 support. Netty!

Posted by mortimerdude on Sat, 11 Sep 2021 12:15:24 -0700