본문 바로가기
JAVA

[NIO] 1부, 버퍼와 채널 #2

by windrises 2007. 3. 29.
Channel

이제 버퍼가 실제로 사용되는 채널(channel)에 대해서 살펴볼 차례이다. 간단하게 말해서 채널은 데이터가 통과하는 쌍방향 통로라고 생각하면 되는데, 채널에서 데이터를 주고 받을 때 사용되는 것이 바로 버퍼이다. 채널에는 소켓과 연결된 SocketChannel, 파일과 연결된 FileChannel, 파이프와 연결된 Pipe.SinkChannel과 Pipe.SourceChannel 등이 존재하며, 서버소켓과 연결된 ServerSocketChannel도 존재한다.

채널은 기존에 존재하는 Socket, ServerSocket, FileInputStream, FileOutputStream 등은 그와 관련된 채널을 리턴해주는 getChannel() 메소드를 제공하고 있다. 모든 채널 클래스들은 public 생성자를 제공하고 있지 않으며, 따라서 채널을 생성하기 위해서는 기존의 스트림, 소켓, 서버 소켓 클래스의 getChannel() 메소드를 사용해야한다. SocketChannel이나 ServerSocketChannel의 경우에는 static 메소드인 open() 메소드를 제공하고 있는데, 이 메소드를 사용하여 해당 채널을 구할 수도 있다.

예를 들어, 간단한 파일 복사 프로그램을 FileChannel을 사용해서 작성해보자. 파일 복사 프로그램은 두 개의 FileChannel을 사용하여 작성하면 된다. 한 파일 채널은 파일로부터 데이터를 읽어올 때 사용될 것이며, 또 다른 파일 채널은 파일에 데이터를 출력할 사용된다.

FileChannel을 생성하기 위해서는 해당하는 스트림의 getChannel() 메소드를 사용해야 한다. 즉, 다음과 같이 FileInputStream.getChannel() 메소드와 FileOutputStream.getChannel() 메소드를 사용해서 FileChannel을 구하면 된다.

    
    FileChannel inputChannel = null;
    FileChannel outputChannel = null;
    try {
        FileIntputStream is = new FileInputStream(source);
        FileOutputStream out = new FileOutputStream(dest);
        inputChannel = is.getChannel();
        outputChannel = out.getChannel();
        
        ...
        
    } catch(IOException ex) {
        //
    } finally {
        //
    }

일단 채널을 생성하면 Buffer를 사용하여 데이터를 입출력하면 된다. 다음은 버퍼를 생성한 후 inputChannel로부터 데이터를 읽어와 outputChannel로 데이터를 출력해주는 코드이다.

    ByteBuffer buffer = ByteBuffer.allocateDirect(512);
    int len = -1;
    while ( (len = inputChannel.read(buffer)) != -1) {
        if (len == 512) {
            buffer.position(0);
        } else {
            buffer.flip();
        }
        outputChannel.write(buffer);
        if (len == 512) {
            buffer.rewind();
        } else {
            buffer.clear();
        }
    }

채널의 사용이 끝나면 채널의 close() 메소드를 사용하여 채널을 닫아주어야 한다.

채널의 종류

현재 나와 있는 자바2 1.4 베타3 버전에는 다음과 같은 종류의 채널이 존재한다.

  • FileChannel - 파일에 대한 입출력 채널
  • Pipe.SinkChannel - 파이프에 데이터를 출력하는 채널
  • Pipe.SourceChannel - 파이프로부터 데이터를 입력받는 채널
  • ServerSocketChannel - 클라이언트의 연결 요청을 처리하는 서버 소켓 채널
  • SocketChannel - 소켓과 연결된 채널
  • DatagramChannel - DatagraSocket과 연결된 채널

이들 채널은 입출력이 동시에 이루어지는 것도 있고 또한 그렇지 않은 것도 존재하는데, 채널의 입출력 가능 여부는 그 채널이 어떤 인터페이스를 구현했느냐에 따라서 달라진다. 예를 들어, 입력이 가능한 채널은 ReadableByteChannel 인터페이스를 구현하도록 되어 있으며, 출력이 가능한 채널은 WritableByteChannel 인터페이스를 구현하도록 되어 있다. 따라서, 동시에 입출력이 가능한 SocketChannel의 경우에는 이 두 인터페이스를 모두 구현하고 있으며, 입력만 가능한 Pipe.SourceChannel의 경우에는 ReadableByteChannel 인터페이스만 구현하고 있다.

FileChannel과 MappedByteBuffer

