본문 바로가기

공부/컴퓨터

[NIO-02] NIO를 이용한 간단한 예제

반응형

==========================================================================
이 글은 이론적으로 아는 것을 직접 설명 및 구현을 해 봄으로써 제 자신의
실력을 다지기 위한 글 입니다. 물론 정확한 이론. 용어도 아님을 밝힙니다.
이 글을 직.간접적으로 사용함으로써 발생되는 모든 불이익을 책임지지 않습니다.

문의점, 오류, 잘못된 용어들은 저의 홈페이지 Work 게시판을 이용하여 주시고
이상의 사항에 대하여는 최대한 덧글 ( 코멘트 ) 를 이용해 주십시오.

본 글은 저의 홈페이지인 http://ggaman.com 과
싸이월드의 (JPSC) JAVA program study club 에서 보실수 있습니다.

homepage : http://ggaman.com e-mail n MSN : chan at ggaman.com

20031206 - Chan
==========================================================================
안녕하세요.
찬 입니다.

역시나 NIO에 대하여 이야기를 해 보겠습니다.

오늘 알아 볼것은 NIO가 동작하는 방법입니다.

이제까지 사용하던 IO의 사용법은 다음과 같습니다.

1. Stream을 연다.
2. Stream에 쓴다.

오~ 놀랍도록 간단합니다. ;;;;


그럼 NIO가 동작하는 방법을 보겠습니다.
1. Stream을 연다
2. Stream에서 Channel을 얻는다.
3. Buffer를 만들어 Channel에 쓴다.

NIO는 스트림을 열고. 그곳에서 다시 채널을 얻어 냅니다.
그런뒤에 그냥 일반적인 DATA를 쓰는것이 아니라.
Buffer를 통째로 넘겨서 Channel에 쓰게 합니다.


그럼 NIO에 대한 소스를 하나 보면서 설명 드리겠습니다.
소스는 매닝사에서 나오고 인포북에서 번역 출간한
" JDK 1.4 튜토리얼 " 을 참고 했습니다.
=================================================================================


01: // NioTest1.java
02: /*
03: * 작성된 날짜: 2003. 12. 5.
04: *
05: * 프로그램명 :
06: * 버젼 :
07: * 파일명 : NioTest1.java
08: *
09: * 프로그램 설명 :
10: *
11: */
12:
13: import java.io.*;
14: import java.nio.*;
15: import java.nio.channels.*;
16:
17: /**
18: * @author ggaman.com
19: * http://ggaman.com
20: *
21: * 클래스 명 : NioTest1.java
22: * 버젼 :
23: *
24: * 하는 일 :
25: *
26: */
27: public class NioTest1 {
28:
29: // 자 프로그램을 시작해 보죠.
30: public static void main(String[] args) throws Exception {
31:
32: // 우선 화일 객체를 두개 만들겠습니다. 각각 입력과 출력을 맡을껍니다.
33: File fi = new File("input.txt") ;
34: File fo = new File("output.txt") ;
35:
36: // 이제 화일의 입출력을 하기 위해서 스트림을 만들어서 열어야 합니다.
37: FileInputStream fis = new FileInputStream(fi);
38: FileOutputStream fos = new FileOutputStream(fo);
39:
40: // #### 1
41: // 여기서 재미난 것이 있군요.
42: // 화일의 입출력을 위해 연 fis 과 fos 에서 채널을 얻어 냅니다.
43: FileChannel fic = fis.getChannel();
44: FileChannel foc = fos.getChannel();
45:
46: // #### 2
47: // 이것은 NIO를 쓰기 위해서 Buffer을 하나 만들죠.
48: // 재미난것은 Buffer은.. 생성자를 쓰지 않는군요.
49: // 나중에 한번 다시 확인해 보도록 하죠.
50: ByteBuffer buf = ByteBuffer.allocate( 1024 );
51:
52:
53: // 무한 루프를 돕니다.
54: while(true) {
55: // #### 3
56: // 여기서는 fic 즉 입력 채널에서 데이터를 읽어 들입니다.
57: // 역시 재미난것은 ByteBuffer만 써 주었다는 것이지요.
58: int ret = fic.read(buf);
59:
60: // 여기서 ret가 -1이 반환되면 화일의 끝이 됩니다.
61: // 즉 -1이 아니면 화일을 복사 해야 겠죠?
62: if ( ret != -1 ) {
63: // #### 4
64: // 버퍼를 플립하고 ( 나중에 설명 들어 갑니다. )
65: // 출력채널에다가 Buffer를 써 줍니다.
66: // 그리고 버퍼를 클리어 시키죠.
67: buf.flip() ;
68: foc.write(buf);
69: buf.clear() ;
70: }
71: else break; // -1(화일끝)이 나오면 루프를 멈춥니다.
72: }
73:
74: }
75:
76: }


