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

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

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

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

20040103 - Chan
==========================================================================

안녕하세요.
찬 입니다.

오랜만에 이렇게 글을 쓰네요.

요즘에 Win32SDK API 한다고 -_-;;
크크크. 외도 중입니다. -_-;
( 오늘 메모장 거의 70% 만들었네요. 축하해 주세요 -_-; )



오늘은 Assertion 에 대하여 이야기 해 보겠습니다.
( 우리말로 읽으면 어썰션이라고 읽더군요. ^^ )


assert 는 문제가 생기면 바로 에러를 리턴해 주는것입니다.
AssertionError 이라는 클래스가 있는데. 이름에서 알수 있다시피 Error 클래스를 상속 받습니다.
( 그래서 예외도 아니고 에러가 생기는것이죠 )



assert의 사용은 아주 단순합니다.

assert (boolena-value);

저기서 boolean-value 가 false 이면 에러를 내게 되어 있습니다.



즉 반드시 처리 후에 null이 되어야 하는데
null이 되지 않을경우를 잡아낼려면 다음과 같이 할수 있습니다.


if ( someBoolen ) someObject = null;
assert someObject == null;


저기서 만약 someBoolean이 false 상태 였다면.
someObject는 당연히 어떤 객체를 가르키고 있을수 있습니다.
그렇다면 (someObject == null) 의 결과는 false 가 되는 경우도 있고,
이때에 assert 문에 의해서 에러를 내게 됩니다.



그렇다면 예외처리를 하지 말고 무언가가 잘못 되었을때
그냥 assert를 사용하면 되지 않겠느냐? 라고 생각하신다면,
알려드리는게 인지상정. ( 으윽 -_- 포켓 몬스터 -_-;; )


예외처리는 보통 내가 만든 클래스들이 잘못 사용되어지고 있을때 나타낼때 사용하게 되어 있습니다.
( 잘 생각해보시면 예외처리하는 부분들은 대부분 그렇게 쓰여지고 있습니다. )
assert는 꼭 원하는 결과가 나와야하는데 그렇지 않은 경우 ( 그래서 프로그램이 잘못 되는 경우 )에
사용하시는게 맞을꺼라고 이 연사 감히 외쳐 봅니다. -_-;;;



Assertion 은 JDK 1.4 부터 지원을 하는 것 입니다.
그래서 그 이전버젼에서 사용을 하면 당연히(!) 동작하지 않습니다.

하지만 Assertion 의 사용은 자바에서 새로 생긴것이 아니고.
이전에 있었던 언어들로 부터 계속 내려온것이라서.
많은 분들이 assert 라는 변수를 이용해서 작업을 하였을것이라 생각합니다.

다음과 같은 예제가 있습니다.


class someClass {
public static void main(String[] args)
{
int assert;
assert true;
}
}


이렇게 하신다면 컴파일 시에 JDK 1.4 이상에서는 int assert 부분에서 에러가 날것입니다.
( assert가 예약어가 되었기 때문에 변수로 사용할수 없겠죠. )
하지만 1.4 미만에서는 당연히 assert ture; 부분에서 에러가 날겁니다.


assert를 사용하는 것은 다음과 같이 컴파일 해 주어야 한다고 합니다.

javac -source 1.4 SomeCode.java


그리고 assert같은 경우는 실행 할 경우 이것을 끄고 켜는 옵션이 있습니다.
다음과 같은 예제가 있습니다.


public class SomeClass {
public static void main (String[] args) {
assert someMethod();
System.out.println("Program End");
}

public boolean someMethod() {
System.out.println("Run someMethod()");
return true;
}
}




이 예제를 컴파일 하고 ( 위와 같이 -source 1.4 를 넣어야합니다. ) 실행 시키면 다음과 같이 실행 됩니다.
java SomeClass

Program End


이상하게도 someMethod() 문이 실행되지 않는군요.
우리가 원하는 ( 또는 예상하는 ) 실행은
someMethod()가 실행 되고 화면에 Run someMethod()를 출력하고, true를 리턴 시켜서
결국 assert true; 가 되고
다시 화면에 Program End 가 출력되어야 할것입니다.


