NIO
Java NIO 는 JDK 1.4 에 적용된 패키지로 기존의 Java I/O 느린 단점을 보완
Java IO
- 파일의 경로와 이름 등의 메타 데이터를 포함한 InputStream, OutputStream 객체 생성
- InputStream 및 OutputStream 객체를 사용하여 read(), write() 등의 메서드 호출
이 메서드들은 JVM을 통해 해당 플랫폼의 운영체제와 상호 작용한다. - JVM은 운영 체제의 파일 시스템 API를 사용하여 파일에 대한 실제 읽기 및 쓰기 작업을 한다. 이 때, 운영 체제는 해당 파일을 찾아 디스크에서 읽기 작업을 수행하거나, 쓰기 작업을 수행하여 디스크에 데이터를 저장한다.
- 운영 체제는 파일로부터 읽은 데이터를 커널의 버퍼에 저장한 뒤 JVM의 사용자 공간 버퍼로 복사하거나, 사용자 공간 버퍼의 데이터를 파일 시스템으로 전달한다. 이 과정에서 대체로 Thread 를 block 시킨다.
Java IO 와 NIO
IO
- 동기 방식, 블로킹 방식, 단방향 전송, 스트림
- 입력 스트림과 출력 스트림이 구분되어 있으며 별도로 생성해야 한다.
- 버퍼를 사용하지 않으며 1바이트씩 읽고 출력하기 때문에 속도가 느리다.
- BufferdInputStream, BufferedOutputStream, BufferedReader, BufferedWriter와 같은 보조 스트림을 별도로 제공하여 버퍼 기능을 지원한다.
- 스트림으로부터 입력된 전체 데이터를 별도로 저장하지 않으면, 입력 데이터의 위치를 이동하면서 자유롭게 이용할 수 없다.
- 블로킹을 빠져나오려면 스트림을 닫아야 한다.
- 연결 클라이언트 수가 적고 전송되는 데이터가 대용량이며 순차적으로 처리될 필요가 있는 경우 사용
NIO
- 채널, 스트림, 동기, 비동기, 블로킹, 넌블로킹, 양방향, 버퍼
- 하나의 채널로 데이터를 읽고 쓰기가 가능한 양방향 전송
- 버퍼를 사용해서 입출력 성능이 좋으며 버퍼에 저장하기 때문에 버퍼 내에서 데이터 위치를 이동해가며 필요한 부분을 읽고 쓸 수 있다.
- interrupt를 사용해 블로킹을 빠져나올 수 있다.
- 연결 클라이언트 수가 많고 전송되는 데이터 용량이 적으며, 입출력 작업 처리가 빨리 끝나는 경우 사용
Selector
여러 채널을 동시에 감시하여 데이터가 읽거나 쓸 준비가 되어있는지 확인
여러 채널에서 발생하는 I/O 이벤트를 동시에 처리할 수 있게 하여 단일 스레드로 여러 I/O 연산을 처리한다. 이를 통해 하나의 스레드가 여러 I/O 작업을 비동기로 처리할 수 있다.
Channel
파일이나 소켓과 같은 I/O 대상과 연결되어 있는, 데이터가 읽고 쓰이는 통로
실제 데이터 전송을 담당하며 파일, 네트워크 소켓 등 다양한 데이터 소스와 연결되고, 데이터는 채널을 통해 읽거나 쓸 수 있다.
실제로 read, write 를 수행하는 주체이다.
Buffer
채널에서 읽거나 쓴 데이터를 임시로 저장하는 메모리 블록 NIO의 모든 데이터는 버퍼를 통해 처리되며, 채널과 버퍼 간 데이터 이동이 발생한다. 채널과 상호작용하여 데이터를 저장하는 메모리 블록을 데이터는 채널에서 버퍼로 읽히거나, 버퍼에서 채널로 쓰인다.
Selector는 여러 Channel을 등록하여 감시한다. 데이터가 준비된 채널이 있으면 셀렉터가 이것을 확인하고 해당 채널에 관련된 버퍼를 통해 데이터를 처리한다. 이를 통해서 하나의 스레드가 여러 I/O 작업을 처리합니다.
Blocking은 채널에서 데이터가 준비될 때까지 스레드가 대기한다. Non-blocking 의 경우 채널에서 데이터가 준비되지 않은 경우 블로킹 되지 않고 다른 작업을 수행하고, 데이터가 준비되면 나중에 다시 확인한다.
Non-blocking 구현하기 위해서는 configureBlocking(false)를 사용하여 non-blocking 모드로 설정한다. 그러면 채널의 I/O 작업이 블로킹되지 않고 반환한다.
즉 데이터가 준비되지 않은 경우 read(), write() 호출이 블로킹 되지 않고 -1, 0을 반환하여 바로 다른 작업을 할 수 있다.
Selector의 select() 메서드를 호출해서 준비된 채널을 확인 한다. 채널이 준비될때까지 블로킹되거나, 준비된 채널이 없으면 반환됩니다.
준비된 채널을 얻으면 해당 채널을 통해서 read(), write() 통해 데이터를 읽거나 사용할 수 있다.
select()가 반환한 값 중 0은 준비된 채널이 없음을 의미하며, 양수가 나오는 경우 준비된 채널의 수를 의미한다.
selectedKeys()
selectedKeys() 메서드 호출을 통해 준비된 채널의 집합을 얻을 수 있으며, 각 selectedKey를 확인해 준비된 작업을 수행할 수 있다.
작업 완료 후 selectionKey를 selectedKeys에서 제거하여 다음 select() 호출에서 중복으로 처리되지 않도록 한다.
interest set
SelectionKey에서 감시하고 싶은 채널의 I/O 읽기 또는 쓰기 이벤트를 감시하도록 설정ready set
Selector가 select() 또는 selectNow()로 감지한 채널의 현재 상태 Selector가 현재 준비된 I/O 작업을 가진 채널의 SelectionKey 객체들을 가지고 있으며 이 값은 select(), selectNow() 호출을 통해 받은 채널의 수와 동일하.
select() 와 selectNow()
두 메서드의 차이는 블로킹 여부와 대기시간에 있다.
select()
select()는 채널이 준비될 때까지 블로킹된다. 지정된 시간 동안 대기하며, 준비된 채널의 수를 반환한다. 준비된 채널이 없는 경우 스레드를 대기 상태로 두어 준비된 채널이 있을때까지 기다린다.
selectNow()
블로킹 없이 현재 준비된 채널의 상태를 실시간으로 확인하는 메서드로 비동기로 채널 상태를 검사할 때 유용하다. 스레드를 대기시키지 않고 빠르게 상태를 확인할 수 있지만 호출이 빈번하게 일어나느 경우 CPU 사용률이 증가할 수 있다. 준비된 채널이 없으면 즉시 반환되며, 대기하지 않는다.
select()에서 블로킹을 피하기 위한 대안으로 타임아웃을 설정하여 지정된 시간동안만 대기하거나, selectNow()를 사용할 수 있다. 또는 여러 개의 selector()을 사용하면 여러 개의 채널을 동시에 관리할 수 있.