I look at how to pull down the altars NIO

I look at how to pull down the altars NIO

1. The conventional blocking I / O

Blocking I / O refers to blocking, socket read function, write function is blocked.

1.2 blocking I / O programming model

public static void main(String[] args) {
        
        try (ServerSocket serverSocket = new ServerSocket()) {
            // 绑定端口
            serverSocket.bind(new InetSocketAddress(8081));
            while (true) {

                // 轮询established
                Socket socket = serverSocket.accept();

                new Thread(() -> {
                    try (BufferedReader buffer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                         PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true)) {
                        // 读消息
                        while (true) {
                            String body = buffer.readLine();
                            if (body == null) {
                                break;
                            }
                            log.info("receive body: {}", body);
                        }

                        // 写消息
                        printWriter.write("server receive message!");

                    } catch (Exception e) {
                        log.error(e.getMessage());
                    }
                }).start();
            }

        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
复制代码

Because the socket accept function, read function, write function is a synchronous blocking, so the main thread continue to accept the socket function calls, polling status of a TCP connection is established.

read function will read from the kernel buffer data has been prepared, copied to the user process, if there is no data in the kernel buffer, then the thread will be suspended, and the corresponding right to use the cpu is released. When the kernel buffer data ready, cpu will respond to I / O interrupt signal, wake-threaded processing data to be blocked.

When a connection when processing I / O, the system is blocked, if it is single-threaded, then inevitably hung and died there; but the CPU is released, opening multiple threads, you can let the CPU to handle more things .

Blocking I / O model

Disadvantage of blocking of I / O

Lack of scalability, heavily dependent on the thread. Java thread take up memory in the 512K-1M, the number of threads too much can cause JVM memory overflow. A large number of thread context switching serious drain on CPU performance. A large number of I / O thread is active will cause the system jagged load.

2. NIO programming

Synchronous non-blocking I / O model

For the NIO, the kernel buffer if there is no data on the direct return an error EWOULDBLOCK general polling process can call the read function, when the buffer with data to copy data to user space, rather than a hung thread .

So synchronous non-blocking in non-blocking refers to the socket read and write functions are not blocked, but users still need to poll the process to read and write function, it is synchronized. But NIO provides us might not need a new thread since you can use the CPU, which is the I / O multiplexing

2.1 I / O multiplexing

In linux system, you can use the select / poll / epoll monitor multiple socket using a thread, as long as there is a socket read cache data, the method returns immediately, and then you can read this readable socket, and if All socket read buffer is empty, it will be blocked, that is, the thread is suspended.

Started using linux using a select, but selct more slowly, eventually using epoll.

2.1.1 epoll advantage

  1. Supports open socket descriptor (FD) is only limited by the operating system handles the maximum number of files, select maximum support 1024.
  2. selcet every time to scan all of the socket, and epoll scan only active socket.
  3. Mmap using acceleration data in the kernel space to user space copy.

2.2 NIO working mechanism

NIO is actually an event-driven model , NIO is the most important thing multiplexer (Selector) . In NIO it provides the ability to choose ready events, we just need to channel (Channel) registered with the Selector, Selector will select the method by (actually the operating system through the epoll) continually polls its registry on the Channel, If there is a Channel on the reading readiness, writing readiness or the connection will be coming out of the polling Selector, then SelectionKey (registered Channel Selector to return SelectionKey and bind it when on) can get to the Channel of the collection is ready otherwise Selector will be blocked on the select method.

Selector call the select method, it is not ready to choose a thread through the Channel for loop, but the operating system by way of epoll event notification JVM thread which channel is ready for reading or writing occurred ready event. So select methods like a listener.

Multiplexed core purpose is to use the least threads to operate more channels, not only one thread within it. The number of threads created is based on the number of channels to the decision, each registered 1023 channels to create a new thread.

NIO is the core of multiplexers and event model , we can actually figure out these two to figure out the basic principle of the NIO. The original NIO in learning mixed feelings about it, along with in-depth understanding of the TCP found NIO is not difficult. When using the NIO, the core is the generation of the Channel and to listen to the event registration Selector.

Different types of events channel support

NIO event model schematic diagram :

2.2.1 Code Example

ServerReactor

@Slf4j
public class ServerReactor implements Runnable {
    private final Selector selector;
    private final ServerSocketChannel serverSocketChannel;
    private volatile boolean stop = false;

    public ServerReactor(int port, int backlog) throws IOException {
        selector = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(new InetSocketAddress(port), backlog);
        serverSocket.setReuseAddress(true);
        serverSocketChannel.configureBlocking(false);
        // 将channel注册到多路复用器上,并监听ACCEPT事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    public void setStop(boolean stop) {
        this.stop = stop;
    }

    @Override
    public void run() {
        try {
            // 无限的接收客户端连接
            while (!stop && !Thread.interrupted()) {
                int num = selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectionKeys.iterator();
                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    // 移除key,否则会导致事件重复消费
                    it.remove();
                    try {
                        handle(key);
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (selector != null) {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    private void handle(SelectionKey key) throws Exception {
        if (key.isValid()) {
            // 如果是ACCEPT事件,代表是一个新的连接请求
            if (key.isAcceptable()) {
                ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                // 相当于三次握手后,从全连接队列中获取可用的连接
                // 必须使用accept方法消费ACCEPT事件,否则将导致多路复用器死循环
                SocketChannel socketChannel = serverSocketChannel.accept();
                // 设置为非阻塞模式,当没有可用的连接时直接返回null,而不是阻塞。
                socketChannel.configureBlocking(false);
                socketChannel.register(selector, SelectionKey.OP_READ);
            }

            if (key.isReadable()) {
                SocketChannel socketChannel = (SocketChannel) key.channel();
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                int readBytes = socketChannel.read(readBuffer);
                if (readBytes > 0) {
                    readBuffer.flip();
                    byte[] bytes = new byte[readBuffer.remaining()];
                    readBuffer.get(bytes);
                    String content = new String(bytes);
                    System.out.println("recv client content: " + content);
                    ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                    writeBuffer.put(("服务端已收到: " + content).getBytes());
                    writeBuffer.flip();
                    socketChannel.write(writeBuffer);

                } else if (readBytes < 0) {
                    key.cancel();
                    socketChannel.close();
                }
            }

        }
    }
}
复制代码

ClientReactor

public class ClientReactor implements Runnable {
    final String host;
    final int port;
    final SocketChannel socketChannel;
    final Selector selector;
    private volatile boolean stop = false;

    public ClientReactor(String host, int port) throws IOException {
        this.socketChannel = SocketChannel.open();
        this.socketChannel.configureBlocking(false);
        Socket socket = this.socketChannel.socket();
        socket.setTcpNoDelay(true);
        this.selector = Selector.open();
        this.host = host;
        this.port = port;

    }

    @Override
    public void run() {

        try {
            // 如果通道呈阻塞模式,则立即发起连接;
            // 如果呈非阻塞模式,则不是立即发起连接,而是在随后的某个时间才发起连接。

            // 如果连接是立即建立的,说明通道是阻塞模式,当连接成功时,则此方法返回true,连接失败出现异常。
            // 如果此通道处于阻塞模式,则此方法的调用将会阻塞,直到建立连接或发生I/O错误。

            // 如果连接不是立即建立的,说明通道是非阻塞模式,则此方法返回false
            // 并且以后必须通过调用finishConnect()方法来验证连接是否完成
            // socketChannel.isConnectionPending()判断此通道是否正在进行连接
            if (socketChannel.connect(new InetSocketAddress(host, port))) {
                socketChannel.register(selector, SelectionKey.OP_READ);
                doWrite(socketChannel);
            } else {
                socketChannel.register(selector, SelectionKey.OP_CONNECT);

            }
            while (!stop && !Thread.interrupted()) {
                int num = selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectionKeys.iterator();
                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    // 移除key,否则会导致事件重复消费
                    it.remove();
                    try {
                        handle(key);
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (selector != null) {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }


    }

    private void handle(SelectionKey key) throws IOException {

        if (key.isValid()) {

            SocketChannel socketChannel = (SocketChannel) key.channel();

            if (key.isConnectable()) {
                if (socketChannel.finishConnect()) {
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    doWrite(socketChannel);
                }
            }

            if (key.isReadable()) {
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                int readBytes = socketChannel.read(readBuffer);
                if (readBytes > 0) {
                    readBuffer.flip();
                    byte[] bytes = new byte[readBuffer.remaining()];
                    readBuffer.get(bytes);
                    System.out.println("recv server content: " + new String(bytes));
                } else if (readBytes < 0) {
                    key.cancel();
                    socketChannel.close();
                }
            }

        }
    }

    private void doWrite(SocketChannel socketChannel) {
        Scanner scanner = new Scanner(System.in);
        new Thread(() -> {
            while (scanner.hasNext()) {
                try {

                    ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                    writeBuffer.put(scanner.nextLine().getBytes());
                    writeBuffer.flip();
                    socketChannel.write(writeBuffer);
                } catch (Exception e) {

                }
            }
        }).start();
    }
}

复制代码

Reference article:

  1. Those myths gossip highly concurrent, Jingdong architects see how it pull down the altars
  2. On the Java NIO
  3. Berkeley common API functions use the TCP protocol Detailed
  4. "NIO Socket Technology and Programming Guide"

Guess you like

Origin juejin.im/post/5dfae986518825122671c846