Java NIO (New I/O)는 Java 1.4 버전에서 도입된 **비동기 I/O (Asynchronous I/O)**를 지원하는 강력한 I/O 라이브러리입니다. 특히, 대규모 네트워크 서버나 파일 시스템과의 I/O 처리가 중요한 애플리케이션에서 매우 유용합니다. Java NIO의 가장 큰 특징은 non-blocking I/O, 버퍼 (Buffer), 채널 (Channel), 셀렉터 (Selector) 등의 개념을 제공하여 높은 성능과 효율적인 자원 관리를 가능하게 만든다는 점입니다.
이번에는 Java NIO의 고급 사용법을 비동기 처리, 파일 처리, 서버 구현 등의 측면에서 심도 깊게 다뤄보겠습니다.
Java NIO 주요 개념
- Channel:
- NIO에서 데이터의 입출력 작업을 수행하는 통로입니다.
- FileChannel, SocketChannel, DatagramChannel, ServerSocketChannel 등이 있습니다.
- NIO의 채널은 기본적으로 non-blocking 모드로 동작할 수 있어, 서버 애플리케이션에서 더 많은 클라이언트를 동시에 처리할 수 있습니다.
- Buffer:
- 데이터는 Buffer 객체를 통해 읽기와 쓰기가 이루어집니다.
- ByteBuffer, CharBuffer, IntBuffer 등 여러 종류가 있으며, 기본적인 버퍼링 기능을 제공합니다.
- 버퍼는 데이터를 읽거나 쓸 때 position, limit, capacity 등을 통해 제어합니다.
- Selector:
- 여러 채널을 감시하고, I/O 작업을 비동기적으로 처리할 수 있는 메커니즘입니다.
- 여러 채널에서 입력/출력 이벤트가 발생했을 때 해당 이벤트를 **선택(select)**하여 처리합니다.
- Selector는 non-blocking I/O 환경에서 매우 중요한 역할을 합니다.
Java NIO 고급 사용법 예시
1. NIO를 사용한 고성능 파일 I/O 처리
Java NIO의 FileChannel을 사용하면 파일을 비동기적으로 읽고 쓸 수 있습니다. **메모리 맵핑 (Memory-mapping)**을 활용하면 파일 데이터를 메모리처럼 직접 다룰 수 있어 성능을 극대화할 수 있습니다.
예시: FileChannel을 이용한 대용량 파일 읽기 및 쓰기
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class FileChannelExample {
public static void main(String[] args) throws IOException {
// 큰 파일을 읽고 쓰는 예시
File inputFile = new File("largeInputFile.txt");
File outputFile = new File("largeOutputFile.txt");
try (FileChannel inputChannel = new FileInputStream(inputFile).getChannel();
FileChannel outputChannel = new FileOutputStream(outputFile).getChannel()) {
// 메모리 맵핑을 이용한 파일 읽기
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (inputChannel.read(buffer) > 0) {
buffer.flip(); // 버퍼를 읽기 모드로 설정
outputChannel.write(buffer);
buffer.clear(); // 버퍼를 다시 쓰기 모드로 설정
}
System.out.println("File copy completed.");
}
}
}
핵심 포인트:
- FileChannel은 대용량 파일 처리에 매우 효율적입니다.
- ByteBuffer를 사용하여 데이터를 버퍼링하고, I/O 성능을 최적화할 수 있습니다.
2. Non-blocking 서버 소켓 구현 (ServerSocketChannel과 Selector)
NIO의 ServerSocketChannel과 Selector를 활용하면, 비동기 방식으로 다수의 클라이언트 요청을 동시에 처리할 수 있습니다. Selector는 여러 채널을 동시에 감시할 수 있기 때문에, 하나의 스레드로 다수의 클라이언트 요청을 처리할 수 있습니다.
예시: Non-blocking 서버 소켓 구현
import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;
public class NioServer {
public static void main(String[] args) throws IOException {
// 서버 소켓 채널을 열고 설정
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(8080));
// 셀렉터 열기
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 셀렉터에서 이벤트가 발생할 때까지 대기
selector.select();
// 셀렉터에 등록된 키를 순회하면서 이벤트 처리
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (key.isAcceptable()) {
// 클라이언트 연결 수락
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
System.out.println("New client connected: " + client.getRemoteAddress());
} else if (key.isReadable()) {
// 클라이언트로부터 데이터 읽기
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(256);
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
client.close();
} else {
buffer.flip();
client.write(buffer); // 데이터를 다시 클라이언트로 전송
}
}
}
}
}
}
핵심 포인트:
- ServerSocketChannel과 Selector를 사용하여 non-blocking 서버를 구현합니다.
- Selector는 여러 입력/출력 이벤트를 처리하며, 하나의 스레드로 여러 클라이언트를 동시에 처리할 수 있습니다.
- 클라이언트 요청을 비동기적으로 처리하여 성능을 최적화합니다.
3. Java NIO를 활용한 비동기 네트워크 클라이언트
NIO를 사용하면 클라이언트에서도 non-blocking I/O 방식으로 네트워크 통신을 할 수 있습니다. 이를 통해 대기 시간 없이 다른 작업을 병렬로 처리할 수 있습니다.
예시: Non-blocking 클라이언트 구현
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.net.*;
public class NioClient {
public static void main(String[] args) throws IOException {
// 클라이언트 소켓 채널 열기
SocketChannel clientChannel = SocketChannel.open();
clientChannel.configureBlocking(false);
clientChannel.connect(new InetSocketAddress("localhost", 8080));
// 셀렉터 열기
Selector selector = Selector.open();
clientChannel.register(selector, SelectionKey.OP_CONNECT);
while (true) {
// 셀렉터에서 이벤트 대기
selector.select();
// 셀렉터의 키를 순회
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (key.isConnectable()) {
SocketChannel channel = (SocketChannel) key.channel();
if (channel.isConnectionPending()) {
channel.finishConnect(); // 연결 완료
System.out.println("Connected to server.");
}
channel.register(selector, SelectionKey.OP_WRITE);
} else if (key.isWritable()) {
// 서버에 데이터 전송
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(256);
buffer.put("Hello, Server!".getBytes());
buffer.flip();
channel.write(buffer);
System.out.println("Message sent to server.");
channel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// 서버로부터 데이터 수신
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(256);
int bytesRead = channel.read(buffer);
if (bytesRead == -1) {
channel.close();
} else {
buffer.flip();
String response = new String(buffer.array(), 0, bytesRead);
System.out.println("Received from server: " + response);
}
}
}
}
}
}
핵심 포인트:
- SocketChannel을 사용하여 non-blocking 방식으로 서버에 연결하고 데이터를 주고받습니다.
- Selector를 사용하여 I/O 이벤트를 처리하며, 메인 스레드에서 다른 작업을 수행하면서도 클라이언트와 서버 간의 통신을 비동기적으로 처리합니다.
NIO를 활용한 고급 팁
- 메모리 맵핑 (Memory-mapped Files):
- **MappedByteBuffer**를 사용하면 파일을 메모리에 직접 매핑하여 매우 빠른 파일 처리가 가능합니다. 이는 대용량 파일을 처리하는 애플리케이션에서 속도와 효율성을 크게 개선할 수 있습니다.
- Selector와 여러 채널을 동시에 관리:
- 여러 네트워크 채널, 파일 채널을 동시에 처리해야 하는 경우, Selector와 Multiplexing 기법을 활용하여 스레드 수를 최소화하고 시스템 성능을 최적화할 수 있습니다.
- 배치 작업 처리:
- Batch I/O operations: 여러 개의 I/O 작업을 한 번에 처리하거나, 특정 시점에 한 번에 모든 데이터를 읽거나 쓸 수 있습니다. 이를 통해 I/O 성능을 최적화할 수 있습니다.
정리
Java NIO는 비동기 I/O, Selector, Channel, Buffer 등을 활용하여 고성능 서버나 대규모 I/O 처리를 할 수 있도록 해줍니다. 이를 통해 단일 스레드로도 많은 클라이언트를 처리할 수 있으며, 성능을 크게 개선할 수 있습니다. Java NIO는 대용량 데이터 처리나 고성능 네트워크 서버에서 매우 유용하게 사용됩니다.
'Java' 카테고리의 다른 글
writeObject와 readObject 메서드를 통한 커스텀 직렬화 (0) | 2025.07.17 |
---|---|
Spring Boot 기반 MQTT → Kafka 브릿지 아키텍처 구조에서 4가지 설정 (2) | 2025.06.27 |
Kafka가 하는 역할(확장성과 안정성) (1) | 2025.06.24 |
클라이언트 (임베디드 Windows 장비): WebSocket을 통해 서버 통신 2 (1) | 2025.06.24 |
클라이언트 (임베디드 Windows 장비): WebSocket을 통해 서버 통신 1 (0) | 2025.06.24 |