返回

从零开始认识Java NIO

后端

最近在学习Netty的相关知识,了解到Java NIO是一个高性能的网络编程框架,可以帮助我们开发高并发、高性能的网络应用。在学习过程中,我发现对NIO的基础知识了解得不够深入,所以对NIO的核心组件进行了梳理。

核心组件

Channel

Channel是Java NIO中最重要的组件之一,它代表了网络通信的通道,是数据传输的媒介。Channel可以分为两种类型:

  • 阻塞式Channel: 阻塞式Channel在读取或写入数据时会阻塞,直到数据传输完成。
  • 非阻塞式Channel: 非阻塞式Channel在读取或写入数据时不会阻塞,即使数据传输尚未完成。

Buffer

Buffer是Java NIO中用于存储数据的缓冲区。它可以存储各种类型的数据,如字节数组、字符串、整数等。当我们从Channel中读取数据时,数据会被存储到Buffer中。当我们向Channel中写入数据时,数据会从Buffer中读取。

Selector

Selector是Java NIO中用于管理多个Channel的组件。它可以同时监听多个Channel,并当其中一个Channel有数据可读或可写时通知我们。

工作原理

NIO通过事件驱动的方式来工作。当一个Channel有数据可读或可写时,Selector会通知我们。我们就可以从Channel中读取数据,或者向Channel中写入数据。

NIO通过这种方式可以同时监听多个Channel,并在数据可读或可写时及时通知我们,从而实现高并发、高性能的网络通信。

实例

现在,让我们通过一个简单的例子来了解NIO是如何工作的。

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class NIOServer {

    public static void main(String[] args) throws IOException {
        // 创建ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        // 绑定端口
        serverSocketChannel.bind(new InetSocketAddress(8080));

        // 设置非阻塞模式
        serverSocketChannel.configureBlocking(false);

        // 创建Selector
        Selector selector = Selector.open();

        // 将ServerSocketChannel注册到Selector上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // 阻塞等待事件发生
            selector.select();

            // 获取所有已发生的事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();

            // 遍历事件
            for (SelectionKey selectionKey : selectionKeys) {
                // 如果是accept事件
                if (selectionKey.isAcceptable()) {
                    // 获取客户端连接的SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();

                    // 设置SocketChannel为非阻塞模式
                    socketChannel.configureBlocking(false);

                    // 将SocketChannel注册到Selector上
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    // 获取SocketChannel
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

                    // 创建ByteBuffer
                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    // 从SocketChannel中读取数据到ByteBuffer中
                    int readBytes = socketChannel.read(buffer);

                    // 如果readBytes小于0,说明客户端连接已关闭
                    if (readBytes < 0) {
                        socketChannel.close();
                    } else {
                        // 将ByteBuffer中的数据转为字符串
                        String data = new String(buffer.array(), 0, readBytes);

                        // 输出数据
                        System.out.println("收到客户端数据:" + data);

                        // 将SocketChannel注册到Selector上
                        socketChannel.register(selector, SelectionKey.OP_WRITE);
                    }
                } else if (selectionKey.isWritable()) {
                    // 获取SocketChannel
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

                    // 创建ByteBuffer
                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    // 向SocketChannel中写入数据
                    buffer.put("Hello, client!".getBytes());
                    buffer.flip();
                    socketChannel.write(buffer);

                    // 将SocketChannel注册到Selector上
                    socketChannel.register(selector, SelectionKey.OP_READ);
                }
            }

            // 清空SelectionKeys集合
            selectionKeys.clear();
        }
    }
}

这个例子演示了一个简单的NIO服务器端。它监听8080端口,当有客户端连接时,它会接受连接并将其注册到Selector上。当客户端有数据可读时,服务器端会从客户端读取数据并输出到控制台。当服务器端有数据可写时,它会向客户端写入数据。