I/O 작업은 Input/Output의 약자로, 주로 파일 입출력을 다룰 때 사용된다. 예를 들어, 두 대 이상의 컴퓨터끼리 네트워크를 통해 통신할 때, 한 컴퓨터에서 출력(send)을 하고 다른 컴퓨터에서 입력(receive)을 받는 과정을 통해 통신할 수 있다. I/O 작업은 User Level에서 직접 수행할 수 없고, 실제 I/O 작업을 수행하는 것은 Kernel Level에서만 가능하다. User Process나 Thread는 커널에게 요청하고, 작업 완료 후 커널이 반환하는 결과를 기다릴 뿐이다.
가장 기본적인 I/O 모델로, 리눅스에서 모든 소켓 통신은 기본적으로 blocking으로 동작한다. I/O 작업이 진행되는 동안 유저 프로세스는 자신의 작업을 중단한 채 대기하는 방식이다.
read
작업을 요청한다.이 모델에서는 작업이 진행되는 동안 어플리케이션이 다른 작업을 수행하지 못하고 대기하게 되므로 자원이 낭비될 수 있다.
위와 같은 blocking 방식의 비효율성을 극복하고자 도입된 방식이다. I/O 작업이 진행되는 동안 유저 프로세스의 작업을 중단시키지 않는 방식이다.
read
작업을 요청한다.이 모델에서는 I/O의 진행 시간과 관계없이 어플리케이션이 작업을 중지하지 않고도 I/O 작업을 진행할 수 있다. 하지만 반복적으로 시스템 호출이 발생하므로 자원이 낭비될 수 있다.
import java.io.*;
import java.net.*;
public class BlockingIOExample {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(6666)) {
Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println(inputLine);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
public class NonBlockingIOExample {
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(6666));
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
for (SelectionKey key : selector.selectedKeys()) {
if (key.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(256);
socketChannel.read(buffer);
String result = new String(buffer.array()).trim();
System.out.println(result);
buffer.clear();
}
}
selector.selectedKeys().clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Blocking I/O와 Non-Blocking I/O는 각각의 장단점을 가지고 있다. Blocking I/O는 구현이 간단하고 이해하기 쉬운 반면, Non-Blocking I/O는 자원을 더 효율적으로 사용할 수 있다. 작업의 특성과 요구 사항에 따라 적절한 I/O 모델을 선택하는 것이 중요하다.