네, 나머지 세 가지 I/O 방식에 대한 예제 코드를 Java를 사용하여 만들어 보겠습니다. 각 예제는 간략하게 I/O 작업을 시뮬레이션하고, 각 방식의 특징을 보여주는 데 초점을 맞출 것입니다.
1. 비동기 논블로킹 I/O (Asynchronous Non-blocking I/O)
이 방식은 I/O 요청을 시작한 후 즉시 반환되며, I/O 작업이 완료되면 콜백 함수나 이벤트 핸들러를 통해 결과를 통지받습니다. 스레드는 I/O 작업이 진행되는 동안 다른 작업을 수행할 수 있으며, 결과를 기다리는 동안 블로킹되지 않습니다. Java NIO.2 (Asynchronous I/O) API를 사용하여 구현합니다.
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future;
public class AsyncNonBlockingIOExample {
public static void main(String[] args) throws Exception {
Path file = Path.of("async_nonblocking_test.txt");
try (AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(file, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;
// CompletionHandler를 사용하여 I/O 작업 완료 시 콜백 처리
CompletionHandler<Integer, Void> handler = new CompletionHandler<Integer, Void>() {
@Override
public void completed(Integer result, Void attachment) {
if (result > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println("비동기 논블로킹 읽기 완료: " + new String(data));
} else {
System.out.println("파일 끝에 도달했습니다.");
}
}
@Override
public void failed(Throwable exc, Void attachment) {
System.err.println("비동기 논블로킹 읽기 실패: " + exc.getMessage());
}
};
System.out.println("비동기 논블로킹 읽기 요청 시작...");
fileChannel.read(buffer, position, null, handler);
// 메인 스레드는 I/O 작업 완료를 기다리지 않고 다른 작업을 수행할 수 있습니다.
System.out.println("메인 스레드는 다른 작업을 수행 중...");
Thread.sleep(5000); // 5초 동안 다른 작업 시뮬레이션
// 또는 Future를 사용하여 결과를 기다릴 수도 있습니다.
ByteBuffer buffer2 = ByteBuffer.allocate(1024);
Future<Integer> future = fileChannel.read(buffer2, 0);
System.out.println("비동기 논블로킹 읽기 요청 (Future) 시작...");
// future.get()을 호출하면 작업이 완료될 때까지 블로킹됩니다 (비동기 블로킹 I/O와 유사).
// 여기서는 논블로킹 예시이므로 get()을 바로 호출하지 않습니다.
System.out.println("메인 스레드는 Future를 통해 결과를 기다리지 않고 계속 작업...");
Thread.sleep(3000);
if (future.isDone()) {
int result2 = future.get();
if (result2 > 0) {
buffer2.flip();
byte[] data2 = new byte[buffer2.remaining()];
buffer2.get(data2);
System.out.println("비동기 논블로킹 읽기 완료 (Future): " + new String(data2));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}2. 동기 블로킹 I/O (Synchronous Blocking I/O)
가장 기본적인 I/O 방식입니다. I/O 요청을 하면 작업이 완료될 때까지 스레드는 블로킹되어 아무런 작업을 수행하지 못합니다. Java의 InputStream과 OutputStream 등의 기본적인 I/O 스트림이 이 방식을 사용합니다.
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class SyncBlockingIOExample {
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("sync_blocking_test.txt")) {
System.out.println("동기 블로킹 읽기 시작...");
int data;
while ((data = inputStream.read()) != -1) {
System.out.print((char) data);
}
System.out.println("\n동기 블로킹 읽기 완료.");
} catch (IOException e) {
e.printStackTrace();
}
}
}3. 동기 논블로킹 I/O (Synchronous Non-blocking I/O)
이 방식에서는 I/O 요청을 하면 즉시 반환됩니다. 그러나 I/O 작업이 완료되었는지 여부를 확인하기 위해 반복적으로 시스템 호출을 해야 합니다 (폴링). 데이터가 준비되지 않았으면 다시 시도해야 하므로, CPU를 비효율적으로 사용할 수 있습니다. Java NIO (New I/O)의 채널과 셀렉터를 사용하여 구현합니다.
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class SyncNonBlockingIOExample {
public static void main(String[] args) {
try (SocketChannel socketChannel = SocketChannel.open()) {
socketChannel.configureBlocking(false); // 논블로킹 설정
InetSocketAddress address = new InetSocketAddress("localhost", 8080);
System.out.println("동기 논블로킹 연결 시도...");
socketChannel.connect(address);
// 연결이 완료될 때까지 폴링
while (!socketChannel.finishConnect()) {
System.out.println("연결 시도 중...");
Thread.sleep(100); // 짧은 간격으로 재시도
}
System.out.println("연결 성공!");
ByteBuffer buffer = ByteBuffer.allocate(1024);
String message = "Hello from non-blocking client!";
buffer.put(message.getBytes());
buffer.flip();
System.out.println("동기 논블로킹 쓰기 시도...");
while (buffer.hasRemaining()) {
socketChannel.write(buffer);
}
System.out.println("동기 논블로킹 쓰기 완료.");
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
System.out.println("동기 논블로킹 읽기 시도...");
int bytesRead = socketChannel.read(readBuffer);
if (bytesRead > 0) {
readBuffer.flip();
byte[] data = new byte[readBuffer.remaining()];
readBuffer.get(data);
System.out.println("동기 논블로킹 읽기 완료: " + new String(data));
} else if (bytesRead == 0) {
System.out.println("읽을 데이터 없음.");
} else if (bytesRead == -1) {
System.out.println("연결 종료.");
socketChannel.close();
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}참고:
- 비동기 논블로킹 I/O 예제는 파일 I/O를 대상으로 했으며, 콜백 (
CompletionHandler)과Future를 사용하는 두 가지 방식을 간략히 보여줍니다. 실제 네트워크 프로그래밍에서는 셀렉터와 함께 사용하여 여러 채널의 I/O 이벤트를 효율적으로 처리합니다. - 동기 논블로킹 I/O 예제는 소켓 채널을 사용하여 연결 시도, 쓰기, 읽기 작업을 논블로킹 방식으로 수행하는 것을 보여줍니다. 연결 및 읽기 작업 완료 여부를 반복적으로 확인하는 폴링 방식이 특징입니다.
이 예제 코드를 통해 각 I/O 방식의 기본적인 동작 방식을 이해하는 데 도움이 되셨기를 바랍니다. 각 방식은 특정 상황과 요구 사항에 따라 장단점을 가지므로, 시스템의 특성과 성능 요구 사항을 고려하여 적절한 I/O 방식을 선택하는 것이 중요합니다. 😊