Site Search :
Standard Enterprise XML Methodology Pattern Setting Tunning Other
Article Contributors
GuestBook
Javapattern Maven
XSourceGen Dev
JetSpeed Test
JLook Image
jLook Family Site


NIO Channel
 
스터디 자료를 아티클로 올립니다. 새로운 primitive I/O 추상화인 Channel을 이해하도록 한다. File Channel을 통한 Channel의 구현과 특징을 배우도록 한다. 비동기적 입출력을 이해하고 ServerSocketChannel과 SocketChannel을 이용하여 이전 버전의 입출력과 비교하여본다. ( 2003/09/03 ) 223
Written by ienvyou - 최지웅
1 of 1
 

이번 장에서 다룰  주제는 New I/O라고 불리는 J2SE 1.4에 있어서의 많은 변경 사항들 중 
일부인 Channel이다
앞에서 .기존의 Java I/O와 비교하여 Buffer의 사용으로 performance를 향상시키고, garbage를 
줄일 수 있다는 것을 배웠을 것이다. Channel은 실제 작업의 이행을 위해 이런 buffer를 필요로 
하는데(사실, channel은 buffer를 사용하기위한 수단이라 봐도 과언이 아니다). 

이번 장에서는 NIO활용을 위해 Channel에 대한 전반적인 개요와 특징에 대해  살펴보도록 하겠다.

<스터디내용>

- Channel Basics 
- File Channels
- ServerSocketChannel 클래스와 SocketChannel 클래스 
- etc

<스터디목표>

- 새로운 primitive I/O 추상화인 Channel을 이해하도록 한다.
- File Channel을 통한 Channel의 구현과 특징을 배우도록 한다.
- 비동기적 입출력을 이해하고 ServerSocketChannel과 SocketChannel을 이용하여 이전 버전의 
입출력과 비교하여본다.

 

▶새로운 IO의 추상화인 채널의 특징-java.nio.channels.Channel

1.1.1 - Channel Basics 

Channel은 하드웨어 디바이스, 파일, 네트워크 소켓 등이 상호간에 읽기나 쓰기 등의 입출력 작업을 
한 개 이상 수행할 수 있는 추상화된 계층이다. 예를 들면, java.nio.channels.Channel interface는 
서브인터페이스로부터의 직접적인 읽기와 쓰기는 지원하지 않고, 채널 인터페이스는 다음과 같은 
메소드만을 갖고 있다.


    public interface Channel {
      void close() throws IOException;
      boolean isOpen();
    }
기존의 I/O와 비교해 본다면, stream과 같다고 할 수 있다. 채널들은 이전의 스트림-기반 모델들과 비교하였을 때 여러 가지 이점을 제공하고 있다:
  1. 추상적 클래스 보다는 인터페이스를 사용하는 확장성
  2. 채널을 통해 읽기와 쓰기 능력을 모두 보여주는 것이 가능한 양방향성 (cf.inputstream,outputstream)
  3. 파일 잠금과 메모리 매핑 등과 같은 기능들의 지원
  4. 비동기적 클로징과 중단 (interruption) 을 위한 명확한 스토리 (즉,NIO 채널은 비동기적으로 닫히고 중단될 수 있어,한 스레드가 한 채널에서 하나의 입출력작업으로 블록화 했다면 다른 스레드가 그 채널을 닫을 수있다는 것이다)
  5. 버퍼 객체들과의 긴밀한 통합
처음의 네 가지 기능들도 중요하기는 하지만 실제로 세 번째I/O 데이터 모델의 생성을 정당화하는 것은 바로 마지막 것이다. 버퍼는 New I/O의 향상된 기능에 있어서 중요한 역할을 하고, 채널들은 버퍼의 읽기와 쓰기를 위해 구축된 객체들이다. (NIO패키지에서 모든 데이터는 버퍼를 사용해 다루어진다.즉, byte를 직접 channel에 쓰거나,읽을 수 없고, byte들을 담은 buffer를 사용해야 한다. ) 그림 1에 보여지는 계층도에서와 같이 java.nio.channels 패키지는 여섯 가지의 주요 인터페이스를 보유하고 있다 ByteChannel interface

