1. IO model

1.1 BIO model

Features: A thread is created each time a connection is made, and no connection is blocked waiting

package com.zhj.test.bio;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/ * * *@author zhj
 */
public class BIOServer {

    public static void main(String[] args) throws IOException {
        // Thread pool mechanism

        / / way of thinking
        1. Create a thread
        // 2. Create a thread to communicate with the client if there is a connection (separate method)

        ExecutorService executorService = Executors.newCachedThreadPool();

        / / create a ServerSocket
        ServerSocket serverSocket = new ServerSocket(6666);
        System.out.println("Server program started!");

        while (true) {
            // Listen and wait for the client to connect
            System.out.println("Waiting for connection!!");
            final Socket socket = serverSocket.accept();
            System.out.println("Connect to a socket!");

            // Create a thread to communicate with
            executorService.execute(new Runnable() {
                @Override
                public void run(a) {
                    // Can communicate with the clienthandler(socket); }}); }}/** * Communicates with the client */
    public static void handler(Socket socket) {
        byte[] bytes = new byte[1024];
        try {
            InputStream inputStream = socket.getInputStream();
            // Loop to read the data read by the client
            while (true) {
                System.out.println("Waiting for input data!!");
                int read = inputStream.read(bytes);
                if(read ! = -1) {
                    System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getId());
                    System.out.println("Receive:" + new String(bytes, 0, read));
                } else {
                    break; }}}catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("Close the connection to the client!");
            try {
                socket.close();
            } catch(IOException e) { e.printStackTrace(); }}}}Copy the code

1.2 NIO

NIO stands for Java non-blocking IO, a new API provided by the JDK. Starting with JDK1.4, Java offers a series of New features that improve input and output, known collectively as NIO(New IO), that are synchronous and non-blocking.

Three core parts: Channel, Buffer cache, Selector

NIO is buffer-oriented, or block-oriented, in that data is read to a buffer that it processes later and can be moved back and forth as needed, which increases the flexibility of its processing and allows it to provide a highly scalable, non-blocking network.

Features:

  • Non-blocking does not require the thread to wait, and other tasks can be performed
  • A thread can handle multiple connections, and when a large number of requests are sent to the server, there is no need to open a thread for each connection

HTTP2.0 uses multiplexing to handle multiple requests for the same connection.

The relationship between the three core components

  • Each Channel has a Buffer
  • Selector corresponds to one thread, and one thread corresponds to multiple Channel connections
  • This diagram shows three channels registered with the Selector
  • Which Channel the program switches to is determined by events, and events are an important concept
  • And the Selector will switch between channels at different times
  • A Buffer is a block of content with an array at the bottom
  • Data is read and written through Buffer, which is essentially different from BIO. BIO is either an input stream or an output stream, which cannot be bidirectional. NIO’s Buffer can be read or written, which requires flip to switch
  • A Channel is bidirectional and returns information about the underlying operating system, which is bidirectional in Linux

1.2.1 Buffer Buffer usage

  • Capacity Indicates the maximum amount of data that can be stored. Is set when the buffer is created and cannot be changed
  • Limit indicates the current end of the buffer. You cannot read or write the buffer beyond the Limit. And the limit can be modified
  • Position, the index of the next element to be read or written. The value is changed each time buffer data is read or written in preparation for the next read or write operation
  • Mark Mark
package com.zhj.test.bio;

import java.nio.IntBuffer;

/ * * *@author zhj
 */
public class BasicBuffer {

    public static void main(String[] args) {
        // Illustrate the use of Buffer
        // Create a Buffer
        IntBuffer intBuffer = IntBuffer.allocate(5);
        // Store data to Buffer
        for (int i = 0; i < intBuffer.capacity(); i++) {
            intBuffer.put(i * 2);
        }
        // Read data from Buffer
        // Convert Buffer to read/write
        /* public final Buffer flip() { limit = position; position = 0; mark = -1; return this; } * /
        intBuffer.flip();
        // Set the read location
        intBuffer.position(2);
        // Set the end of the read
        intBuffer.limit(4);

        while(intBuffer.hasRemaining()) { System.out.println(intBuffer.get()); }}}public class NIOByteBufferPutGet {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(64);
        buffer.putInt(100);
        buffer.putLong(9L);
        buffer.putChar('strong');
        buffer.putShort((short) 4); buffer.flip(); System.out.println(buffer.getInt()); System.out.println(buffer.getLong()); System.out.println(buffer.getChar()); System.out.println(buffer.getShort()); }}public class ReadOnlyBuffer {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(64);

        for (int i = 0; i < 64; i++) {
            buffer.put((byte) i);
        }
        buffer.flip();
        ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
        System.out.println(readOnlyBuffer.getClass());

        while (readOnlyBuffer.hasRemaining()) {
            System.out.println(readOnlyBuffer.get());
        }
        // Read-only cannot store data
        // readOnlyBuffer.put((byte) 1);}}/** * MappedByteBuffer * 1. Files can be modified directly in memory (off-heap memory) without having to be copied by the operating system *@author zhj
 */
public class MappedByteBufferTest {
    public static void main(String[] args) throws Exception {
        File file1 = new File("E:\\data_file\\log1.txt");
        File file2 = new File("E:\\data_file\\log2.txt");
        RandomAccessFile randomAccessFile = new RandomAccessFile(file1, "rw");
        FileChannel fileChannel = randomAccessFile.getChannel();
        /** * parameters (1 read/write mode, 2 start position, 3 map to memory size) */
        MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE,0.5);
        mappedByteBuffer.put(0, (byte) 'H');
        mappedByteBuffer.put(3, (byte) '9');

        randomAccessFile.close();
        System.out.println("Modified successfully ~"); }}Copy the code

1.2.2 Channel Channel Usage

Basic introduction

1) NIO channels are similar to streams, but the differences are as follows

  • A channel can read and write simultaneously, whereas a stream can only read or write
  • Channels can read and write data asynchronously
  • A channel can read data from and write data to a buffer

2) A stream in BIO is unidirectional. For example, a FileinputStream can only read data, while a channel in NIO is bidirectional and can be read or written

3) A Channel is an interface in NIO

4) Common Channel classes include FileChannel, DatagramChannel, ServerSocketChannel, and SocketChannel