assert는 일반적으로는 실행 되지 않게 되어 있습니다.
그래서 실행할때는 다음과 같이 실행 해야 됩니다.

java -ea SomeClass

Run someMethod()
Program End


개발중일때에는 -ea 옵션을 붙여서 실행 시켜 잘못된것을 전부 찾아 보고
운영할때에는 -ea 옵션 없이 실행 시키면 됩니다.


여기서 다시 한가지 생각해 볼것이 있습니다.
그렇다면 assert가 컴파일된 class에 들어 있으니, 용량도 많고, 속도도 느릴것 아니냐? 라는 궁금증을 가지게 됩니다.
맞습니다. -ea 옵션 없이 실행 된다고해도 코드를 가지고 있으니, 당연히 그렇습니다.


이부분은 다음과 같이 해결 할수 있습니다. 요즘에 나오는 컴파일러들은 참으로 똑똑합니다.
( 아니 책들을 보니깐 그렇다고 합니다. -_-;; )

예를 들자면 다음의 예를 보겠습니다.



int i = 0;
int j = 1;

for ( i = 0 ; i< 100; i++ ) {
j=2;
}


이런 소스가 있다면 컴파일러는 다음과 같이 처리를 해 줍니다.


int i = 0;
int j = 1;
j = 2;

for ( i = 0; i < 100 ; i++ ) { }



j가 루프에서 2로 고정적이기 때문에 j=2 항목을 위로 빼버립니다.
저렇게 하면 j에 2가 100번 할당 되는게 한번만 할당되고 말죠.


이렇듯이 컴파일러는 똑똑합니다.

그래서 다음과 같이 처리하면 assert를 아무리 많이 넣어도 assert는 컴파일된 코드에 포함 되지 않게 됩니다.
( 물론 포함될수도 있지만, 전혀 성능에 영향을 미치지 못하게 될것 같습니다. ;;;;; ( 자신 없음 ) )


final boolean enableAssert = false;


if ( enableAssert ) {
assert someCompare();
}





위와 같은 코드로 작성을 한다면 enableAssert는 항상 false를 가지고 있으며
if 문의 조건 역시 항상 false 가 되므로 저 if 문 사이는 절대로 실행 될수 없다.
그러므로 컴파일러가 저 부분은 그냥 무시하게 되는 것이다.


하지만 꼭 그렇게 속도나 메모리가 문제 되지 않는다면 위와 같은 코드는 권장하지 않는다.

assert 는 실행 옵션으로 끄고 켜고를 할수 있기 때문에 굳이 저렇게 막을 필요는 없을것이다.




무조건 assert가 동작하도록 하는 방법도 있다. ( 물론 어렵지는 않다. 내가 이글을 설명할 정도라면 충분히 ;; )


static {
boolean enableAssert = false;
assert enableAssert = true;

if ( !enableAssert) {
throw new RuntimeException("Asserts must be enabled!!!");
}
}


public static void main(String[] str) {
System.out.println("Program run");
}



위와 같은 것을 컴파일 했다고 하고 -ea 옵션으로 실행 시켜 보면 다음과 같이 실행 될것이다.
-ea 옵션을 실행 시켰으니 assert 문은 실행 될것이다.
그렇다면 enableAssert 는 true가 될것이고 if 문 안의 내용은 실행 되지 않을것이다.

하지만 -ea 옵션 없이 실행 시킨다고 해 보자.
그렇다면 assert 문은 무시 될것이다. ( 이 줄은 실행되지 않을것이다. )
그러므로 enableAssert의 값은 false 가 될것이고.
if 문 안의 내용이 실행 되게 되면서 런타임 예외를 실행 시켜줄수 있을것이다.






아아. -_- 쓰다 보니 존대와 반말이 오고 갔네요 ;; 정말 죄송.