public interface ReadableByteChannel extends Channel {
  int read(ByteBuffer dst) throws IOException;
}
public interface WritableByteChannel extends Channel {
  int write(ByteBuffer src) throws IOException;
}
public interface ByteChannel extends ReadableByteChannel,
 WritableByteChannel {
  // 새로운 메소드 없음
}
분산과 집합 (Scattering and Gathering) ScatteringByteChannel interface과 GatheringByteChannel interface --> 여러개의 버퍼를 읽어들이거나 쓰는 것을 지원한다. ㅁ public long read(ByteBuffer [] dsts) throws IOException ㅁ public long read(ByteBuffer [] dsts, int offset, int length) throws IOException ㅁ public long write(ByteBuffer [] srcs) throws IOException ㅁ public long write(ByteBuffer [] srcs, int offset, int length) throws IOException 분산 (scattering)"이라는 이름은 단일 채널의 데이터가 다중 버퍼에 퍼져 있다는 생각에서 비롯된 것이다. 이 방식은 일부 프로토콜 또는 파일 포맷의 경우 매우 간편하게 사용할 수 있는데, 예를 들면, 사용자는 고정된 길이의 이미지 헤더를 하나의 버퍼로 읽고 그것의 컨텐트는 다른 것으로 읽을 수가 있다. 채널은 다음을 진행하기 전에 연속적으로 위치한 각각의 버퍼를 채우려고 노력을 하게 되는데, 어느 버퍼가 시작되고 얼마나 많은 버퍼들을 채울 수 있는지를 나타내기 위해서는 두 번째 메소드에서 오프세트와 길이를 표시하는 것이 매우 중요하다. 그것들은 데이터를 위치 시킬 버퍼 내의 장소와는 전혀 관련이 없고, 버퍼들은 항상 그것들 자신의 내부 상태를 보존하고 있다. GatheringByteChannel 인터페이스는 분산의 카운터파트 역할을 한다. 여러분이 예상했던 것처럼 집합(gathering) 채널은 데이터 채널을 작성하는 연속된 버퍼들로부터 수집되고 있다. 또한 이 방법은 일부 프로토콜 또는 파일 포맷의 경우에는 매우 유용하게 사용되고 있다. 예를 들면, 여러분은 (네 개의 구분된 버퍼에 저장된) 상태 코드 라인, 일부 표준 서버 헤더, 사용자-할당 헤더 그리고 응답 본문 등으로 이루어진 HTTP 응답을 구성할 수 있다. 응답 본문은 대용량 메모리-매핑된 파일 버퍼 내에 위치할 수 있고, 서버 헤더는 커널 메모리에 저장될 수 있으며, 수집 채널은 다음을 진행하기 전에 각각의 버퍼 채널로 작성을 하고 채널은 통합되어 정렬된 출력을 보게 될 것이다. ====== <여기서 잠깐!!> ============ "scattering/gathering의 목적은 무엇일까? 간편함에도 불구하고 왜 프로그래머는 for 루프를 작성할 수가 없을까? 왜 for 루프를 피하는 인터페이스 계층은 복잡한 것일까?"라고 질문할 수 있다. 약 20초 동안 이것에 대해 한번 생각해 본 후 해답을 읽어보아라. 분산/집합의 목적은 프로그래머의 효율성이 아니다; 그것은 시스템의 효율성이다. 운영 체제는 사용자 공간에서 시스템 공간으로 점프하는 시스템의 반복 호출 보다는 오히려 단일 시스템 호출의 결과로 버퍼를 가득 채우거나 고갈시킬 수 있다. 또한 다중 CPU 시스템 등과 같은 일부 시스템들도 동시에 가득 채우거나 고갈시킬 수 있다. 예를 들면, SCSI는 시스템에 작성을 할 때 분산/집합을 지원하고 있습니다. 그 시스템의 기능에 따라 분산/집합은 for 루프에 대한 막대한 성능의 향상을 이룰 수가 있다. 이것을 이해한다면 여러분은 새로운 I/O의 개념도 이해할 수 있을 것이다. 대부분의 Java와는 다르게 이 관점은 여러분의 생활을 더 쉽게 만들지는 않는다 (가끔은 그러하지만) 그것은 시스템의 생활을 더 쉽게 만드는 것이다. New I/O는 높은 수준의 Java 객체들을 사용해서 낮은 수준의 시스템 기능을 그대로 드러낸다. ' 사실, 나는 이 scattering과 gathering이 자바에 있어 어떤 큰 영향이 있을까를 확실히 이해 할 순 없었다. 하지만, 정리해보건데, scattering과 gathering수행은 수년간 운영체계에서 효과적인 I/O매니져 역할을 해왔던 것이며, 자바에서 channel의 이런 분산,집합기능이 시스템영역까지 깊숙이 사용될수 있으니, 중요한 변화이다 는 것같다.(이 개념이 생각보다, 큰 개념으로 나와서 이해하려고 노력했기에..--;; 참고로 붙인것이다.좀더 명쾌한 해석 이 되시는 분은 알려주시라~~) (참고 : http://otn.oracle.co.kr/tech/column/2003/hunteronj2se.jsp) ▶InterruptibleChannel 인터페이스 블로킹된 스레드(입출력)를 깨우는 기능 ==> non-blocking 기능 채널 입출력 중 다른 스레드에 의해 채널이 중단될 수 있음을 나타내는 인터페이스이다 New I/O의 모든 채널 클래스들은 이 인터페이스를 구현하고 있으므로 모든 채널은 blocking 중에 다른 스레드에 의해 깨어날 수 있는 것이다. 이것은 중요한 특성으로 파일처리뿐만 아니라 나중에 네트워크에 관한 사항을 다룰때 나오는 Selector에 관련된 내용이기도 하다. 우선은 채널들은 어떻게 구현되던지 간에 항상 스레드-세이프이고 동시 발생의 읽기와 쓰기가 연속적으로 수행된다는 것을 항상 보장을 하고 있다고 알아두자. (뒤에 해당 예를 설명할 FileChannel, ServerSocketChannel, SocketChannel 세개는 꼭 기억해두시라~) ====== <참고하세요> InterruptibleChannel 인터페이스 ============ 요 인터페이스는 NIO의 중요한 비동기적 입출력에 중요역할이니, 어떻게 구현되는지는 알아야 할 것같아 좀 무식한 것같지만,API부분을 옮겨둔다. public interface InterruptibleChannel extends Channel A channel that can be asynchronously closed and interrupted. A channel that implements this interface is asynchronously closeable: If a thread is blocked in an I/O operation on an interruptible channel then another thread may invoke the channel's close method. This will cause the blocked thread to receive an AsynchronousCloseException. A channel that implements this interface is also interruptible: If a thread is blocked in an I/O operation on an interruptible channel then another thread may invoke the blocked thread's interrupt method. This will cause the channel to be closed, the blocked thread to receive a ClosedByInterruptException, and the blocked thread's interrupt status to be set. If a thread's interrupt status is already set and it invokes a blocking I/O operation upon a channel then the channel will be closed and the thread will immediately receive a ClosedByInterruptException; its interrupt status will remain set. A channel supports asynchronous closing and interruption if, and only if, it implements this interface. This can be tested at runtime, if necessary, via the instanceof operator. - 쓰레드의 제어방법을 이용한 것이란 것을 알 수있으리라 본다. ▶SelectableChannel 클래스 (interruptibleChannel구현) Selector에 의한 관리나 Non-Blocking I/O를 위한 기본적인 기능을 가진 Abstract class이다. SelectableChannel은 Selector클래스에 의해 선택될 수 있으며 Non-Blocking I/O를 가능하게 해준다. 가능한 이유는 AbstractInterruptibleChannel 클래스를 상속하기 때문이다. selectableChannel은 블록모드나 비블록모드가 되는데 , 새롭게 작성되면, 항상 blocking mode가 된다. Non -Blocking mode는 seletor의 다중화와 함께 가장 유용한데, Selector가 관리하는 SelectableChannel들은 전부 non-blocking I/O모드이어야 하며 blocking I/O모드의 채널은 Selector에 의해 관리되지 못한다. (selector에 관해서는 뒷파트에 자세히 나올것이다) 1.1.2. FileChannel FileChannels는 파일입출력을 위한 채널로 AbstractInterruptibleChannel 클래스를 상속해서 비동기적으로 중단될 수 있게 되어있다. 그리고 ByteChannel 인터페이스를 구현해서 읽기와 쓰기를 동시에 할 수 있다. 앞에서 계층도등 복잡한 것처럼 말했지만,Channel을 생성하는 것은 간단하다. I/O의 가장 기본이 되는 파일에 읽기,쓰기를 해보겠는데, 우선,기존의 I/O의 file 처리를 사용하여 (Stream이 들어가는 클래스가 생 각나지 않는가?) 얻어오면 된다.