FileChannel과 관련해서 알아두어야 할 것이 있는데, 그것은 바로 파일의 내용을 메모리에 매핑시켜서 버퍼로 사용할 수 있는 기능이다. FileChannel.map() 메소드는 파일의 특정 영역의 데이터가 매핑된 메모리 영역을 버퍼 영역으로 사용하는 MappedByteBuffer를 리턴해준다. MappedByteBuffer는 ByteBuffer를 상속받고 있으며, 물리적 메모리에 직접적으로 접근하는 다이렉트 버퍼의 일종으로서 ByteBuffer와 동일하게 동작한다. 그림4는 MappedByteBuffer의 기본 개념을 보여주고 있다.


그림4 - MappedByteBuffer

그림4에서 볼 수 있듯이 MappedByteBuffer는 파일의 특정 영역의 데이터를 메모리상에 표현하고 있는데, 여기서 중요한 점은 파일의 해당 영역의 데이터가 변경되며 메모리에 있는 데이터도 함께 변경된다는 점이다. (실제로는 하부의 운영체제에 따라서 변경여부가 결정된다.) 또한, MappedByteBuffer.force() 메소드를 사용하여 메모리에서 변경된 내용을 파일의 매핑된 영역에 반영할 수도 있다.

MappedByteBuffer는 FileChannel로부터 만들어지고, FileChannel은 FileInputStream, FileOutputStream 또는 RandomAccessFile로부터 생성된다. 여기서 FileInputStream, FileOutputStream 그리고 RandomAccessFile은 각각 입력, 출력 가능 여부가 다르다. 예를 들어, FileInputStream의 경우는 입력만 가능한 반면에 RandomAcceeFile은 입출력이 동시에 가능하도록 만들어질 수 있다.

이처럼 파일 입출력 스트림은 읽기/쓰기 여부가 스트림의 종류에 따라서 다르기 때문에 MappedByteBuffer도 읽기 전용과 읽기 쓰기 겸용으로 구분된다. 또한, 파일로부터 데이터만 읽어오고 파일 데이터의 변경 여부는 반영하지 않는 MappedByteBuffer도 존재한다. 이처럼 MappedByteBuffer에는 3가지 종류가 존재하기 때문에 FileChannel.map() 메소드는 다음과 같이 어떤 버퍼를 생성할 지의 여부를 파라미터로 전달받도록 되어 있다.

    public MappedByteBuffer map(FileChannel.MapMode mode, long position, int size)

위 메소드 정의에서 FileChannel.MapMode가 어떤 MappedByteBuffer를 생성할지의 여부를 지정하기 위해 사용된다. FileChannel.MapMode 클래스는 다음과 같이 3 개의 상수를 사용하여 생성할 MappedByteBuffer의 종류를 지정할 수 있도록 하고 있다.

  • FileChannel.MapMode.PRIVATE - 파일에 영향을 미치지 않고 받지 않는 버퍼를 생성한다.
  • FileChannel.MapMode.READ_ONLY - 읽기 전용 버퍼를 생성한다.
  • FileChannel.MapMode.READ_WRITE - 읽기/쓰기 버퍼를 생성한다.

FileChannel.map() 메소드의 두번째 세번째 파라미터는 각각 MappedByteBuffer에 저장할 파일의 시작 위치와 시작 위치로부터 읽어올 데이터 길이를 나타낸다.

1부에서는 NIO API를 사용하는 데 있어서 가장 기본이 되는 버퍼와 채널에 대해서 살펴보았다. 버퍼를 사용함으로써 데이터를 입출력할 때 성능향상을 일으킬 수 있다. 또한, NIO API가 제공하는 버퍼는 물리적 메모리에 버퍼를 생성하고 원시 메소드를 통해서 접근하는 다이렉트 버퍼를 제공함으로써 자바 배열로는 제공할 수 없는 뛰어난 성능을 제공하고 있다.

본장에서는 단순히 채널이 데이터가 통과하는 연결통로라는 것에 대해서만 언급했는데, 실제로 NIO API의 주요 기능은 채널과 실렉터를 이용한 논블럭킹 입출력이다. 다음 2부에서는 Charset을 이용한 캐릭터셋의 변환과 Selector를 이용한 논블럭킹 입출력에 대해서 살펴볼 것이다.

-출처 [http://javacan.madvirus.net/]

'JAVA' 카테고리의 다른 글

icon package  (0) 2007.03.29
NIO SAMPLE SOURCE  (0) 2007.03.29
[NIO] 1부, 버퍼와 채널 #1  (0) 2007.03.29
[NIO] 2부, Charset을 이용한 인코딩/디코딩처리  (0) 2007.03.29
[NIO] 3부, 블럭킹 IO와 논블럭킹 IO #2  (0) 2007.03.29