오랜만에 자바 관련 글을 정리하다 보니.
혹시 소스가 문제가 있을수 있습니다. 주로 보고 쳤지만 오타때문에 ..
( 예제는 직접 실행 시켜보지 못했음을 알립니다. -_-; )



그럼이만.
즐거운 하루 되세요.
드래그 드랍을 지원하기 위해서는 다음과 같은 속성을 윈도우에 주어야 한다.


hWnd = CreateWindowEx(WS_EX_ACCEPTFILES,.... )

저렇게해야만 드래그앤 드랍이 가능해 진다.

그리고 다음과 같은 메세지를 받을수 있다.


switch(iMessage) {
case WM_DROPFILES:
        DragQueryFile((HDROP)wParam, 0, d_filename,1024);
        loadFile(DROP_OK);
        DragFinish((HDROP) wParam);
        return 0;
.....

d_filename 으로 화일명을 받아 들일수 있다.
여기서 1024는 화일명이 받을수 있는 크기이다.

다음은msdn의 DragQueryFile 함수이다.

DragQueryFile Function  

--------------------------------------------------------------------------------

Retrieves the names of dropped files that result from a successful drag-and-drop operation.

Syntax

UINT DragQueryFile(          HDROP hDrop,
    UINT iFile,
    LPTSTR lpszFile,
    UINT cch
);
Parameters

hDrop
Identifier of the structure containing the file names of the dropped files.
iFile
Index of the file to query. If the value of the iFile parameter is 0xFFFFFFFF, DragQueryFile returns a count of the files dropped. If the value of the iFile parameter is between zero and the total number of files dropped, DragQueryFile copies the file name with the corresponding value to the buffer pointed to by the lpszFile parameter.
lpszFile
Address of a buffer to receive the file name of a dropped file when the function returns. This file name is a null-terminated string. If this parameter is NULL, DragQueryFile returns the required size, in characters, of the buffer.
cch
Size, in characters, of the lpszFile buffer.
--------------------------------------------------------------
Return Value

When the function copies a file name to the buffer, the return value is a count of the characters copied, not including the terminating null character.

If the index value is 0xFFFFFFFF, the return value is a count of the dropped files. Note that the index variable itself returns unchanged, and will therefore remain 0xFFFFFFFF.

If the index value is between zero and the total number of dropped files and the lpszFile buffer address is NULL, the return value is the required size, in characters, of the buffer, not including the terminating null character.

Windows 95/98/Me: DragQueryFile is supported by the Microsoft Layer for Unicode. To use this, you must add certain files to your application, as outlined in Microsoft Layer for Unicode on Windows 95/98/Me Systems.
#1. EDIT 에 대한 설명
일반적으로는 텍스트를 입력할수 있는 컨트롤이라고 생각하면 된다.

#2. 일반적인 생성법
CreateWindow("edit", NULL, 생성스타일, x, y, width, height, 부모윈도우, 메뉴, 핸들인스턴스, NULL )



#3. 생성스타일
ES_AUTOHSCROLL : 수평 스크롤 지원
ES_AUTOVSCROLL : 수직 스크롤 지원

ES_LEFT : 왼쪽 정렬
ES_CENTER : 중앙 정렬
ES_RIGHT : 오른쪽 정렬

ES_LOWERCASE : 소문자로 변환출력
ES_UPPERCASE : 대문자로 변환출력

ES_MULTILINE : 여러줄 편집 가능

ES_NOHIDESEL : 포커스 잃어도 선택영역표시

ES_READIONLY : 읽기 전용

ES_PASSWORD : 패스워드 입력 에디트생성 ( 기본 * )

ES_NUMBER : 숫자만 입력받음

ES_WANTRETURN : 대화상자에서 Enter 키로 개행 가능 ( 대화상자에서 엔터는 보통 확인으로 사용 )

ES_OEMCONVERT : 입력된 문자를 OEM 문자셋으로 변경.



#4. 생성예

멀티라인)
CreateWindow("edit",NULL,
                      WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL|WS_BORDER|
                      ES_MULTILINE,
                      10,10,200,200,
                      hWnd,(HMENU)ID_EDIT,g_hInst,NULL);



