Java NIOの導入

Java NIO(New IO)はJava 1.4から提供される新しいIO APIです。従来のIOと異なり、NIOは非同期かつ非ブロッキングなIOをサポートしており、単一のスレッドで複数の接続を管理できます。例えば、5つのクライアントが1つのサーバーに接続している場合、従来のIOではサーバー側で5つのスレッドが必要ですが、NIOを使用すれば1つのスレッドで管理でき、リソースの節約が可能となります。

NIOの主要なコンポーネントはChannel、Buffer、およびSelectorです。Channelはデータの読み書きを行うためのインターフェースで、Bufferはデータの格納場所として機能します。Selectorは複数のChannelを監視し、イベントが発生したChannelを選択して処理することで非同期かつ非ブロッキングな動作を実現します。

以下に、NIOを使って1つのサーバースレッドで5つのクライアント接続を管理するコード例を示します。

ServerApp.java:

package net.server;

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;
import java.util.Set;

public class ServerApp {

    private static final int BASE_PORT = 10000;
    private static final int BUFFER_SIZE = 4096;

    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();

        for (int i = 0; i < 5; i++) {
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.bind(new InetSocketAddress(BASE_PORT + i));
            serverChannel.configureBlocking(false);
            serverChannel.register(selector, SelectionKey.OP_ACCEPT, i);
            System.out.println("Server started at port " + (BASE_PORT + i));
        }

        ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);

        while (true) {
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();

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

                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel client = server.accept();
                    client.configureBlocking(false);
                    client.register(selector, SelectionKey.OP_READ, key.attachment());
                } else if (key.isReadable()) {
                    SocketChannel client = (SocketChannel) key.channel();
                    buffer.clear();
                    int bytesRead = client.read(buffer);
                    if (bytesRead > 0) {
                        String message = new String(buffer.array(), 0, bytesRead);
                        System.out.println("Received from client " + key.attachment() + ": " + message);
                        client.register(selector, SelectionKey.OP_WRITE, key.attachment());
                    }
                } else if (key.isWritable()) {
                    SocketChannel client = (SocketChannel) key.channel();
                    String response = "Response from server to client " + key.attachment();
                    buffer.clear();
                    buffer.put(response.getBytes());
                    buffer.flip();
                    client.write(buffer);
                    client.register(selector, SelectionKey.OP_READ, key.attachment());
                }
            }
        }
    }
}

ClientApp.java:

package net.client;

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.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class ClientApp implements Runnable {

    private final int clientId;
    private final int port;
    private final Selector selector;
    private final ByteBuffer readBuffer;
    private final ByteBuffer writeBuffer;
    private final SocketChannel channel;

    public ClientApp(int clientId) throws IOException {
        this.clientId = clientId;
        this.port = 10000 + clientId;
        this.selector = Selector.open();
        this.readBuffer = ByteBuffer.allocate(4096);
        this.writeBuffer = ByteBuffer.allocate(4096);
        this.channel = SocketChannel.open();
        this.channel.configureBlocking(false);
        this.channel.connect(new InetSocketAddress("localhost", port));
        this.channel.register(selector, SelectionKey.OP_CONNECT);
    }

    @Override
    public void run() {
        try {
            while (true) {
                selector.select();
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = keys.iterator();

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

                    if (key.isConnectable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        if (client.finishConnect()) {
                            System.out.println("Client " + clientId + " connected to server.");
                            writeBuffer.clear();
                            writeBuffer.put(("Message from client " + clientId).getBytes());
                            writeBuffer.flip();
                            client.write(writeBuffer);
                            client.register(selector, SelectionKey.OP_READ);
                        }
                    } else if (key.isReadable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        readBuffer.clear();
                        int bytesRead = client.read(readBuffer);
                        if (bytesRead > 0) {
                            String message = new String(readBuffer.array(), 0, bytesRead);
                            System.out.println("Client " + clientId + " received from server: " + message);
                            client.register(selector, SelectionKey.OP_WRITE);
                        }
                    } else if (key.isWritable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        writeBuffer.clear();
                        writeBuffer.put(("Next message from client " + clientId).getBytes());
                        writeBuffer.flip();
                        client.write(writeBuffer);
                        client.register(selector, SelectionKey.OP_READ);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        for (int i = 0; i < 5; i++) {
            new Thread(new ClientApp(i)).start();
        }
    }
}

タグ: Java NIO 非同期IO 非ブロッキングIO Selector

7月11日 17:32 投稿