public class CopyFile
{
  static public void main( String args[] ) throws Exception {
    if (args.length<2) {
      System.err.println( "Usage: java CopyFile infile outfile" );
      System.exit( 1 );
    }
    String infile = args[0];
    String outfile = args[1];
    FileInputStream fin = new FileInputStream( infile );
    FileOutputStream fout = new FileOutputStream( outfile );
    FileChannel fcin = fin.getChannel(); //channel얻기
    FileChannel fcout = fout.getChannel();
    ByteBuffer buffer = ByteBuffer.allocate( 1024 ); //버퍼형성
    while (true) {
      buffer.clear();
      int r = fcin.read( buffer ); //channel통해 buffer 읽기
      if (r==-1) {        break;      }
      buffer.flip();
      fcout.write( buffer ); //channel통해 buffer 쓰기
   }
  }
} 
앞에서 학습한 Buffer 에서 많이 보았으니,눈에 익으리라 본다. 위는 각 스트림 클래스에 채널로의 변환을 지원하는 메소드를 사용한 방법이고, java.nio.channels.Channels라는 유틸리티클래스에 있는 채널로의 변환을 지원하는 static 메소 드를 사용해도 된다. 또,이 java.nio.channels.Channels에는 채널에서 스트림으로 변환하는 메소드도 있다.(스트림은 단순한 데이터흐름이지만, 채널은 더 추상화된 단계로 질서가 부여된 데이터 흐름을 나타낸다고 할 수 있다.) ▶File Lock 프로그램이나 유저들에게서 특정파일에 접근을 방지하는 것처럼 생각되겠지만, file lock은 보통 자바객체의 lock의 의미와 같다.(advisory lock) 즉,어떤 데이터접근도 방지할 수 없지만, OS에서 지원하는 파일간 lock을 공식적으로 지원한다.이를 사용하면 불명확한 이유에 의해 파일 입출력에 제약받는 상황을 피할 수 있게 되면 효율적으로 파일에 락을 걸거나 해제할 수 있게 된다.