5) FileChannel is used for file data reading and writing, DatagramChannel is used for UDP data reading and writing, and ServerSocketChannel and SocketChannel are used for TCP data reading and writing

FileChannel class

  • Read reads channel data into a buffer
  • Write Writes buffer data to a channel
  • TransferFrom () Copies data from the destination channel to the current channel
  • TransferTo () copies data from the current channel to the destination channel
/ / case
/ / write
public class NIOFileChannel01 {
    public static void main(String[] args) throws IOException {
        String str = "hello world";
        // Create an output stream
        FileOutputStream fileOutputStream = new FileOutputStream("E:\\data_file\\log.txt");
        // Get the corresponding fileChannel via fileOutputStream
        // The fileChannel real type is FileChannelImpl
        FileChannel fileChannel = fileOutputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        // put STR in
        byteBuffer.put(str.getBytes());
        // Read/write switch
        byteBuffer.flip();
        / / write ChannelfileChannel.write(byteBuffer); fileOutputStream.close(); }}/ / read
public class NIOFileChannel02 {
    public static void main(String[] args) throws IOException {
        File file = new File("E:\\data_file\\log.txt");
        // Create an output stream
        FileInputStream fileInputStream = new FileInputStream(file);
        // Get the corresponding fileChannel via fileOutputStream
        // The fileChannel real type is FileChannelImpl
        FileChannel fileChannel = fileInputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());
        // Read the file into the buffer
        fileChannel.read(byteBuffer);
        // Read/write switch
        // byteBuffer.flip();
        System.out.println(newString(byteBuffer.array())); fileInputStream.close(); }}/ / to read and write

            }
            // Write buffer to fileChannel02byteBuffer.flip(); fileChannel02.write(byteBuffer); } fileInputStream.close(); fileOutputStream.close(); }}// Copy the file