에디터박스 변경되었는지 확인하는 법)
if ( SendMessage(hEdit, EM_GETMODIFY, 0, 0 ) == TRUE ) {
    return MessageBox(g_hWnd, "저장확인","확인",MB_YESNOCANCEL);
} else return IDNO;



패스워드 문자 변경)
SendMessage( hEdit, EM_SETPASSWORDCHAR, (WPARAM) '#', 0 );

에디터 편집용량제한변경 )
SendMessage( hEdit, EM_LIMITTEXT , (WPARAM)1048576 , 0 );



폰트변경 1)
HFONT hFont;
hFont = CreateFont( 30,0,0,0,0,0,0,0,HANGEUL_CHARSET,3,2,1,
                             VARIABLE_PITCH, | FF_ROMAN , "궁서");
SendMessage( hEdit , WM_SETFONT , (WPARAM)hFont , MAKELPARAM(FALSE,0) );

폰트변경 2)
HFONT hFont;
hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
SendMessage(g_hEdit, WM_SETFONT, (WPARAM) hFont,MAKELPARAM(FALSE,0));
윈도우는 여섯개의 스톡 폰트를 지원하며 이것들은 GetStockFont 를 이용하여 바로 이용할수 있다.
메세지는 다음과 같다.




문자갯수 리턴받기)

length = SendMessage(g_hEdit,WM_GETTEXTLENGTH,0,0);

length는 \0을 포함하지 않은 실제 문자수를 리턴시켜 준다.
( The length does not include the terminating null character. )




에디트박스안의 문자리턴 받기)

length2= GetWindowText(g_hEdit,buffer,length+1);

buffer은 저장되어질 char* 버퍼.

length2는 리턴 되어진 문자의 갯수.

length+1 은 Edit 박스에서 리턴되어  buffer로 들어갈 문자수.
length+1을 하는 이유는 리턴 되는 문자열이 \0을 포함하고 있기 때문이다.
즉 문자열로 받기 위해서는 원래의 문자수 + 1 (\0) 을 해서 리턴을 받아야
문자열에 대응이 되게 되어 있다.
( copy to the buffer, including the NULL character.  )