RandomAccessFile raf = new RandomAccessFile( "usefilelocks.txt", "rw" );
    FileChannel fc = raf.getChannel();
   // Get lock
     FileLock lock = fc.lock( 10, 20, false ); //부분적인 lock설정
Lock()호출시 이 파일을 locking하는 다른 쓰레드가 없는 한 락을 걸 수 있다. 만약 다른 스레드가 이 파일을 락킹하고 있다면 락이 풀릴때까지 기다리게 된다. 락을 공유한다면 하나의 쓰레드만 독점적으로 사용하는 것이 아니라 미리 약속된 쓰레드에 한해 그 파일에 접근 가능하다. (OS의 lock정책에 좌우된다) ▶Memory-mapped file I/O Memory-mapped fiel I/O는 기존의 스트림이나 다른 채널 I/O보다 더 빠를 수 있다. (MappedByBuffer에서 다루어졌을 것이다) 1.1.3 ServerSocketChannel 클래스와 SocketChannel 클래스 이들은 net패키지의 ServerSocket클래스와 Socket클래스를 채널로서 다루고자 할 때 쓰는 SelectableChannel이다. 앞에서 selectableChannel이 non-blocking기능과 함께 나왔던 것을 기억하시는가? 이들 네트워크 관련 채널들은 독자적으로 소켓의 역할을 대처하지는 않는다. 대신 소켓 클래스를 내부에 가지고 있으면서 이들의 기능을 채널화하는데 적절히 이용하게 된다. ▶New working and asynchronous I/O 네트워킹은 자바에서 I/O과정의 필수지식인 비동기식 입출력을 배우는 위한 좋은 분야이다. NIO에서 networking도 앞에서 설명한 NIO의 다른 동작과 크게 다르지 않다. 비동기식 입출력은 한마디로,blocking기능 없이 데이터를 읽거나 쓰는 방법을 말한다. 보통 read()를 호출하면,그 해당 코드블럭은 데이터가 다 읽혀질때까지, 블록된다. 반면에,비동기적 입출력이 호출되면 블록되지 않는다. 그대신, 데이터가 완전히 읽혀진다던가 새로운 소켓연결 같은 특정 I/O이벤트를 등록해 두었다가 그러한 이벤트가 일어나면 그것을 알려준다. 비동기적 입출력의 장점중 하나는 매우 많은 입출력이 동시에 많이 일어 나더라도,또 다른 I/O작업을 할수 있다는 것이다. 동기적 프로그램은 자주 계속 polling하거나 많은 연결을 해결하기 위해 매우 많은 쓰레드를 만들어야 한다. 비동기적 입출력에서는 이 같은 polling이나 여분의 쓰레드생성없이 임의의 적절한 숫자의 channel에서 I/O이벤트를 감시할수 있다. 많은 예로 활용되는 1.4이전의 간단한 서버소켓 프로그램과 비교하여 알아보자. ServerSocket s = new ServerSocket(); //서버소켓생성 Socket conn = s.accept( ); // 새로운 연결 소켓 accept() 호출은 서버 소켓이 클라이언트의 연결 요청을 받아들일 때까지 블록화된다. 이것은 호출하는 쓰레드가 결정되지 않는 시간동안 멈추어 있는데, 이 어플리케이션이 단지 하나의 쓰레드라면 시스템에 큰 타격이다. 일단 연결이 이루어지면 서버가 소켓으로부터 LineNumberReader를 사용해 클라이언트 요청을 읽는다. LineNumberReader는 버퍼가 가득찰 때까지 데이터를 큰 단위로 읽어 나가기 때문에 호출은 읽기 상태에서 블록화된다. 다음 예제 코드는 실행중인 LineNumberReader를 보여준다. (블록과 모든 것)

