• NIO学习笔记


    NIO学习笔记

    NIO是什么?

    NIO(Non-blocking I/O)是Java中的一种高性能I/O模型,用于处理大量并发连接。与传统的阻塞式I/O模型不同,NIO允许在单个线程上管理多个通道(网络连接或文件IO),并使用选择器(Selector)实现非阻塞式的事件驱动IO操作。这种方式可以大大减少线程的数量,提高系统的并发能力和性能。

    哪些场景需要 NIO

    Java NIO 适用于需要高性能、高并发的网络应用程序场景。以下是一些适合使用Java NIO的场景:

    1. 高并发的网络服务器:NIO允许一个线程管理多个连接,这使得它非常适合处理高并发的网络通信,比如Web服务器、聊天服务器等。

    2. 实时通信应用:对于需要实时处理消息、低延迟的应用程序,NIO提供了非阻塞IO和事件驱动的特性,能够更好地满足这类需求。

    3. 大规模文件传输:如果需要传输大量的数据,特别是大文件,NIO的非阻塞IO特性可以使得系统更高效地利用资源。

    4. 多协议支持:NIO可以轻松地支持多种协议,如TCP、UDP等,因为它提供了更灵活的IO操作方式。

    5. 游戏服务器:游戏服务器通常需要处理大量的并发连接和实时通信,NIO能够提供良好的性能和可扩展性,满足游戏服务器的需求。

    总的来说,如果应用程序需要处理大量的并发连接、需要实时通信或者需要高性能的网络IO操作,那么就适合使用Java NIO。

    代码示例

    下面是一个简单的Java NIO实现的聊天弹幕服务器和客户端的代码示例:

    // 服务器端
    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.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    
    public class ChatServer {
        private static final int PORT = 8080;
    
        public static void main(String[] args) throws IOException {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            serverSocketChannel.configureBlocking(false);
    
            Selector selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    
            System.out.println("Server started on port " + PORT);
    
            while (true) {
                selector.select();
    
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
    
                    if (key.isAcceptable()) {
                        acceptConnection(selector, serverSocketChannel);
                    } else if (key.isReadable()) {
                        readMessage(key);
                    }
                }
            }
        }
    
        private static void acceptConnection(Selector selector, ServerSocketChannel serverSocketChannel) throws IOException {
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);
            System.out.println("New client connected: " + socketChannel.getRemoteAddress());
        }
    
        private static void readMessage(SelectionKey key) throws IOException {
            SocketChannel channel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int bytesRead = channel.read(buffer);
    
            if (bytesRead == -1) {
                channel.close();
                key.cancel();
                return;
            }
    
            buffer.flip();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            String message = new String(bytes).trim();
            System.out.println("Received message: " + message);
    
            broadcastMessage(channel, message);
        }
    
        private static void broadcastMessage(SocketChannel sender, String message) throws IOException {
            ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
    
            for (SelectionKey key : sender.selector().keys()) {
                if (key.isValid() && key.channel() instanceof SocketChannel && key.channel() != sender) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    channel.write(buffer);
                    buffer.rewind();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    // 客户端
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SocketChannel;
    import java.util.Scanner;
    
    public class ChatClient {
        private static final String SERVER_HOST = "localhost";
        private static final int SERVER_PORT = 8080;
    
        public static void main(String[] args) {
            try {
                SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(SERVER_HOST, SERVER_PORT));
    
                Thread receiverThread = new Thread(() -> {
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    try {
                        while (true) {
                            buffer.clear();
                            int bytesRead = socketChannel.read(buffer);
                            if (bytesRead == -1) {
                                break;
                            }
                            buffer.flip();
                            byte[] bytes = new byte[buffer.remaining()];
                            buffer.get(bytes);
                            String message = new String(bytes).trim();
                            System.out.println("Received message: " + message);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
                receiverThread.start();
    
                Scanner scanner = new Scanner(System.in);
                while (true) {
                    String message = scanner.nextLine();
                    ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
                    socketChannel.write(buffer);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    这个简单的示例实现了一个基于Java NIO的简易聊天弹幕系统,其中服务器接受来自客户端的连接,并将客户端发送的消息广播给所有连接的客户端。客户端可以发送消息到服务器,并接收其他客户端发送的消息。

    NIO如何实现允许在单个线程上管理多个通道?

    Java NIO 实现允许在单个线程上管理多个通道的核心是通过 Selector(选择器)机制。Selector 是一个多路复用器,用于监听多个通道的事件,并且当事件发生时,能够通知应用程序进行处理。Selector 提供了一种高效的方式来处理多个通道的 I/O 事件,避免了传统的阻塞 I/O 模型中需要为每个连接创建一个线程的开销。

    Selector 的工作原理如下:

    1. 将多个 Channel 注册到 Selector 上,并指定感兴趣的事件,如读、写、连接、接收等。
    2. Selector 负责监听这些通道上发生的事件,一旦某个通道上的事件发生,Selector 就会返回对应的 SelectionKey。
    3. 应用程序可以遍历 SelectionKey,获取发生事件的通道,然后进行相应的处理。

    这种机制使得一个线程可以同时管理多个通道的 I/O 操作,大大减少了线程的数量,提高了系统的并发性能。

    Java NIO 实现允许在单个线程上管理多个通道的核心是通过 Selector(选择器)机制。Selector 是一个多路复用器,用于监听多个通道的事件,并且当事件发生时,能够通知应用程序进行处理。Selector 提供了一种高效的方式来处理多个通道的 I/O 事件,避免了传统的阻塞 I/O 模型中需要为每个连接创建一个线程的开销。

    Selector 的工作原理如下:

    1. 将多个 Channel 注册到 Selector 上,并指定感兴趣的事件,如读、写、连接、接收等。

    2. Selector 负责监听这些通道上发生的事件,一旦某个通道上的事件发生,Selector 就会返回对应的 SelectionKey。

    3. 应用程序可以遍历 SelectionKey,获取发生事件的通道,然后进行相应的处理。

    这种机制使得一个线程可以同时管理多个通道的 I/O 操作,大大减少了线程的数量,提高了系统的并发性能。

    以下是一个简单的示例代码,演示了如何使用 Selector 实现单个线程管理多个通道:

    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.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    
    public class NioServer {
        private static final int PORT = 8080;
    
        public static void main(String[] args) throws IOException {
            // 创建 Selector
            Selector selector = Selector.open();
    
            // 创建 ServerSocketChannel 并绑定端口
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式
            // 将 ServerSocketChannel 注册到 Selector,并指定关注的事件为接收连接事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    
            System.out.println("Server started on port " + PORT);
    
            while (true) {
                // Selector 调用 select() 方法进行监听,阻塞直到有事件发生
                selector.select();
    
                // 获取就绪的事件集合
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
    
                    if (key.isAcceptable()) { // 如果是接收连接事件
                        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                        SocketChannel clientChannel = serverChannel.accept(); // 接收客户端连接
                        clientChannel.configureBlocking(false); // 设置为非阻塞模式
                        clientChannel.register(selector, SelectionKey.OP_READ); // 注册读事件
                        System.out.println("Accepted new connection from " + clientChannel.getRemoteAddress());
                    } else if (key.isReadable()) { // 如果是可读事件
                        SocketChannel clientChannel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        int bytesRead = clientChannel.read(buffer); // 读取数据到缓冲区
                        if (bytesRead != -1) {
                            buffer.flip(); // 切换为读模式
                            byte[] data = new byte[buffer.remaining()];
                            buffer.get(data);
                            System.out.println("Received message: " + new String(data));
                        } else {
                            clientChannel.close(); // 关闭连接
                        }
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
  • 相关阅读:
    Docker Swarm 集群搭建
    接口自动化测试数据驱动DDT模块使用
    07-Redis缓存设计
    起重机控制电路接线 - 基础与进阶(修订中...)
    ElementUI组件-日期时间控件设置禁用日期
    鸿鹄工程项目管理系统em Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统
    解决远程视频会议卡顿问题,优化企业网络办公体验
    API对接是一种在不同系统或应用程序之间共享数据和功能的方式
    Vega Prime入门教程14.04:CDB测试
    Portraiture3.5升级版磨皮滤镜插件使用效果教程
  • 原文地址:https://blog.csdn.net/u010720890/article/details/136732256