public class NIOFileChannel04 {
    public static void main(String[] args) throws IOException {
        File file1 = new File("E:\\data_file\\img01.jpg");
        File file2 = new File("E:\\data_file\\img02.jpg");
        // Create an output stream
        FileInputStream fileInputStream = new FileInputStream(file1);
        FileChannel fileChannel01 = fileInputStream.getChannel();
        // Create an output stream
        FileOutputStream fileOutputStream = new FileOutputStream(file2);
        FileChannel fileChannel02 = fileOutputStream.getChannel();
        fileChannel02.transferFrom(fileChannel01,0, fileChannel01.size()); fileInputStream.close(); fileOutputStream.close(); }}Copy the code

ScatteringAndGathering

/** * Scattering writes data to buffer, using the buffer array, write * Gathering to read data into buffer *@author zhj
 */
public class ScatteringAndGatheringTest {
    public static void main(String[] args) throws IOException {
        // Use ServerSocketChannel and SocketChannel networks
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);

        // Bind the port to the Socket and start
        serverSocketChannel.socket().bind(inetSocketAddress);

        // Create a buffer array
        ByteBuffer[] byteBuffers = new ByteBuffer[2];
        byteBuffers[0] = ByteBuffer.allocate(5);
        byteBuffers[1] = ByteBuffer.allocate(3);

        // Wait for the client to connect
        SocketChannel socketChannel = serverSocketChannel.accept();
        int messageLength = 8; // Suppose 8 are received from the client
        while (true) {
            int byteRead = 0;

            while (byteRead < messageLength) {
                long read = socketChannel.read(byteBuffers);
                byteRead += read;
                // System.out.println("byteRead = " + byteRead);
                Arrays.asList(byteBuffers).stream().map(
                        buffer -> "postion = " + buffer.position() + ", limit = " + buffer.limit())
                        .forEach(System.out::println);
            }
            / / buffer inversion
            Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());
            // Display the data to the client
            long byteWrite = 0;
            while (byteWrite < messageLength) {
                long write = socketChannel.write(byteBuffers);
                byteWrite += write;
            }

            Arrays.asList(byteBuffers).forEach(buffer -> buffer.clear());

            System.out.println("byteRead = " + byteRead);
            System.out.println("byteWrite = " + byteWrite);
            System.out.println("messageLength = "+ messageLength); }}}Copy the code

1.2.3 Use of Selector

The characteristics of

  1. Netty’s IO thread NioEventLoop aggregates selectors (also known as multiplexers) and can process hundreds or thousands of client connections concurrently.
  2. When a thread reads or writes data from a client Socket channel, it can perform other tasks if no data is available.
  3. Threads often spend idle time of non-blocking IO performing IO operations on other channels, so a single thread can manage multiple input and output channels.
  4. Since both read and write operations are non-blocking, this greatly improves the efficiency of THE I/O thread and avoids thread suspension due to frequent I/O blocking.
  5. A single I/O thread can concurrently process N client connections and read/write operations, which fundamentally solves the traditional synchronous blocking I/O connection-thread model, and greatly improves the architecture performance, elastic scalability and reliability.

Method: open()

  • The selector. The select () blocks
  • Select (1000) blocks for 1s and returns
  • The selector. Wakeup () wake up
  • The selector. SelectNow () is not blocked

1. NIO implementation

NIO introduction case

