Java NIO程式設計示例

神圣兽国窝窝乡独行侠發表於2024-09-21

服務端:

package org.example;

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.nio.channels.spi.SelectorProvider;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NIOServer {
    static ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {

        try {
            // 開啟一個選擇器
            Selector selector = SelectorProvider.provider().openSelector();

            // 開啟一個伺服器套接字通道
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.configureBlocking(false);

            // 繫結到指定埠
            serverChannel.socket().bind(new InetSocketAddress(8081));

            // 將伺服器通道註冊到選擇器上,並指定關注的事件為接收連線
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("伺服器已啟動,監聽埠8080...");

            while (true) {
                // 選擇器等待就緒的通道
                selector.select();

                // 獲取就緒的選鍵集合
                Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();

                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    keyIterator.remove();

                    // 檢查選鍵的狀態並進行相應的處理
                    if (key.isAcceptable()) {
                        handleAccept(key);
                    } else if (key.isReadable()) {
                        processMsg(key);
                    } else if (key.isWritable()) {
                        // 通常這裡不會處理寫就緒,因為寫操作通常由應用邏輯觸發
                    } else if (key.isConnectable()) {
                        // 這是客戶端連線的情況,對於伺服器來說通常不會遇到
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            System.err.println("伺服器發生異常,已關閉。");
        } finally {
            executorService.shutdown();
        }
    }

    private static void handleAccept(SelectionKey key) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel clientChannel = serverChannel.accept();
        clientChannel.configureBlocking(false);

        // 註冊到選擇器並關注讀事件
        clientChannel.register(key.selector(), SelectionKey.OP_READ);
        System.out.println("接受到新的客戶端連線:" + clientChannel.getRemoteAddress());
    }

    private static void sendData(SocketChannel socketChannel, String message) {
        ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
        while (buffer.hasRemaining()) {
            try {
                socketChannel.write(buffer);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void processMsg(SelectionKey key) {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(2);
        StringBuilder sb = new StringBuilder();
        try {
            while (true) {
                int bytesRead = clientChannel.read(buffer);
                if (bytesRead == -1) { // 客戶端關閉連線
                    clientChannel.close();
                    break;
                } else if (bytesRead == 0) { // 訊息讀取結束
                    break;
                }
                buffer.flip();
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
                sb.append(new String(bytes, StandardCharsets.UTF_8));
                buffer.clear();
            }
            if (!clientChannel.isOpen()) return;
            executorService.submit(() -> {
                System.out.println("收到訊息:" + sb);
                sendData(clientChannel, "abcdaaaaaaaaaaaaaaabbbbbbbbccccccccccccccccccccccccccccd");
            });
        } catch (Exception e) {
            // 發生讀錯誤,關閉連線並列印錯誤資訊
            System.err.println("讀錯誤:" + e.getMessage());
            try {
                clientChannel.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}

客戶端:

package org.example;

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

public class NIOClient {

    public static void main(String[] args) {
        // 伺服器的地址和埠
        String serverAddress = "localhost";
        int serverPort = 8081;

        try {
            // 開啟一個套接字通道
            SocketChannel socketChannel = SocketChannel.open();
            // 配置為非阻塞模式
            socketChannel.configureBlocking(false);

            // 嘗試連線到伺服器
            if (socketChannel.connect(new InetSocketAddress(serverAddress, serverPort))) {
                // 連線成功,可以直接傳送資料(但在非阻塞模式下,這通常不會發生)
                sendData(socketChannel, "Hello, Server!");
            } else {
                // 連線可能需要時間,等待連線完成(在實際應用中,這通常是在一個迴圈中完成的)
                while (!socketChannel.finishConnect()) {
                    // 可以在這裡執行其他任務,比如更新使用者介面等
                    // 但在這個簡單的例子中,我們只是空轉等待連線完成
                }
                // 連線完成,傳送資料
                for (int index = 1; index <= 5; index++) {
                    sendData(socketChannel, "Hello, Server!");
                }
            }

            // 讀取伺服器的響應(在非阻塞模式下,這可能需要多次嘗試)
            StringBuilder sb = new StringBuilder();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while (socketChannel.read(buffer) <= 0) {
                // 如果沒有資料可讀,可以在這裡執行其他任務
                // 但在這個簡單的例子中,我們只是空轉等待資料到來
            }

            // 伺服器傳送過來的所有資料
            buffer.flip();
            String response = new String(buffer.array(), 0, buffer.limit());
            sb.append(response);
            while (socketChannel.read(buffer) > 0) {
                buffer.flip();
                response = new String(buffer.array(), 0, buffer.limit());
                sb.append(response);
            }
            System.out.println("Received from server: " + sb);
            // 關閉連線
            socketChannel.close();
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("Client failed to connect to server or read response.");
        }
        System.out.println("finish");
    }

    private static void sendData(SocketChannel socketChannel, String message) throws Exception {
        ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
        while (buffer.hasRemaining()) {
            socketChannel.write(buffer);
        }
    }
}

執行效果:

相關文章