======================================================================


자 그림 위에서 체크 ( #1, #2 ... ) 되어 있는 몇 부분을 확인해 보겠습니다.

####### 1
// #### 1
// 여기서 재미난 것이 있군요.
// 화일의 입출력을 위해 연 fis 과 fos 에서 채널을 얻어 냅니다.
FileChannel fic = fis.getChannel();
FileChannel foc = fos.getChannel();

바로 스트림에서 채널을 얻어 내는 부분입니다.

사용법은 다음과 같네요

FileChannel fic = fis.getChannel();

여기 같은 경우는 FileInputStream이니깐 당연히 FileChannel 로 받아야 합니다.

채널은 다음과 같은 종류가 있습니다.
DatagramChannel, FileChannel, Pipe.SinkChannel, Pipe.SourceChannel, SelectableChannel,

SocketChannel 등
Java 1.4.2 API 문서에서 참고한것입니다.

각각 맞는 채널을 사용하면 될 것 같습니다.



####### 2
// #### 2
// 이것은 NIO를 쓰기 위해서 Buffer을 하나 만들죠.
// 재미난것은 Buffer은.. 생성자를 쓰지 않는군요.
// 나중에 한번 다시 확인해 보도록 하죠.
ByteBuffer buf = ByteBuffer.allocate( 1024 );

바로 Buffer 부분입니다. NIO에서는 입출력을 위해서 Buffer를 Channel에 쓰게 됩니다.

제가 생각하기로 이 버퍼는 데이터를 저장하기 위한 공간입니다.
일반적인 io에서는 데이터를 넘기면(읽거나 쓰거나) Block 되면서 처리하게 됩니다.
하지만 NIO에서는 Block 되지 않으므로 호출한 즉시 처리가 되지 않는다면
데이터를 어디엔가 저장했다가 써야 하기 때문에 Buffer이 필요한것으로 생각됩니다.

재미난것은 보통과 다르게 Buffer은 생성자를 쓰지 않습니다.
위의 소스를 보면 다음과 같이 사용했습니다.

ByteBuffer buf = ByteBuffer.allocate( 1024 );

바로 allocate를 사용하여 buf라는 이름으로 메모리를 할당하게 됩니다.
( 마치 C에서 malloc(sizeof(byte) * 1024 ) 하는것과 같은것 같군요 ^^ )

그럼 ByteBuffer의 원래 소스의 일부를 한번 확인해 보겠습니다.
====================================================================================
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset)
{
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
====================================================================================
보다시피 생성자가 package-private 로 설정이 되어 있군요.
그래서 new를 이용해서
ByteBuffer buf = new ByteBuffer(1024);
이런식으로 생성할수가 없게 되어 있습니다.
( 이렇게 생성자를 private로 만든것은 Singleton 패턴에도 존재하죠 )
( 궁금하신 분은 Performance 쪽을 확인해 주십시오. ^^ )



####### 3
// #### 3
// 여기서는 fic 즉 입력 채널에서 데이터를 읽어 들입니다.
// 역시 재미난것은 ByteBuffer만 써 주었다는 것이지요.
int ret = fic.read(buf);

이 부분은 채널에서 데이터를 읽어 오는 부분입니다.

보통의 IO에서는 다음과 같은 메소드로 데이터를 읽어 들입니다.
1. read()
2. read(byte[] b)
3. read(byte[] b, int off, int len)

하지만 NIO는 다음과 같은 메소드를 사용합니다.
1. abstract int read(ByteBuffer dst)
2. long read(ByteBuffer[] dsts)
3. abstract long read(ByteBuffer[] dsts, int offset, int length)
4. abstract int read(ByteBuffer dst, long position)

역시나 몇개가 되죠.
하지만 여기서 중요한것은 바로 모든것이 ByteBuffer을 사용한다는 것입니다.
( 물론 다른 형태(Char, Float등)에 대하여는 거기에 맞는것을 사용해야 할것입니다. )


채널에 대하여 하나 더 알아두고 가야 할것이 있습니다.
FileChannel 클래스를 확인해 보면...
read() 메소드 뿐만 아니라, write 메소드가 있습니다.
즉, 채널로 입출력을 동시에 할 수 있게 됩니다.



####### 4
// #### 4
// 버퍼를 플립하고 ( 나중에 설명 들어 갑니다. )
// 출력채널에다가 Buffer를 써 줍니다.
// 그리고 버퍼를 클리어 시키죠.
buf.flip() ;
foc.write(buf);
buf.clear() ;


이부분은 데이터를 복사하는 부분입니다.
화일의 끝 ( API에 따르면 End-of-Stream ) 이면 -1을 반환하게 됩니다.


이 다음에 나오는 부분이 가장 설명하기가 까다로운 부분입니다.
buf.flip() 과 buf.clear() 부분인데.


buf는 데이터를 저장하고 있습니다.
버퍼의 끝 ( 데이터 저장할 마지막 자리, 배열처럼 설명하면 buf[1023] )이 있고
그리고 데이터가 들어 있는 장소의 끝이 있을것입니다.



배열은 아니지만 배열 처럼 설명을 드리겠습니다.
buf는 10개의 방이 있습니다.
그리고 0,1,2,3,4,5 에는 데이터가 들어 있습니다.

그렇다면 buf의 끝은 9번 방이 될것이고
buf 데이터의 끝은 5가 될것입니다.



buf.flip() 는 buf의 데이터끝을 조정합니다.
처음 buf가 만들어지면 실제 데이터의 끝이 어딘지 모르니깐
맨 마지막을 가리키고 있을것입니다.
그리고 실제 데이터가 들어 오면, 입출력 할곳을 정한 포인터가
한칸씩 이동하면서 데이터를 읽거나 쓰게 될겁니다.

하지만 읽은 데이터를 사용하려고 한다면,
입출력을 정하는 포인터는 제일 앞으로 이동해야 할것이고,
데이터의 마지막에 어디인지를 가르키는 포인터가 데이터의 끝 지점에 위치 해야 할것입니다.


그렇기 때문에 buf.flip()를 사용하여 buf에서 데이터의 끝을 가르키는 포인터를
( 현재는 제일 버퍼의 마지막에 있을것입니다. ) buf 실제 데이터의 마지막으로 옮깁니다.
그리고 파일을 입출력할 장소를 나타내는 포인터를 제일 앞으로 옮깁니다.

그런뒤에 foc 채널에다가 데이터를 씁니다.

그뒤에 buf.clear()을 사용하여 버퍼를 완전히 비워 버립니다.
( buf의 초기화 )
물론 데이터의 입출력 포인터 역시 제일 앞칸으로 이동하고,
buf의 실제 데이터의 끝을 나타내는 포인터 역시 제일 마지막칸으로 이동합니다.



예제가 간단하기는 하지만 어떤 방식으로 NIO를 사용하는지 이해가 될것이라 믿습니다.

마지막으로 중요한것만 정리 하면.

1. Stream에서 Channel을 얻는다.
2. Buffer을 만들어서 Channel로 입출력을 한다.
3. ByteBuffer.flip() 또는 ByteBuffer.clear() 가 동작하는것을 알아야한다.


엇 -_- 겨우 세줄인가? -_-?
이렇게 많이 적었는데 -_-;;;;;


아무튼 도움이 되는 자료였으면 합니다.

그럼이만.
즐거운 하루 되세요~ ^^
반응형

'공부 > 컴퓨터' 카테고리의 다른 글

[NIO-03] NIO Selector - 1  (0) 2003.12.11
CVSNT 설치 하기  (0) 2003.12.10
[NIO-01] NIO 란 무엇인가?  (0) 2003.12.02
서로 다른 Thread 끼리의 참조가 가능합니까?  (1) 2003.10.07
채팅 서버 UML 다이어그램  (0) 2003.10.01