참고 사항.
#1. WS_HSCROLL 을 쓴다면 자동개행(워드랩)은 되지 않음.
#2. EM_SETFONT는 사용되지 않음. 폰트 변경을 위해서 WM_SETFONT 메세지 사용할것
키보드에는 여러 특수문자가 있습니다. !나 @, %, ^와 같은것들 말이지요.
편의상 ~는 '물결', ^는 '꺽쇄', *는 '별표' 등으로 읽는데 정확한 명칭은 무엇일까요?
읽는 법은 다음과 같습니다.

       ! : Exclamation Point (익스클레메이션 포인트)
       " : Quotation Mark (쿼테이션 마크)
       # : Crosshatch (크로스해치)
       $ : Dollar Sign (달러사인)
       % : Percent Sign (퍼센트사인)
       @ : At Sign (엣 사인, 혹은 엣)
       & : Ampersand (앰퍼센드)
       ' : Aposterophe (어퍼스트로피)
       * : Asterisk (아스테리스크)
       - : Hyphen (하이픈)
       . : Period (피리어드)
       / : Slash (슬래시)
       \ : Back Slash (백슬래시)
       : : Colon (콜론)
       ; : Semicolon (세미콜론)
       ^ : Circumflex (서큠플렉스)
       ` : Grave (그레이브)
       { : Left Brace (레프트 브레이스)
       } : Right Brace (라이트 브레이스)
       [ : Left Braket (레프트 브라켓)
       ] : Right Braket (라이트 브라켓)
       | : Vertical Bar (버티컬바)
       ~ : Tilde (틸드)


출처 : http://mytechnic.com/brd_tips/view.php?cmcode=php&mycode=tips&bfcode=143
자바를 위주로 설명했을 뿐이지.
주요 부분들은 어디든지 적용할수 있는 ( 객체지향 ) 내용들이다.



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

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

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

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

20031211 - Chan
==========================================================================


안녕하세요.
찬 입니다.

정말 오랜만이죠 ^^

컴터가 좋아 지니 더 공부를 안하는것 같아서 참 걱정입니다. -_-;;;

오늘 다루어 볼 내용은 정말로 제가 다루고 싶었던 Non-Blocking I/O 입니다.

아마도 이 내용을 다 다루고 나면 더 이상의 NIO 정리를 없을것 같습니다.
( 필요할때 나머지를 정리하던지.. 할것 같습니다. )

제 목표가 우선 JAVA로 채팅 서버를 만드는것인데.
이 Non-Blocking I/O로 만들려고 한것 입니다.
( 그래서 1.4 튜토리얼도 구입했구요 ^^ )

인터넷에 있는 Non-Blocking I/O에 대한 강좌나 글들은
초보가 알아 먹기 힘들게 되어 있더군요.
그래서 전 제가 알아 먹기 쉽게. 글을 적도록 하겠습니다.


Java 1.4 이전에는 이 Non-Blocking I/O (이하 NBIO : 정식명칭이 아님)가 지원되지 않았습니다.
1.4에서 지원이 되는것이지요.
C나 C++에서는 이미 지원이 되고 있었죠.


첫번째 글에도 적었지만 NBIO에 대하여 대충 아는대로 설명 드리겠습니다.
자바 1.3이하에서는 입출력을 위해서 항상 block 되어 있어야 했습니다.
( 비록 다른 방법을 이용하여 block를 피할수는 있었지만.. )
여기서 block가 되어 버리면 다른 작업을 하지 못하고 계속 대기하고 있어야 할것입니다.
정말로.. 낭비가 아닐수 없죠.


하지만 1.4에서는 I/O를 할때 block 이 되지 않게 할수 있습니다.
즉 i/o를 할때 그 결과를 기다리지 않습니다. 바로 다음 작업으로 넘어가는 거죠.
그럼 잘되었는지 안되었는지는 어떻게 알수 있냐구요?
그건 천천히 말씀 드리겠습니다.


보통 자바( 또는 다른언어로 ) 서버를 만들때에 나오는 예제나 글들은
클라이언트 하나 하나 마다 모두 쓰레드를 만들어서 돌립니다.
즉, 한 채팅 사이트에 20명이 붙어서 대화를 하면 쓰레드가 총 20개가 생기는것입니다.
이것은 엄청난 낭비라고 볼수 있습니다.
쓰레드가 20개라는 말은 쓰레드에 사용되는 각종 private 변수들이 모두 따로 존재하는것이라서
메모리 관리 측면에서도 문제가 됩니다. 그리고 자세히는 모르지만 스레드를 많이 쓰면
부하를 많이 받는다는 말도 들어 본것 같습니다. ;;
( 설명하는 제가 정확하게 모르니;; 이해를 부탁드립니다. )


자~ 설명은 이정도로 대충 끝내고 다름으로 넘어 가겠습니다.


이번에 설명 드릴것은 Polling (폴링) 이라고 불리는 기법입니다.
서버를 만들때 다음과 같은 기법을 사용하여 작성하는 것입니다.

채팅서버를 만든다고 하면 여러개의 클라이언트 커넥션이 존재할것입니다.
처음에 1번의 커넥션을 조사해서 IO가 있으면 처리하고. 없다면
2번의 커넥션을 조사하고... 다시 3번을 조사하고.
맨 마지막 커넥션까지 가면 다시 1번커넥션을 검사..
이런 방식으로 처리하는 방식입니다.
( 뭐.. 돌아가는 방식은 그렇게 어렵지 않군요 .. )
( 책에서 확인하니 커넥션이 한번 루프를 돌때마다 0.1초 정도 쉬어주어야 한다는군요 ;; )


하지만 이 방식은 약간의 문제가 있습니다.
만약 1번의 커넥션이 처리가 된지... 나머지것들은 I/O가 없고 다시 1번에서 IO가 발생하면
끝까지 루프를 돈 뒤에야 1번 커넥션을 다시 처리할수 있다는 것입니다.
그렇다면 전혀 입출력이 없는 2~n 번까지의 커넥션을 거치기 때문에
1번의 처리는 느려지는것은 당연지사입니다.



이것을 보완하는 방법이 이제 설명드릴 select라는 방식입니다.
( 저도 말만 들어 봤지.. 이번에 처음 공부하는 부분입니다 ^^ )

그럼 select를 공부해야 하는데. 우선 이부분은 잠시 후에 다시 하겠습니다.



우리가 오늘 배울것은 Non-Blocking I/O에 대해서 배울것입니다.
이것은 java 1.4 부터 나오는 nio에 속해 있죠.
두번째 정리에서 공부했던게 생각나시나요?
nio에서는 입출력을 위해 Stream을 바로 쓰지 않고 Channel을 만들어서 쓴다는것.
맞습니다. 우리는 Non-Blocking I/O에서도 Channel을 만들어서 사용할것입니다.

그럼. 소스를 보면서 이야기 해 보겠습니다.

=======================================================================
1: ServerSocketChannel ssc = ServerSocketChannel.open();
2: ssc.configureBlocking( false );
3: ServerSocket ss = ssc.socket();
4: InetSocktAddress isa = new InetSocketAddress( PORT );
5: ss.bind( isa );
=======================================================================

소스를 보시면 우선 1 서버소켓채널을 생성합니다.
( 이 역시 ByteBuffer과 마찬가지로 생성자가 없이 생성을 시키는것입니다. )
그리고 2 서버소켓채널을 NBIO 모드로 설정합니다.
그런뒤에 서버소켓채널에서 서버소켓을 하나 얻습니다.
그리고 port를 정해서 서버소켓에 바인드 시킵니다.
( 여기서 바인드 시킨다는 말은 match 시킨다는 뜻으로 해석하면 될것 같습니다. )


뭐 순서만 본다면 그렇게 어렵지는 않군요..





그럼 이제 다시 select에 대하여 공부해 보죠..
select는 이것 하나로 입출력을 감지 할수 있습니다. 감지 할수 있다는 말이 참 좋은 말이죠.
예전의 폴링 같은 경우는 하나씩 검사를 해서 처리를 하곤했는데.
데이터가 입출력되는지 감지를 하기 때문에 일일이 검사할 필요가 없어지게 된것입니다.

자 그림 select를 생성해 보겠습니다.
========================================================================
import java.nio.channels.*;
Selector selector = Selector.open();
========================================================================
생성하는것 역시 간단하군요. 마찬가지로 여기도 Selector 생성자를 직접 사용하지 않는군요.




그럼 지금 만든 selector를 서버소켓채널에 등록을 시켜야 할것입니다.
========================================================================
ssc.register( selector, SelectionKey.OP_ACCEPT );
========================================================================
이 줄은 ssc에 selector을 등록시켜서 ssc를 감지하겠다는 말입니다.
맨 마지막에 있는 SelectionKey.OP_ACCEPT는 이것을 감지하겠다는 말입니다.
즉 ACCEPT를 감지하겠다는 말이 됩니다.

그럼 어떤 종류를 감지 할수 있는지 대충 적어 보겠습니다.

OP_READ    : 채널에 데이터가 들어와서 읽을수 있는 상태가 되면 발생.
OP_WRITE   : 채널이 쓰기 가능한 상태가 되면 발생.
OP_CONNECT : 리모트 서버에 완벽하게 연결되었을때 발생.
OP_ACCEPT  : 서버소켓에 클라이언트가 접속시 발생.

흠. 어렵지는 않군요.
채널은 입출력이 다 되니 당연히 READ와 WRITE 둘다 있어야 할것입니다.
그리고 클라이언트입장에서 서버에 접속했을때(CONNECT)와
서버입장에서 클라이언트가 접속했을때(ACCEPT) 이렇게 이벤트(?) 가 발생되는군요.


여기서 하나 짚고 넘어가야 할 부분이 있습니다.
저기 네가지의 경우는 모든 소켓관련 부분에서 쓸수 있는가 입니다.
당연히 정답은 "아니오"가 됩니다.

OP_READ와 OP_WRITE는 당연히 Socket쪽에서만 쓸수 있고
OP_CONNECT와 OP_ACCEPT는 당연히 ServerSocket쪽에서만 쓸수 있습니다.
( 데이터를 입출력할때는 Socket을 사용하여 입출력합니다. )
( ServerSokcet는 접속 부분만 관련되어 있죠 )



만약 하나의 서버소켓채널에서 여러가지의 이벤트를 받고 싶다면 다음과 같이 사용하면 됩니다.
========================================================================
ssc.register( selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ );
========================================================================
저렇게 | ( bit연산 OR )을 사용해서 여러개의 이벤트를 받아 들일수도 있습니다.


하지만 여기서 문제가 또 하나 발생합니다.
이벤트가 일어 났다면 둘중(ACCEPT와 READ)중 어느 것이 발생했는지 알아야 할것입니다.
register() 메소드는 리턴값이 SelectionKey 객체를 반환하게 됩니다.

그것을 이용해서 실제도 일어난 이벤트는 다음과 같은 코드로 검출이 가능합니다.
===========================================================================================
SelectionKey sk = ssc.register( selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ );
int op = sk.readOps();
if ( ( op & SelectionKey.OP_ACCEPT ) == SelectionKey.OP_ACCEPT ) { // SomeCode };
===========================================================================================


오늘은 이만 끝내도록 하죠.
시간도 늦었고.. ( 새벽 4:30분 -_-;; 백수는 할일이 없음 ;; )

다음에는 이제 처리하는 방법에 대하여 대략적인 설명과 코드를 적도록 하겠습니다.
그 다음시간에는 아마도 채팅 서버를 만들수 있을것 같네요.
물론 프로토콜 같은게 정확하게 정해진게 없으니.. 대충이라도 하나 만들어 보겠습니다.
오늘 드디어 CVSNT를 설치 했다.

CVSNT를 설치 할때 Full 로 설치 해야 한다.
그렇지 않으면 ntserver_protocol.dll 이 설치 되지않아..
애로사항이 있다.

설치후
set cvsroot=:ntserver:127.0.0.1:/java
cvs passwd -a cvs
(패스워드 입력)

하면 사용자 등록까지 끝나게 된다.




그런데 이클립스에서 CVSNT를 쓸려고 하는데 약간의 문제가 있는것 같다.
저장소 위치를 앞에 붙인다는데.. 서로 안 되는것 같은 느낌도 든다.

자세한 자료는 더 찾아 보아야 겠다.

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

문의점, 오류, 잘못된 용어들은 저의 홈페이지 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-02] NIO를 이용한 간단한 예제  (5) 2003.12.06
[NIO-01] NIO 란 무엇인가?  (0) 2003.12.02
서로 다른 Thread 끼리의 참조가 가능합니까?  (1) 2003.10.07
채팅 서버 UML 다이어그램  (0) 2003.10.01
  1. 김금동 2008.03.05 12:04

    참말 좋은자료 올려주셔서 감사합니다.
    많은 도움이 되였습니다.
    당신의 일이 앞으로 잘되길 바랍니다.

    • Chan 2008.03.06 03:49

      오래된 글에 보기도 힘들글 보신다고 고생하셨네요.

  2. javatop 2009.12.06 11:56

    참말 좋은자료 올려주셔서 감사합니다.
    많은 도움이 되였습니다.

    jdk1.6에서도 같은지요...

    • Chan 2009.12.09 22:56

      Java 6에서도 똑같이 동작하리라 생각됩니다.

      방문 감사합니다.

  3. 경력만 많은 개발자 2014.10.08 16:32

    ㅎㅎ 감샤합니다.
    좋은 예제내요
    좋은 내용 많이 올려주세요

+ Recent posts