Java NIO 编程 - NIO Echo Server、NIO Client(NIO 异步客户端、NIO Selector 异步客户端)

一、NIO Echo Server

  • NioEchoServer.java
java 复制代码
public class NioEchoServer {
    public static void main(String[] args) {
        try {

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

            // 2. 创建 ServerSocketChannel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false); // 设置为非阻塞
            serverSocketChannel.bind(new InetSocketAddress(8080)); // 绑定端口

            // 3. 将 ServerSocketChannel 注册到 Selector,关注 ACCEPT 事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("[NIO Echo Server] start on port 8080...");

            // 4. 循环等待事件
            while (true) {
                int result = selector.select();
                if (result == 0) {
                    continue;
                }

                // 5. 获取事件的 SelectionKey 集合
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();

                    // 6. 处理事件
                    if (selectionKey.isAcceptable()) {

                        // 有连接
                        System.out.println("[NIO Echo Server] accept");
                        handleAccept(selectionKey, selector);
                    } else if (selectionKey.isReadable()) {

                        // 有数据可读
                        System.out.println("[NIO Echo Server] read");
                        handleRead(selectionKey);
                    }

                    iterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handleAccept(SelectionKey selectionKey, Selector selector) {
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();

        try {

            // 接受连接
            SocketChannel socketChannel = serverSocketChannel.accept();
            if (socketChannel == null) return;
            socketChannel.configureBlocking(false);

            // 将 SocketChannel 注册到 Selector,关注 READ 事件,并附加一个 ByteBuffer
            socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
            System.out.println("[NIO Echo Server] client connect: " + socketChannel.getRemoteAddress());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handleRead(SelectionKey selectionKey) {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();

        try {
            int bytesRead = socketChannel.read(byteBuffer);
            if (bytesRead == -1) {
                System.out.println("[NIO Echo Server] client disconnect: " + socketChannel.getRemoteAddress());
                socketChannel.close();
                return;
            }

            // 切换为读模式
            byteBuffer.flip();

            if (byteBuffer.hasRemaining()) {
                byte[] receivedData = new byte[byteBuffer.remaining()];
                byteBuffer.get(receivedData);
                String message = new String(receivedData);
                System.out.println("[NIO Echo Server] read: " + message);

                byteBuffer.clear();
                byteBuffer.put(receivedData);
                byteBuffer.flip();
            }
            while (byteBuffer.hasRemaining()) {
                socketChannel.write(byteBuffer);
            }

            byteBuffer.clear();
        } catch (IOException e) {
            e.printStackTrace();

            try {
                System.out.println("[NIO Echo Server] client disconnect: " + socketChannel.getRemoteAddress());
                socketChannel.close();
            } catch (IOException ex) {
                e.printStackTrace();
            }
        }
    }
}

二、Test Client

1、简单的阻塞客户端
java 复制代码
try (Socket socket = new Socket("localhost", 8080)) {
    PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

    Scanner scanner = new Scanner(System.in);
    while (true) {
        System.out.print("enter message: ");
        String message = scanner.nextLine();

        if ("exit".equalsIgnoreCase(message)) {
            break;
        }

        // 发送消息
        printWriter.println(message);

        // 接收消息
        String response = bufferedReader.readLine();
        System.out.println("echo: " + response);
    }
} catch (IOException e) {
    e.printStackTrace();
}
  1. Server 输出结果

    [NIO Echo Server] start on port 8080...
    [NIO Echo Server] accept
    [NIO Echo Server] client connect: /127.0.0.1:59465
    [NIO Echo Server] read
    [NIO Echo Server] read: 123

    [NIO Echo Server] read
    [NIO Echo Server] read: 456

  2. Client 输出结果

    enter message: 123
    echo: 123
    enter message: 456
    echo: 456

2、多线程并发客户端
java 复制代码
private static final int THREAD_COUNT = 3;
private static final int MESSAGES_PER_THREAD = 3;
java 复制代码
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);

for (int i = 0; i < THREAD_COUNT; i++) {
    final int clientId = i + 1;
    executorService.submit(() -> {
        try (Socket socket = new Socket("localhost", 8080)) {
            PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            for (int j = 0; j < MESSAGES_PER_THREAD; j++) {
                String message = "client-" + clientId + "-message-" + (j + 1);
                System.out.println("[client " + clientId + "] send message: " + message);
                printWriter.println(message);

                String response = bufferedReader.readLine();
                System.out.println("[client " + clientId + "] received: " + response);

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    });
}

try {
    countDownLatch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}
executorService.shutdown();
  1. Server 输出结果

    [NIO Echo Server] start on port 8080...
    [NIO Echo Server] accept
    [NIO Echo Server] client connect: /127.0.0.1:60733
    [NIO Echo Server] accept
    [NIO Echo Server] client connect: /127.0.0.1:60734
    [NIO Echo Server] accept
    [NIO Echo Server] client connect: /127.0.0.1:60732
    [NIO Echo Server] read
    [NIO Echo Server] read: client-3-message-1

    [NIO Echo Server] read
    [NIO Echo Server] read: client-2-message-1

    [NIO Echo Server] read
    [NIO Echo Server] read: client-1-message-1

    [NIO Echo Server] read
    [NIO Echo Server] read: client-3-message-2

    [NIO Echo Server] read
    [NIO Echo Server] read: client-2-message-2

    [NIO Echo Server] read
    [NIO Echo Server] read: client-1-message-2

    [NIO Echo Server] read
    [NIO Echo Server] read: client-3-message-3

    [NIO Echo Server] read
    [NIO Echo Server] read: client-2-message-3

    [NIO Echo Server] read
    [NIO Echo Server] read: client-1-message-3

    [NIO Echo Server] read
    [NIO Echo Server] client disconnect: /127.0.0.1:60732
    [NIO Echo Server] read
    [NIO Echo Server] client disconnect: /127.0.0.1:60734
    [NIO Echo Server] read
    [NIO Echo Server] client disconnect: /127.0.0.1:60733

  2. Client 输出结果

    [client 1] send message: client-1-message-1
    [client 3] send message: client-3-message-1
    [client 2] send message: client-2-message-1
    [client 3] received: client-3-message-1
    [client 2] received: client-2-message-1
    [client 1] received: client-1-message-1
    [client 2] send message: client-2-message-2
    [client 3] send message: client-3-message-2
    [client 3] received: client-3-message-2
    [client 2] received: client-2-message-2
    [client 1] send message: client-1-message-2
    [client 1] received: client-1-message-2
    [client 3] send message: client-3-message-3
    [client 2] send message: client-2-message-3
    [client 3] received: client-3-message-3
    [client 2] received: client-2-message-3
    [client 1] send message: client-1-message-3
    [client 1] received: client-1-message-3


三、Test NIO Client

1、NIO 异步客户端
java 复制代码
try {
    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.configureBlocking(false);
    socketChannel.connect(new InetSocketAddress("localhost", 8080));

    while (!socketChannel.finishConnect()) {
        System.out.println("connecting...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println("connect server");

    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    Scanner scanner = new Scanner(System.in);
    while (true) {
        System.out.print("enter message: ");
        String message = scanner.nextLine();

        if ("exit".equalsIgnoreCase(message)) {
            break;
        }

        // 发送消息
        byteBuffer.clear();
        byteBuffer.put(message.getBytes());
        byteBuffer.flip();
        while (byteBuffer.hasRemaining()) {
            socketChannel.write(byteBuffer);
        }

        // 接收消息
        byteBuffer.clear();
        int bytesRead;
        while ((bytesRead = socketChannel.read(byteBuffer)) == 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (bytesRead == -1) {
            System.out.println("disconnected");
            break;
        }
        byteBuffer.flip();
        byte[] responseBytes = new byte[byteBuffer.remaining()];
        byteBuffer.get(responseBytes);
        String response = new String(responseBytes);
        System.out.println("echo: " + response);
    }
} catch (IOException e) {
    e.printStackTrace();
}
  1. Server 输出结果

    [NIO Echo Server] start on port 8080...
    [NIO Echo Server] accept
    [NIO Echo Server] client connect: /127.0.0.1:61864
    [NIO Echo Server] read
    [NIO Echo Server] read: 123
    [NIO Echo Server] read
    [NIO Echo Server] read: 456

  2. Client 输出结果

    connect server
    enter message: 123
    echo: 123
    enter message: 456
    echo: 456

2、NIO Selector 异步客户端
java 复制代码
public class NioEchoClient {
    public static void main(String[] args) {
        try {
            Selector selector = Selector.open();

            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);

            // 注册连接事件
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
            socketChannel.connect(new InetSocketAddress("localhost", 8080));

            handleWrite(socketChannel);

            while (true) {
                int readyChannels = selector.select();
                if (readyChannels == 0) {
                    continue;
                }

                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectedKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();

                    if (selectionKey.isConnectable()) {
                        handleConnect(selectionKey, selector);
                    } else if (selectionKey.isReadable()) {
                        handleRead(selectionKey);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handleWrite(SocketChannel socketChannel) {
        new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            try {
                while (socketChannel.isConnected()) {
                    System.out.print("enter message: ");
                    String message = scanner.nextLine();

                    if ("exit".equalsIgnoreCase(message)) {
                        break;
                    }

                    ByteBuffer byteBuffer = ByteBuffer.wrap((message).getBytes());
                    while (byteBuffer.hasRemaining() && socketChannel.isConnected()) {
                        try {
                            socketChannel.write(byteBuffer);
                        } catch (IOException e) {
                            e.printStackTrace();
                            break;
                        }
                    }

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                scanner.close();
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private static void handleConnect(SelectionKey selectionKey, Selector selector) {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        try {
            if (socketChannel.finishConnect()) {
                System.out.println("connect server");
                socketChannel.register(selector, SelectionKey.OP_READ);
            } else {
                System.out.println("connect server fail");
                socketChannel.close();
            }
        } catch (IOException e) {
            e.printStackTrace();

            System.out.println("connect server error");
            try {
                socketChannel.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    private static void handleRead(SelectionKey selectionKey) {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

        ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
        if (byteBuffer == null) {
            byteBuffer = ByteBuffer.allocate(1024);
            selectionKey.attach(byteBuffer);
        }

        try {
            int bytesRead = socketChannel.read(byteBuffer);
            if (bytesRead == -1) {
                System.out.println("disconnected");
                socketChannel.close();
                return;
            }
            byteBuffer.flip();
            byte[] responseBytes = new byte[byteBuffer.remaining()];
            byteBuffer.get(responseBytes);
            byteBuffer.clear();
            String response = new String(responseBytes);
            System.out.println("echo: " + response);
        } catch (IOException e) {
            e.printStackTrace();

            try {
                System.out.println("disconnected");
                socketChannel.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}
  1. Server 输出结果

    [NIO Echo Server] start on port 8080...
    [NIO Echo Server] accept
    [NIO Echo Server] client connect: /127.0.0.1:65429
    [NIO Echo Server] read
    [NIO Echo Server] read: 123
    [NIO Echo Server] read
    [NIO Echo Server] read: 456

  2. Client 输出结果

    connect server
    enter message: 123
    echo: 123
    enter message: 456
    echo: 456

相关推荐
嗯、.1 小时前
使用Itext9生成PDF水印,兼容不同生成引擎的坐标系(如: Skia、OpenPDF)
java·pdf·itextpdf·openpdf·坐标变换矩阵
前端炒粉2 小时前
35.LRU 缓存
开发语言·javascript·数据结构·算法·缓存·js
kk哥88993 小时前
iOS开发:关于日志框架
网络·ios·cocoa
星释3 小时前
Rust 练习册 75:ETL与数据转换
开发语言·rust·etl
happyjoey2173 小时前
使用Qt自带的Maintenance Tool将Qt6.9升级为QT6.10
开发语言·qt
断剑zou天涯4 小时前
【算法笔记】窗口内最大值或最小值的更新结构
java·笔记·算法
m***66735 小时前
SQL 实战—递归 SQL:层级结构查询与处理树形数据
java·数据库·sql
鲸沉梦落6 小时前
Java中的Stream
java
yihuiComeOn7 小时前
[源码系列:手写Spring] AOP第二节:JDK动态代理 - 当AOP遇见动态代理的浪漫邂逅
java·后端·spring