NIO模式的JAVA实现

74 阅读3分钟

Socket

说起NIO就不得不说Socket这个一个抽象类门面类,它对TCP/IP协议进行了封装,对于我们来说只需要调用其提供的接口就可以实现。比如,我们创建了一个监听本地8080端口的socket服务端,这时用浏览器请求,则可以看到下面的打印信息。

ServerSocket serverSocket = new ServerSocket(8080);
while (true){
    Socket socket = serverSocket.accept();
    InputStream input = socket.getInputStream();

    // 读取客户端发送的数据
    byte[] buffer = new byte[1024];
    int len = input.read(buffer);
    String request = new String(buffer, 0, len);
    System.out.println(request);
    OutputStream output = socket.getOutputStream();
    String response = "Hello, client!";
    output.write(response.getBytes());
    output.flush();
}
GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
sec-ch-ua: "Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
...

由上可以判断出,scoket封装的为TCP/IP协议而上层的HTTP或自定义的应用协议并未实现。它作为一个最基础的网络封装,http和ws都可以用它去进行实现,可自由实现长短连接,满足不同需求。

BIO与NIO概念

BIO

说起NIO就又不得不说说BIO(Blocking I/O),即阻塞IO,它的核心思想为:服务器其中一个服务线程阻塞等待客户端的连接,就像上述代码一样,serverSocket.accept();一直在等待客户端的连接。当这样的客户端与服务端连接成功后,我们可以通过上述代码看到input.read(buffer);主服务线程又在等待数据传输完毕,才能进行下一步的操作。这就是BIO的基本实现逻辑。

如果说我们想改变这样的一对一的处理模式,可以在处理任务时丢到一个线程池中去做,比如读数据时,把读的这个操作直接放入到一个线程池的线程去做处理,并且保留这个scoket连接达到可以实现长连接的效果。

NIO

与BIO相比,N就在于它为非阻塞。它是面向缓冲的而不是面向流。说的明白点就是,有一块地方放了些外部的过来的数据,你只需要检查那块地方有没有放着东西,有就拿走处理,没有就可以释放掉IO资源。 NIO的主要构成为Selector、Channel、Buffer 它们之间的关系为Selector可以选择多个Channel,然后从Buffer中读取想要的数据。我们可以通过发布订阅的模式去理解NIO,因为对比BIO来说NIO就相当于是把原本的确定连接、读写数据、关闭连接、保持连接这些流程全部分开,原本为一个人所做全流程,现在可以让多个人也就是多个线程去做每个流程的具体实现。比如服务端存在一个线程负责接收所有的连接请求,而其余的事情则交由其他线程处理,这样就能提高服务端的最大连接数量,也相应提高了并发量。若把读或写分离,我们则可以应对专门的需求去调整数据的传输。资源是有限的,如何调度、调度的策略是代码的所求之道。下面为NIO的JAVA实现示例:

服务端

public class Server {
    public static void main(String[] args) throws IOException {
        // 创建Selector
        Selector selector = Selector.open();

        // 创建ServerSocketChannel并注册到Selector上
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(8080));
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

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

            // 获取发生事件的SelectionKey集合
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();

            // 遍历SelectionKey集合
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                keyIterator.remove();

                // 根据事件的类型进行相应的处理
                if (key.isAcceptable()) {
                    // 处理连接请求
                    ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = serverChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    // 处理读事件
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int len = socketChannel.read(buffer);
                    if (len > 0) {
                        buffer.flip();
                        byte[] bytes = new byte[buffer.remaining()];
                        buffer.get(bytes);
                        System.out.println(new String(bytes));
                    }
                    buffer.flip();
                    socketChannel.write(buffer);
                }
            }
        }
    }
}

客户端

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8080);
        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        // 发送消息给服务器
        Scanner scanner = new Scanner(System.in);
        boolean flag = true;
        while (flag){
            String sendData = scanner.nextLine();
            if (sendData.equals("kill")){
                flag = false;
            }
            out.println(sendData);
            // 接收来自服务器的响应并打印
            String response = in.readLine();
            System.out.println("Received from server: " + response);
        }
        // 关闭客户端套接字
        socket.close();

    }
}