ator.next();
                // Event-driven
                if (key.isAcceptable()) {
                    System.out.println("New client connection");
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    // Set it to non-blocking
                    socketChannel.configureBlocking(false);
                    // register selector for associated Buffer
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                    System.out.println("Generate non-blocking socketChannel:" + socketChannel.hashCode());
                }
                if (key.isReadable()) {
                    // Obtain the corresponding channel through the key
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    // Get the Buffer associated with the channel
                    ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
                    socketChannel.read(byteBuffer);
                    System.out.println("Client:" + new String(byteBuffer.array()));
                }
                // Manually remove the current key from the collection to prevent multiple readsiterator.remove(); }}}}public class NIOClient {
    public static void main(String[] args) throws Exception {
        // 1. Get a network channel
        SocketChannel socketChannel = SocketChannel.open();
        // 2. Set non-blocking
        socketChannel.configureBlocking(false);
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1".6666);
        // 3. Connect to the server
        if(! socketChannel.connect(inetSocketAddress)) {while(! socketChannel.finishConnect()) { System.out.println("The client will not block because the connection takes time.");
            }
        }
        System.out.println("Client connection server connection successful!");
        // 4. Set the content to be sent
        String str = "hello world!!!";
        // 5. Wrap allocates data according to the size of byte arrayByteBuffer buffer = ByteBuffer.wrap(str.getBytes()); socketChannel.write(buffer); System.in.read(); }}Copy the code

1.3 Comparison of NIO and BIO

  1. BIO processes data as a stream, while NIO processes data as a block, which is much more efficient than stream IO
  2. BIO is blocking, NIO is non-blocking
  3. BIO operates based on byte streams and character streams, while NIO operates based on Channel channels and Buffer buffers, where data is always read from a Channel to a Buffer or written from a Buffer to a Channel. The Sellector selector is used to listen for multiple channel events such as connection requests, data arrivals, etc., so you can listen for multiple client channels using a single thread

2 NIO group chat

The service side

public class GroupChatServer {

    // Define the related attributes
    private Selector selector;
    private ServerSocketChannel serverSocketChannel;
    private static final int PORT = 8888;

    / / the constructor
    public GroupChatServer(a) {
        try {
            selector = Selector.open();
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch(IOException e) { e.printStackTrace(); }}// Read client information
    private void readData(SelectionKey key) {
        / / define
        SocketChannel socketChannel = null;
        try {
            socketChannel = (SocketChannel) key.channel();
            / / create a buffer
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int count = socketChannel.read(buffer);
            if (count > 0) {
                // Convert buffer data to string output
                String msg = new String(buffer.array());
                // Outputs the message
                System.out.println("From client:" + msg);
                // Forward the messagesendInfoToOtherClients(msg, socketChannel); }}catch (Exception e) {
            try {
                System.out.println(socketChannel.getRemoteAddress() + "Offline");
                key.cancel();
                socketChannel.close();
            } catch(IOException ioException) { ioException.printStackTrace(); }}}private void sendInfoToOtherClients(String msg, SocketChannel self) {
        System.out.println("Server forwarding message...");
        // Iterate over all registrations in selector and exclude yourself
        try {
            for (SelectionKey key : selector.keys()) {
                Channel targetChannel = key.channel();
                if (targetChannel instanceofSocketChannel && targetChannel ! = self) {/ / transformationSocketChannel socketChannel = (SocketChannel) targetChannel; ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes()); socketChannel.write(buffer); }}}catch(IOException e) { e.printStackTrace(); }}/ / to monitor
    public void listen(a) {
        try {
           while (true) {
               int count = selector.select(2000);
               if (count > 0) {
                   Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                   while (iterator.hasNext()) {
                       SelectionKey key = iterator.next();
                       if (key.isAcceptable()) {
                           SocketChannel socketChannel = serverSocketChannel.accept();
                           socketChannel.configureBlocking(false);
                           socketChannel.register(selector, SelectionKey.OP_READ);
                           System.out.println(socketChannel.getRemoteAddress() + "Online!");
                       }
                       if (key.isReadable()) {
                           // Handle the read methodreadData(key); } iterator.remove(); }}else {
                   // system.out. println(" wait... ") );}}}catch(Exception e) { e.printStackTrace(); }}public static void main(String[] args) {
        GroupChatServer server = newGroupChatServer(); server.listen(); }}Copy the code

The client

public class GroupChatClient {
    private final String HOST = "127.0.0.1";
    private final int PORT = 8888;
    private Selector selector;
    private SocketChannel socketChannel;
    private String username;

    public GroupChatClient(a) {
        try {
            selector = Selector.open();
            socketChannel = SocketChannel.open(new InetSocketAddress(HOST,PORT));
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);
            username = socketChannel.getLocalAddress().toString().substring(1);
            System.out.println(username + "Ready...");
        } catch(Exception e) { e.printStackTrace(); }}public void sendInfo(String info) {
        info = username + ":" + info;
        try {
            socketChannel.write(ByteBuffer.wrap(info.getBytes()));
        } catch(Exception e) { e.printStackTrace(); }}public void readInfo(a) {
        try {
            int readChannels = selector.select();
            if (readChannels > 0) {
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    if (key.isReadable()) {
                        SocketChannel sc = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        sc.read(buffer);
                        String msg = newString(buffer.array()); System.out.println(msg.trim()); } iterator.remove(); }}else {
                // system.out. println(" No channel available... ") );}}catch(Exception e) { e.printStackTrace(); }}public static void main(String[] args) {
        GroupChatClient client = new GroupChatClient();

        new Thread() {
            public void run(a) {
                while (true) {
                    client.readInfo();
                    try {
                        Thread.sleep(3000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNextLine()) { String msg = scanner.nextLine(); client.sendInfo(msg); }}}Copy the code

3 zero copy

  1. From an operating system perspective, no data is duplicated between kernel buffers
  2. Not only does zero copy result in less data replication, it also brings other performance benefits, such as fewer context switches, fewer CPU cache pseudo-shares, and cpuless checksum computation

SendFile optimized 2.1 is not 2.4 is zero copy

Difference between mmap and SendFile

  • Mmap is suitable for reading and writing small amounts of data, while sendFile is suitable for transferring large files
  • Mmap requires four context switches and three data copies: sendFile requires three context switches and at least two data copies
  • SendFile can use DMA to reduce CPU copying, mmap cannot (must be copied from kernel to Socket buffer)

case

// Traditional IO server
public class OldIOServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(7001);

        while (true) {
            Socket socket = serverSocket.accept();
            DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
            try {
                byte[] bytes = new byte[4096];
                while (true) {
                    int readCount = dataInputStream.read(bytes, 0, bytes.length);
                    if (-1 == readCount) {
                        break; }}}catch(IOException e) { e.printStackTrace(); }}}}// Traditional IO server
public class OldIOClient {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("127.0.0.1".7001);
        String fileName = "";
        InputStream inputStream = new FileInputStream(fileName);
        DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
        byte[] bytes = new byte[4096];
        long readCount;
        long total = 0;
        long startTime = System.currentTimeMillis();

        while ((readCount = inputStream.read(bytes)) > 0) {
            total += readCount;
            dataOutputStream.write(bytes);
        }
        System.out.println("Total bytes sent:" + total + "Time:"+ (System.currentTimeMillis()-startTime)); dataOutputStream.close(); socket.close(); inputStream.close(); }}/ / new
public class NewIOServer {
    public static void main(String[] args) throws Exception {
        InetSocketAddress address = new InetSocketAddress(7002);
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        ServerSocket socket = serverSocketChannel.socket();
        socket.bind(address);

        ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            int readCount = 0;
            while (-1! = readCount) {try {
                    readCount = socketChannel.read(byteBuffer);
                } catch (IOException e) {
                    e.printStackTrace();
                    break;
                }
                byteBuffer.rewind(); Position = 0 mark = -1}}}}public class NewIOClient {
    public static void main(String[] args) throws Exception {
        InetSocketAddress address = new InetSocketAddress("127.0.0.1".7002);
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(address);
        String fileName = "";

        FileChannel channel = new FileInputStream(fileName).getChannel();
        long startTime = System.currentTimeMillis();
        // The next Linux transferTo method completes the transfer
        // Windows can only send 8m when used down, so you need to transfer files in segments. Note that the transfer location needs to be calculated in a loop
        // Use zero copy
        long transferCount = channel.transferTo(0, channel.size(), socketChannel);
        System.out.println("Total bytes sent:" + transferCount + "Time:"+ (System.currentTimeMillis()-startTime)); channel.close(); }}Copy the code

4 AIO understand

  1. JDK 7 introduces Asynchronous I/O, also known as AIO. In I/O programming, two modes are commonly used; Reactor and Proactor. Java’s NIO is a Reactor. When an event is triggered, the server gets notified and acts accordingly
  2. AIO is NIO 2.0, called asynchronous non-blocking IO.AIO introduces the concept of asynchronous channel, using the Proactor mode, simplify the writing of the program, effective request to start the thread, it is characterized by the operating system to complete the first notify the server program to start the thread to deal with, generally applicable to the number of connections and connections for a long time
  3. Currently AIO is not widely used, and Netty is based on NIO, not AIO
BIO NIO AIO
IO model A synchronized block Synchronous non-blocking (multiplexing) Asynchronous nonblocking
Programming difficulty simple complex load
reliability poor good good
throughput low high high