InputStream in = conn.getInputStream();
InputStreamReader rdr = new InputStreamReader(in);
LineNumberReader lnr = new LineNumberReader(rdr);
Request req = new Request();
while (!req.isComplete() )
{
   String s = lnr.readLine();//SocketInputStream.rea()을 호출
   req.addLine(s);
}
이 코드는 문제를 일으킨다. 데이터는 네트워크 buffer에서 기다리고 호출한곳에 데이터가 리턴되는데,충분한 데이터가 버퍼되지 않으면 읽기 위한 호출은 충분한 데이터가 받아지거나 다른 쪽컴퓨터가 소켓을 닫지 않을 때 까지 블록화된다.(버퍼가 찰때까지 기다리겠지) 또 다른 문제는 이 코드는 너무 많은 가비지와 LineNumberReadr는 소켓으로 읽은 데이터를 위한 버퍼와 같은 데이터를 위해 String을 만든 다는 것이다..버퍼는 다시 사용되긴 하지만, 스트링은 다시 가비지가 빨리 되니까 큰 문제를 일으킬수 있다. Write메소드도 마찬가지다. 길어졌는데 blocking과 garbage의 문제가 있다는 것이다. 여기에 thread 이슈까지 생각해보면,JDK1.4의 네트워크과 I/O부분에서 가장 크게 주목해야 할 것이 non-blocking의 공식적 지원이란 생각이 든다. (너무 잘 알려진 부분이겠지만, 비교하기 위해 일정 면을 장식했다..) ▶Nonblocking channel 생성하기 기본적인 nonblocking방식의 소켓읽기 쓰기 작업을 구현하기 위해 실제 읽기 쓰기 작업을 수행하는 SocketChannel클래스를 다루어야 한다. 다음 코드는 기초적인 서버소켓프로그램을 개발하기 위해 변경된 nonblocking방식을 보여준다. 앞에서 사용된 코드와의 차이를 보기 바란다.

class AcceptThread extends Thread {
  private ServerSocketChannel ssc;
public AcceptThread(Selector connectSelector, ConnectionList list, int port) throws Exception {
    super("Acceptor");
…
   ssc = ServerSocketChannel.open();
//모든 특별한 Channel의 경우와 같이  직접 ServerSocketChannel 객체를 직접 생성할 수 없고 .
//ServerSocketChannel.open() 의 factory 메소드를 사용해 연결되지 않은 SocketChannel생성
// 이전까진 java.net.Socket을 리턴받아 사용했지만,여기서는 ServerSocket을 리턴한다.
      ssc.configureBlocking(false);

    InetSocketAddress address = new InetSocketAddress(port);
    ssc.socket().bind(address);
}
nonblocking모드에서의 채널사용에 초점을 맞추고 있지만, 채널은 blocking과 nonblocking 양쪽에서 사용될 수 있다. Nonblocking모드에서 스레드는 데이터 양이 얼마나 되든 읽어나갈 것이고 결국 다른 작업을 수행하기 위해서 복귀할 것이다. configureBlocking()가 참으로 전달되면, 채널은 Socket에 대해 blocking 모드에서의 읽기 쓰기 작업과 정확한 동일한 작업을 수행하게 될 것이다. Channel만으로는 nonblocking입출력을 구현을 만드는데 충분하지 않다. Channel클래스는 nonblocking입출력을 위해 selector 클래스와 협력하여 작업해야한다. 이는 다음 part에서 알아보기로 하자. ====== <참고하세요> ============ accept()를 통해 연결된 socketChannel을 얻을 수도 있는데,blocking mode에서 , ServerSocketChannel에서의 accept()메소드는 요청연결이 도착할 때 까지 리턴되지 않고 TimeoutException이 난다. Non-blocking모드에서는 accept()는 항상 즉히 socketchannel이나 null을 리턴한다. http://www.javaworld.com/javaworld/jw-09-2001/jw-0907-merlin_p.html 좀 더 잘 정리된 내용과 nonblocking서버소스를 보실 수 있습니다.
 
1
References
 
Copyright ⓒ 2003 www.javapattern.info & www.jlook.com, an jLOOK co.,LTD