본 글은 Indy의 홈페이지에 링크되어있던 http://www.hower.org/Kudzu/Articles/IntroToIndy/를 저의 초필살울트라아토믹하이퍼완벽삽질로 번역(의역?)한 글입니다. 저처럼 초초초초초초초초.....초보이신 분들께 작게나마 도움이 되길 희망합니다.

이 글은 델마당의 Robert Teminian에 의해 번역되었으며, 옮겨가시는건 자유입니다만(옮겨가실 분이나 있을런지 모르겠네요) 출처와 원저자, 그리고 번역자를 꼭 밝혀주십시오. 어디로 가져가는지 밝히시면 더 좋겠습니다만, 대부분 귀찮으실테니 그러고 싶지 않으시거든 안하셔도 됩니다.

그리고 내용수정은 오타수정 빼곤 삼가해주세요. -.-

Robert Teminian, the Blue Phoenix:
-연락은 ilily@chollian.net으로.

제목: 인디를 소개합니다(The Introduction to Indy).

-=-=-=-=-=-=-=-=-=-=-
인디는 블록킹입니다.
-=-=-=-=-=-=-=-=-=-=-
인디는 블록킹 소켓 콜을 사용합니다. 블록킹 콜은 마치 파일에 읽고 쓰는 것과 유사합니다. 만일 당신이 데이터를 읽거나 쓰게 되면 작동된 함수는 동작이 완료되기 전까지 리턴을 행하지 않습니다. 파일작업과 다른 점이 있다면 데이터를 즉각적으로 읽고 쓸 수 없을 수 있기 때문에 함수 사용에 걸리는 시간이 꽤 오래 걸릴 수 있다는 점입니다(아무래도 네트워크나 모뎀의 데이터 전송속도보다 빠르게 움직이지는 못하겠죠).

이를테면, 블록킹 모드는 두 컴퓨터를 연결하기 위해서는 "연결" 메서드를 호출하고 리턴되기를 기다립니다. 만일 성공하면 리턴을 하겠지만, 실패한다면 예외(exception)를 발생시킬 것입니다.


-=-=-=-=-=-=-=-=-=-=-
블록킹은 나쁘지 않습니다.
-=-=-=-=-=-=-=-=-=-=-
블록킹 소켓은 정당한 근거 없이 항상 공격의 대상이 되었습니다. 하지만 일반적인 믿음과는 달리 블록킹 소켓은 절대 나쁘지 않습니다.

윈쇽이 윈도우즈로 포팅되었을 때 한가지 문제점이 발생했습니다. 유닉스에서는 포크(멀티스레딩과 유사하지만 스레드가 아닌 분리된 프로세스에 관계된 것임)가 일반화되어 있었습니다. 유닉스 클라이언트와 대몬들은 프로세스를 포킹하고, 블록킹 소켓을 사용했습니다. 윈도우즈 3.x대 버전은 이러한 포킹을 할 수 없었으며 멀티스레딩은 지원되지 않았습니다. 블록킹 인터페이스를 사용하는 것은 유저 인터페이스가 "잠겨지고" 프로그램이 응답하지 않는 결과를 초래했습니다. 따라서 윈쇽에는 비동기적 작동을 지원하는 확장이 추가되어 달랑 하나뿐인 프로그램의 스레드가 "잠기는" 문제를 극복하자 하였습니다. 그렇지만 이러한 방식은 다른 방식의 프로그래밍을 요구했으며, 마이크로소프트와 기타등등(.....)은 윈도우즈 3.x가 가지고 있는 단점을 감추기 위해 블록킹을 격렬히 비방하였습니다.

그 후 멀티스레딩을 확실히 지원할 수 있는 Win32가 등장하였습니다. 그렇지만 이쯤 되자 모든 사람들의 마인드가 바뀌었습니다(이를테면, 개발자들은 블록킹이 나쁘다고 믿게 된 것이죠). 그리고 이러한 성향을 되돌리는 것은 매우 어려운 일이었습니다. 따라서 블록킹 소켓에 대한 비난은 계속되었습니다.

실제로 블록킹 소켓은 유닉스가 쓰는 단 하나의 방식입니다. 또한 블록킹 소켓은 여러가지 다른 장점을 제공하며, 스레딩, 보안, 그리고 다른 면에 있어서 훨씬 뛰어납니다. 물론 유닉스에도 논-블록킹 소켓을 위한 확장(extension)이 제공되었습니다만 이들은 윈도우즈와는 확연히 다르게 동작합니다. 그리고 그것들은 표준안도 아니고, 많이 쓰이는 것도 아닙니다. 유닉스하의 블록킹 소켓은 거의 모든 상황에 이용되며, 앞으로도 그 이용은 계속될 것입니다.

-=-=-=-=-=-=-=-=-=-=-
블록킹의 장점
-=-=-=-=-=-=-=-=-=-=-
프로그래밍하기 쉽다 - 블록킹은 프로그래밍하기가 슆습니다. 모든 사용자 코드를 한 곳에 순차적으로 몰아넣을 수 있지요.
유닉스로 포팅하기 쉽다 - 유닉스가 본질적으로 블록킹 소켓을 사용하는 바, 포팅 가능한 코드를 짜는 것이 간편해집니다. 인디는 이러한 점을 바탕으로 하여 단일 소스로 해결 가능한 솔루션(single source solution)이 되었습니다.
스레드와 잘 연동된다 - 블록킹 소켓은 순차적이므로 이들은 본래부터 캡슐화(encapsulation)되어 있으며 따라서 스레드에 매우 간편하게 사용될 수 있습니다.

-=-=-=-=-=-=-=-=-=-=-
블록킹의 단점
-=-=-=-=-=-=-=-=-=-=-
유저인터페이스는 클라이언트와 함께 얼어버립니다(※역자 주: Freeze. 타락천사님의 2002년 12월 세미나를 참조하세요) - 블록킹 소켓 함수 호출은 작업이 완료되기 전까지는 리턴을 행하지 않습니다. 만일 이러한 함수 호출이 응용프로그램의 메인 스레드에서 이루어진다면 응용프로그램은 그동안 사용자 인터페이스 메시지를 처리할 수 없게 됩니다. 이러한 점은 유저 인터페이스로 하여금 "얼어버리는" 문제점을 만들어내는데, 왜냐 하면 갱신 및 화면 재생성과 기타 메시지들이 블록킹 소켓 호출이 응용프로그램에 메시지 프로세스 루프를 넘겨주기 전까지는 작동되지 않기 때문입니다.

-=-=-=-=-=-=-=-=-=-=-
TIdAntiFreeze
-=-=-=-=-=-=-=-=-=-=-
인디는 이러한 유저 인터페이스가 얼어버리는 문제점을 해결하기 위한 특별한 컴포넌트를 가지고 있습니다. IIndAntiFreeze를 추가하는 것만으로 사용자 여러분은 유저인터페이스가 얼어버리지 않은 상태로 표준 블록킹 인디 호출을 사용할 수 있습니다.

TIdAntiFreeze는 내부적으로 스택에 타임아웃을 생성한 뒤 타임아웃동안 Application.ProcessMessage를 수행합니다. The external calls to Indy continue to block, and thus work exactly as without a TIdAntiFreeze otherwise(※역자 주: 해석 불가. -_-;). TIdAntiFreeze를 사용함으로서 블록킹 소켓의 장점을 모두 얻고 가장 잘 알려진 단점을 보강할 수 있습니다.

-=-=-=-=-=-=-=-=-=-=-
스레딩
-=-=-=-=-=-=-=-=-=-=-
ㅅ레딩은 거의 항상 블록킹 소켓과 함께 사용됩니다. 논-블록킹 소켓도 스레드화될 수 있지만 이를 위해선 약간의 추가 핸들링 코드를 작성해야 하며, 논블록킹 소켓의 장점은 소켓을 블록킹시킴과 함꼐 사라져버립니다. 스레딩은 블록킹 소켓 서버 생성에 있어 중요한 역할을 수행하므로 추후 잠깐 논의될 것입니다. 스레딩은 또한 고차원적인 블록킹 클라이언트를 만드는데 사용할 수 있습니다.


-=-=-=-=-=-=-=-=-=-=-
스레딩의 장점
-=-=-=-=-=-=-=-=-=-=-
선점화 관리 - 개개의 스레드가 가지고 있는 선점화(priority)가 조정될 수 있습니다. 이를 통해 개개의 서버 테스크나 연결에 CPU 시간이 할당되는 시간을 조절할 수 있습니다.
캡슐화 - 각각의 연결이 포함될 것이며 다른 연결을 거의 방해하지 않습니다.
보안 - 각가의 스레드는 서로 다른 보안 속성을 가질 수 있습니다.
다중프로세서(multiple processors) - 스레딩은 자동적으로 다중 프로세서의 장점을 사용할 수 있습니다.
순차화가 없음 - 스레딩은 진정한 동시발생(concurrency)을 지원합니다. 스레딩이 없다면 모든 요청은 하나의 스레드에 의해 조정되어야 합니다. 이것들이 제대로 작동되게 하기 위해서는 각각의 작업들은 작은 단위로 쪼개져 빠르게 실행될 수 있도록 해야 합니다. 만일 어떠한 작업이 다른 연결의 접근을 막거나 수행되는데 오랜 시간을 소요할 경우 작업의 다른 부분들은 연결작업이 끝날 때까지 멈춰서게 됩니다. 하나의 작업 파트가 종료되면 다음이 실행되고 하는 형태로 수행되는 것이지요. 스레딩을 사용한다면 각각의 작엄은 완벽한 하나의 태스크로서 포로그래밍이 가능하며 운영체제는 작업들 간의 CPU 시간을 쪼갤 것입니다.

-=-=-=-=-=-=-=-=-=-=-
스레드 풀링
-=-=-=-=-=-=-=-=-=-=-
스레드의 생성과 파괴는 리소스 의존적일 수 있습니다. 이것은 각각의 네트워크 연결이 단명(short-lived)하는 특성을 가진 서버에서 특히 그렇습니다. 이러한 서버들은 하나의 스레드를 생성하여 잠깐동안 사용한 뒤 파괴시킵니다. 이러한 행위는 스레드의 생성과 파괴를 매우 자주 유발시키게 되지요. 이에 대한 단적인 예는 타임서버나 웹서버입니다. 어떠한 단순요구가 전달되고, 단순한 대답이 오면 그것으로 끝이지요. 웹브라우저를 사용해 웹사이트를 접속할 때는 서버측에 수백개의 연결과 단절이 일어날 것입니다.

스레드 풀링은 이러한 상황을 완화시킵니다. 요청받는데로 스레드를 만들고 파괴하는 대신, 스레드들은 생성되었지만 작동하지는 않는 목록(즉, 풀-pool-)에서 빌려오는 형태를 취합니다. 만일 스레드가 더이상 필요하지 않을 경우 그 스레드는 파괴되는 대신 다시 풀에 저장됩니다. 스레드는 풀에 있는 동안 작동이 비활성화된(inactive) 상태로 체크되며 따라서 CPU 사이클을 소비하지 않습니다. 게다가, 더 발전된 형태의 스레드에서는 스레드의 크기가 시스템의 요구에 맞춰 동적으로 변경될 수 있습니다.

물론 인디 역시 스레드 풀링을 지원합니다. 인디의 스레드 풀링은 TIdThreadMgrPool 컴포넌트를 사용함으로서 이루어질 수 있습니다.

-=-=-=-=-=-=-=-=-=-=-
수백개의 스레드
-=-=-=-=-=-=-=-=-=-=-
상당히 바쁜 서버는 수백, 아니, 때로 수십만개에 이르는 스레드를 요구하곤 합니다. 아마 여러분들은 스레드하 한 수십만개쯤 되면 시스템이 뻗어버리지 않을까 하는 생각을 하시게 되는데요, 이는 분명 잘못된 생각입니다.

대부분의 서버에서 스레드들은 대부분의 시간을 자료를 기다리는데 소비합니다. 블록킹 모드에서 대기시간동안 스레드는 비활성화됩니다. 따라서, 500개의 스레드를 처리하는 서버에서는 지정된 시간동안 단지 50개의 스레드만 활성화될런지도 모를 일이죠.

아마 여러분의 시스템이 현재 작동시키고 있는 스레드가 몇 개인지 알면 꽤나 놀라실겁니다. 단지 최소한도의 프로그램만 작동시키고 있음에도 불구하고 제 시스템은 무려 333개의 스레드를 가동시키고 있죠. 하지만, 그럼에도 불구하고 제 시스템의 CPU 사용률은 1%에 불과합니다. 아마 IIS(MS Internet Information Server-역자주)는 최소 수백개에서 수천개 이상의 스레드를 생성할 것입니다.

(※역자 주: 원래는 여기에 그림이 들어가는데, 여건상 그림을 집어넣을 수가 없어서 그냥 소설쓰기로 얼버무립니다.)

-=-=-=-=-=-=-=-=-=-=-
스레드와 글로벌 섹션
-=-=-=-=-=-=-=-=-=-=-
다수의 스레드가 자료 입출력을 위해 데이터 접근을 필요로 할 때, 이러한 접근은 분명 자료의 완전성을 위한 제어를 필요로 합니다(※역자 주: 하나의 데이터의 여러개의 스레드가 맞물릴 경우, 한 번에 하나씩 접근하지 않는다면 상당히 피곤해지겠죠. 그나마 읽기일 경우에는 좀 낫습니다만 쓰기일 경우라면 그 수많은 스레드들이 서로 다른 내용을 같은 공간에 집어넣으려고 서로 싸울테니.....). 이러한 점은 스레드를 처음 접근하는 프로그래머에게 상당히 위협적인 요소입니다. 그렇지만 데이터를 통채로 원하는 서버는 극히 일부분일 뿐입니다. 대부분의 서버는 지엽적인 부분에서만 각각의 기능을 수행하죠. 이렇게 해서 각각의 스레드는 고립된 작업을 수행하게 됩니다. 글로벌 읽기/쓰기 섹션은 수많은 멀티스레드 응용프로그램의 뜨거운 감자지만 대부분의 소켓 서버에 있어서 이러한 점은 그다지 문제가 되지 않습니다.

-=-=-=-=-=-=-=-=-=-=-
인디 방법론(Indy Methodology)
-=-=-=-=-=-=-=-=-=-=-
인디는 여러분들에게 친숙할지도 모르는 다른 윈쇽 컴포넌트들과 차이가 있습니다. 만일 여러분이 다른 컴포넌트로 작업하는데 익숙해져있다면, 아마 인디에 대한 가장 좋은 접근방법은 이게 어떻게 동작하는건지 잊어버리는 것일 겁니다. 다른 컴포넌트들은 거의 대부분 논-블록킹(비동기화)모드로 동작합니다. 이것들은 여러분에게 이벤트에 대해 응답하고, 하드웨어를 셋업하고, 종종 대기시간동안 루프를 돌게 만들죠.

이를테면, 다른 컴포넌트를 사용할 경우, 여러분이 "connect" call을 기동하면 여러분은 분명 연결되었음을 확인해주는 이벤트가 발생할때까지 기다리던지 아니면 무한루프를 돌아야 할 것입니다. 인디를 사용할 경우, 여러분은 단순히 "Connect"를 호출하고 상대방이 응답할때까지 멍하니 기다리면 됩니다. 만일 연결이 성공하면 리턴할테고요, 그렇지 않으면 아마 예외(exception)를 발생시킬겁니다.
인디를 이용한 작업은 파일 작업과 비슷합니다. 서로 다른 이벤트들에 대해 일일이 코딩하는 대신, 인디는 여러분들이 모든 코드를 한 곳에 몰아넣을 수 있도록 해줍니다. 게다가, 인디는 스레드 적용이 쉽고, 또한 스레딩에 적합하도록 설계되어 있습니다.

-=-=-=-=-=-=-=-=-=-=-
인디, 무엇이 다른가?
-=-=-=-=-=-=-=-=-=-=-

-=-=-=-=-=-=-=-=-=-=-
잽싸게 보기(Quick Overview)
-=-=-=-=-=-=-=-=-=-=-
1.블록킹 콜 사용.
2.이벤트에 의존하지 않는다 - 인디에는 정보전달 목적으로 쓰일 수 있는 이벤트들이 존재하지만, 이벤트 프로그래밍이 꼭 필요한건 아닙니다.
3.스레딩을 위한 디자인 - 인디는 스레딩을 염두에 두고 설계되었습니다. 그렇지만, 인디는 스레딩을 사용하지 않고도 사용될 수 있습니다.
4.순차적 프로그래밍


-=-=-=-=-=-=-=-=-=-=-
깊게 파고들기(Details)
-=-=-=-=-=-=-=-=-=-=-
인디는 블록킹(동기화) 펑션콜을 사용할 뿐만 아니라 그런 방식으로 동작합니다. 일반적으로 인디 세션은 이렇게 생겼습니다:

with IndyClient do begin
 Connect; Try
 // Do your stuff here
 finally Disconnect; end;
end;

하지만 다른 컴포넌트들을 쓰려면 아마 이렇게 하셨어야 될겁니다:

procedure TFormMain.TestOnClick(Sender: TComponent);
begin
 with SocketComponent do begin
   Connect; try
     while not Connected do begin
     if IsError then begin
       Abort;
     end;
     Application.ProcessMessages;

     OutData := 'Data To send';
     while length(OutData) > 0 do begin
       Application.ProcessMessages;
     end;
   finally Disconnect; end;
 end;
end;

procedure TFormMain.OnConnectError;
begin
 IsError := True;
end;

procedure TFormMain.OnRead;
var
 i: Integer;
begin
 i := SocketComponent.Send(OutData);
 OutData := Copy(OutData, i + 1, MaxInt);
end;

대부분의 컴포넌트들은 프로그래머를 스택으로부터 분리시키는데 그다지 큰 효용을 발위하지 못합니다. 대부분의 컴포넌트들은 사용자를 복잡하기 이를데없는 스택으로부터 분리시키는 대신 단순하게 무시해버리거나 스택을 위한 델파이(또는 C++Builder) wrapper를 제공할 뿐입니다.

-=-=-=-=-=-=-=-=-=-=-
인디 방식(The Indy Way)
-=-=-=-=-=-=-=-=-=-=-
인디는 기초부터 스레딩이 가능하도록 제작되었습니다. 인디를 이용한 서버/클라이언트 구축은 유닉스에서 서버/클라이언트를 구축하는 방법과 유사합니다-단지 훨씬 더 쉽다는 점을 제외하면요. 여러분은 인디와 델파이를 쓰고 있지 않습니까? 유닉스 응용프로그램은 일반적으로 추상화 계층이 거의 없는 상태로 스택을 직접 호출합니다.

일반적으로 유닉스 서버는 한 개 이상의 "청취자(listener)" 프로세스를 생성하여 이를 통해 클라이언트의 요청을 받아들입니다. 서비스를 요청하는 각각의 클라이언트에 대해서, 유닉스는 각각의 클라이언트들에 대해 새로운 프로세스를 포킹(fork)합니다. 이러한 점은 프로그래밍을 매우 쉽게 만듭니다-각각의 프로세스는 단지 하나의 클라이언트만 상대하거든요. 또한, 이때 생성된 각각의 프로세스들이 사용하는 보안체계는 청취자, 루트 인증서, 사용자 인증, 또는 다른 방법에 의해 서로 다르게 설정될 수 있습니다(※역자 주: 쉽게 말해서, 같은 서버지만 클라이언트 입맛에 따라 서로 다른 보안환경을 구축할 수 있다는 소리입니다. 프로세스 1은 SSL1을 쓰고, 프로세스 2는 일반연결에 AES로 동작하고..... 뭐 그런 거죠).

인디 서버들도 이와 매우 비슷하게 동작합니다. 윈도우즈는 유닉스와는 다르게 포킹 실력은 좋지 못하지만, 스레딩은 잘 하는 편입니다. 인디 서버는 각각의 클라이언트 연결에 대해 개별 스레드를 생성합니다.

인디 서버는 우선 메인스레드와 분리된 청취자 스레드를 생성합니다. 이 청취자 스레드는 서버를 향해 들어오는 클라이언트의 요청이 있나 살피는 역할을 수행합니다. 서버가 응답하는 각각의 클라이언트들에 대해 서버는 그 클라이언트에 대한 새로운 스레드를 만들어냅니다. 그리고 그 스레드 안에서 실질적인 수행이 이루어지는 거죠.

-=-=-=-=-=-=-=-=-=-=-
인디 클라이언트 흝어보기
-=-=-=-=-=-=-=-=-=-=-
인디는 매우 높은 수준의 추상화 게층을 제공하도록 디자인되었습니다. TCP/IP 스택의 복잡한 구조와 세부사항은 인디 프로그래머로부터 숨겨져 있습니다.
일반적인 인디 클라이언트 세션은 이렇게 생겼습니다:

with IndyClient do begin
 Host := 'zip.pbe.com'; // Host to call
 Port := 6000; // Port to call the server on
 Connect; Try
   // Do your stuff here
 finally Disconnect; end;
end;

-=-=-=-=-=-=-=-=-=-=-
인디 서버 흝어보기
-=-=-=-=-=-=-=-=-=-=-
인디 서버는 우선 메인스레드와 분리된 청취자 스레드를 생성합니다. 이 청취자 스레드는 서버를 향해 들어오는 클라이언트의 요청이 있나 살피는 역할을 수행합니다. 서버가 응답하는 각각의 클라이언트들에 대해 서버는 그 클라이언트에 대한 새로운 스레드를 만들어냅니다. 그리고 그 스레드 안에서 실질적인 수행이 이루어지는 거죠.
(※역자 주 1: 이 부분에는 그림이 들어갑니다.)
(※역자 주 2: 이 부분은 몇 줄 위의 내용과 98% 이상 똑같은 내용에 문장 수까지 똑같았던지라 그냥 옮겨왔습니다.)

-=-=-=-=-=-=-=-=-=-=-
예제
-=-=-=-=-=-=-=-=-=-=-
여기 있는 예제들은 일반적으로 쉬운 재사용을 위해 다른 컴포넌트를 상속한 새 컴포넌트 안에 캡슐화되어 존재해야 하지만, 데몬스트레이션을 위해 이 예제들은 간단한 응용프로그램이 될 것입니다. 다양한 환경을 보여주는 수많은 프로젝트들이 존재하며, 이들은 zip 파일로도 제공될 것입니다.

-=-=-=-=-=-=-=-=-=-=-
예제 1 - 우편번호 찾기
-=-=-=-=-=-=-=-=-=-=-
첫번째 프로젝트는 최대한 간단하게 제작되었습니다. "우편번호 찾기"는 클라이언트가 도시(city)와 주(state)를 알려주면 서버가 그에 해당하는 우편번호를 보내주는 프로그램입니다.

혹시나 미국이 아닌 다른 곳에 계셔서 우편번호가 뭔지 모를 분들을 위해 말씀드리자면, 우편번호는 미국의 우체국에서 배달가는 곳이 어디인지를 숫자로 표시해놓은 코드입니다. 미국의 우편번호는 다섯자리 숫자로 되어있지요.

-=-=-=-=-=-=-=-=-=-=-
프로토콜
-=-=-=-=-=-=-=-=-=-=-
서버/클라이언트 구축에 있어서 첫 단계는 바로 프로토콜을 이해하는 것입니다. 표준 프로토콜에 있어 이들은 그에 적절한 RFC를 읽는 것을 의미합니다. "우편번호 찾기"에서는 다음과 같이 정의된 프로토콜을 사용할 것입니다.

대부분의 프로토콜은 대화체이며 단순 문자(plain text)입니다. 대화체라는 것이 의미하는 것은 우선 명령어가 주어지고, 명령어에 따른 결과, 그리고 때론 필요한 데이터가 뒤따라오는 것을 의미합니다. 몇몇 극소수의 프로토콜은 대화체는 아니지만, 그럼에도 불구하고 단순 문자입니다. "우편번호 찾기"는 단순 문자이지만 대화체는 아닙니다. 단순 문자 형태로 제작된 프로토콜은 디버깅이 쉽고 다른 종류의 프로그래밍 언어와 운영체제를 사용하는 인터페이스를 구축하는데 용이합니다.

연결시 서버는 환영 메시지를 보내고 명령어를 받아들일 것입니다. 이 명령어들은 "ZipCode x"(x는 우편번호) 또는 "Quit" 중 하나입니다. ZipCode 명령어는 한 줄의 메시지로 응답되거나 아니면 엔트리에 존재하지 않는 코드일 경우에는 빈 줄(empty line)만을 출력할 것입니다. Quit 명령어는 연결 해제를 요구할 것입니다. 서버는 Quit 명령어를 받을때까지 명령어를 반복해서 처리할 수 있습니다.

-=-=-=-=-=-=-=-=-=-=-
서버 소스코드
-=-=-=-=-=-=-=-=-=-=-
unit ServerMain;

interface

uses
 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
 IdBaseComponent, IdComponent, IdTCPServer;

type
 TformMain = class(TForm)
   IdTCPServer1: TIdTCPServer;
   procedure FormCreate(Sender: TObject);
   procedure FormDestroy(Sender: TObject);
   procedure IdTCPServer1Connect(AThread: TIdPeerThread);
   procedure IdTCPServer1Execute(AThread: TIdPeerThread);
 private
   ZipCodeList: TStrings;
 public
 end;

var
 formMain: TformMain;

implementation
{R *.DFM}

procedure TformMain.IdTCPServer1Connect(AThread: TIdPeerThread);
begin
 AThread.Connection.WriteLn('Indy Zip Code Server Ready.');
end;
procedure TformMain.IdTCPServer1Execute(AThread: TIdPeerThread);
var
 sCommand: string;
begin
 with AThread.Connection do begin
   sCommand := ReadLn;
   if SameText(sCommand, 'QUIT') then begin
     Disconnect;
   end else if SameText(Copy(sCommand, 1, 8), 'ZipCode ') then begin
     WriteLn(ZipCodeList.Values[Copy(sCommand, 9, MaxInt)]);
   end;
 end;
end;

procedure TformMain.FormCreate(Sender: TObject);
begin
 ZipCodeList := TStringList.Create;
 ZipCodeList.LoadFromFile(ExtractFilePath(Application.EXEName) + 'ZipCodes.dat');
end;

procedure TformMain.FormDestroy(Sender: TObject);
begin
 ZipCodeList.Free;
end;

end.
이들 중 인디에 관련된 부분들은 IdTCPServer1 컴포넌트, IdTCPServer1Connect 메서드, 그리고 IdTCPServer1Execute 메서드 뿐입니다.

IdTCPServer1는 TIdTCPServer 컴포넌트이며 폼 위에 놓여집니다. 폼에 올려놓을 때 다음과 같은 프라퍼티들이 기본값으로부터 변경될 것입니다:

Active = True - 응용프로그램이 작동되면서 서버가 청취(listen)를 시작하도록 합니다.
DefaultPort = 6000 - 이 데모프로그램을 위한 대수입니다. 이 포트는 청취자가 인디 클라이언트의 요청을 받아들이는 통로로서 작동할 것입니다.

IdTCPServer1Execute 메서드는 서버의 OnExecute 이벤트에 후킹됩니다. OnExecute 이벤트는 클라이언트의 접속이 허가되었을 경우 발생합니다. OnExecute 이벤트는 여러분들에게 친숙한 다른 이벤트들과는 독특한 차이점을 지닙니다. OnExecute는 스레드의 내용 안에서 발생합니다. 이벤트가 발생되었을 때 사용되는 스레드(※역자 주: 여기서는 IdTCPServer1Execute 메서드가 되겠죠)는 메서드의 AThread 아규먼트로 넘겨집니다. 이는 OnExecute 이벤트가 동시다발적으로 발생될 때 매우 중요해집니다. 이러한 작업들은 이벤트와 함께 발생하므로 서버는 새로운 컴포넌트를 여러개 사용할 필요 없이 구축될 수 있습니다. 또한 인디에서는 상속받는 컴포넌트가 만들어질 때 오버라이드될 수 있는 메서드들도 제공됩니다.

OnConnect는 연결이 허가되었을 경우 발생하며, 역시 이 때 별도의 스레드가 생성됩니다. 이 서버에서 이는 단지 클라이언트에 환영 메시지를 전달하기 위해서만 발생됩니다. 물론, 이 또한 여러분이 원할경우 OnExecute에 이 작업을 포함시킬 수 있습니다.

OnExecute 이벤트는 연결이(어떠한 형태로든) 단절될 때까지 반복적으로 발생합니다. 이는 이벤트 안에서 연결상태를 검사하기 위한 루프의 사용을 불필요하게 만듭니다.

IdTCPServer1Execute는 ReadLn과 WriteLn이라는 두 가지의 기본적인 인디 함수를 사용합니다.ReadLn은 연결상에서 한 줄을 읽어들이고 WriteLn은 연결상에 한 줄을 적어넣습니다.

sCommand := ReadLn;
이 문장은 클라이언트로부터 명령어 한 줄을 읽고 그 문장을 문자열 지역변수인 sCommand에 저장시킵니다.

if SameText(sCommand, 'QUIT') then begin
 Disconnect;
end else if SameText(Copy(sCommand, 1, 8), 'ZipCode ') then begin
 WriteLn(ZipCodeList.Values[Copy(sCommand, 9, MaxInt)]);
end;
다음, sCommand에 저장된 문장은 해석되어 그에 따른 명령을 수행합니다.

만일 명령어가 Quit일 경우 연결은 단절됩니다. 연결 단절 이후에는 연결에 대한 그 어떠한 읽고 쓰기도 불가능합니다. 만일 연결 단절 이후 이벤트가 종료되었다면, 청취자는 이벤트를 다시 호출하지 않고 스레드와 연결을 모두 깔끔하게 제거할 것입니다.

민일 명령어가 ZipCode 파라미터였다면 명령 뒤의 문자열은 별도로 추출되어 이를 기반으로 도시와 주를 검색하는 작업이 수행될 것입니다. 도시와 주의 이름은 연결된 클라이언트쪽으로 전달되거나, 만일 매칭되는 주소가 없을 경우 빈 줄만이 전송될 것입니다.

마지막으로 메서드에서 빠져나옵니다. 서버는 연결이 유지되는 한 이벤트를 계속 다시 불러내어 클라이언트로 하여금 명령어를 여러번 사용할 수 있도록 할 것입니다.

-=-=-=-=-=-=-=-=-=-=-
클라이언트 소스코드
-=-=-=-=-=-=-=-=-=-=-
unit ClientMain;

interface

uses
 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
 StdCtrls, ExtCtrls, IdAntiFreezeBase,
 IdAntiFreeze, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient;

type
 TformMain = class(TForm)
   Client: TIdTCPClient;
   IdAntiFreeze1: TIdAntiFreeze;
   Panel1: TPanel;
   Panel2: TPanel;
   memoInput: TMemo;
   lboxResults: TListBox;
   Panel3: TPanel;
   Button1: TButton;
   Button2: TButton;
   Label1: TLabel;
   procedure Button2Click(Sender: TObject);
   procedure Button1Click(Sender: TObject);
 private
 public
 end;

var
 formMain: TformMain;

implementation
{R *.DFM}

procedure TformMain.Button2Click(Sender: TObject);
begin
 memoInput.Clear;
 lboxResults.Clear;
end;

procedure TformMain.Button1Click(Sender: TObject);
var
 i: integer;
 s: string;
begin
 butnLookup.Enabled := true; try
   lboxResults.Clear;
   with Client do begin
     Connect; try
       lboxResults.Items.Add(ReadLn);
       for i := 0 to memoInput.Lines.Count - 1 do begin
         WriteLn('ZipCode ' + memoInput.Lines[i]);
         lboxResults.Items.Add(memoInput.Lines[i]);

         s := ReadLn;
         if s = '' then begin
           s := '-- No entry found for this zip code.';
         end;
         lboxResults.Items.Add(s);

         lboxResults.Items.Add('');
       end;
       WriteLn('Quit');
     finally Disconnect; end;
   end;
 finally butnLookup.Enabled := true; end;
end;

end.
인디와 관련된 부분은 Client 컴포넌트와 Button1Click 메서드 뿐입니다.

클라이언트는 폼 위의 TIdTCPClient 컴포넌트입니다. 기본값으로부터는 다음과 같은 사항이 변경되었습니다:

Host = 127.0.0.1 - 서버는 클라이언트와 같은 컴퓨터상에서 동작합니다.
Port = 6000 - 본 데모를 위한 대수(arbitrary number)입니다. 이는 서버와 연결시 클라이언트가 접촉을 시도할 서버측 포트 번호입니다.

Button1Click은 Button1의 OnClick 이벤트에 의해 후킹됩니다. 버튼이 클릭되면 이 메서드가 실행되는 것이죠. 여기서 인디 컴포넌트가 하는 일은 다음과 같이 요약될 수 있습니다:

서버에 연결합니다 ( Connect; ) 
서버로부터 환영 메시지를 읽어들입니다.
TMemo에 사용자가 입력한 각 줄에 대하여:
서버에 결과를 요청합니다 ( WriteLn('ZipCode ' + memoInput.Lines[i]); ) 
서버로부터 결과를 읽어들입니다 ( s := ReadLn; ) 
연결 종료 명령어를 송부합니다 ( WriteLn('Quit'); ) 
연결을 단절시킵니다 ( Disconnect; ) 
-=-=-=-=-=-=-=-=-=-=-
테스팅
-=-=-=-=-=-=-=-=-=-=-
이 데모프로그램들은 미리 테스트가 되었으며 TCP/IP가 여러분의 컴퓨터에 인스톨된 한 작동될 것입니다. 여러분은 이 프로그램의 호스트 프라퍼티에 IP 어드레스나 URL을 적어넣음으로서 이 프로그램이 네트워크로 연결된 원격지의 컴퓨터에서 실행되는지를 확인해볼 수 있습니다. 그렇지 않을 경우 본 프로그램은 클라이언트가 실행되는 바로 그 컴퓨터에서 서버를 찾을 것입니다.

본 프로젝트를 실험해보시려면, 컴파일 후 서버를 먼저 실행시키고 그 다음 클라이언트를 실행시키십시오. 메모필드에 우편번호를 넣은 후 "lookup"버튼을 클릭하시면 프로그램이 작동될 것입니다.

-=-=-=-=-=-=-=-=-=-=-
디버깅
-=-=-=-=-=-=-=-=-=-=-
단순 문자 프로토콜은 텔넷 세션을 이용하여 쉽게 디버깅될 수 있습니다. 이를 위해 여러분은 서버 프로그램이 청취하는 포트만 알면 됩니다. 우편번호 검색 서버는 포트 6000번을 청취하고 있지요.

서버를 재기동시키십시오. 다음 커맨드 프롬프트 창(쉽게 말하자면, 도스창)을 여시고 다음을 입력하시기 바랍니다:

telnet 127.0.0.1 6000 <enter>

여러분은 이제 우편번호 검색 서버에 연결되었습니다. 어떠한 서버는 환영 메시지와 함께 여러분을 맞이할 것입니다. 하지만 이녀석은 절대 그렇지 않죠. 여러분은 여러분이 입력하는 내용을 보실 수 없으실겁니다. 대부분의 서버는 키입력을 화면에 표시하지 않습니다-표시해봐야 라인에 부하만 더 주는 꼴이거든요. 하지만 여러분은 여러분의 텔넷 프로그램 옵션에서 "Echo On"을 줌으로서 이 문제를 해결할 수 있습니다. 각각의 텔넷 프로그램은 이러한 기능을 다른 이름으로 부르곤 합니다-심지어는 없는 것도 있고요. 어찌됐거나, 다음을 입력해 보십시오:

zipcode 37642 <enter>
그러면 여러분은 다음과 같은 대답을 들을 것입니다:

CHURCH HILL, TN
서버로부터 연결을 종료시키려면 다음을 입력하십시오:

quit <enter>

-=-=-=-=-=-=-=-=-=-=-
예제 2 - 데이터베이스 접근
-=-=-=-=-=-=-=-=-=-=-
이 데모는 소켓 콜 대신 블록킹으로 작업을 처리해야만 하는 서버를 시뮬레이션할 것입니다. 많은 서버들이 이러한 것을 요구하죠. 데이터베이스 콜, 외부 루틴 콜, 또는 연산을 필요로 하는 많은 서버들은 이 콜들의 로직을 작게 쪼갤 수 없습니다-이 모든 콜들은 전부 외부의 DLL등에서 external로 사용되는 콜이거나, 아니면 콜 자체가 복잡할 경우에는 작게 쪼개는게 불가능하죠(※역자 주: 아예 새로운 글을 써버리는군요. -_-;). 데이터베이스 콜은 작은 콜들로 쪼개질 수 없으며, 개발자는 데이터베이스 콜이 리턴되기까지 무작정 기다려야만 합니다. 이런 것은 데이터베이스 콜에만 한정된 것은 아닙니다. 압축, 연산, 또는 다른 프로세스들이 이러한 범주에 들어갑니다.

데몬스트레이션의 편의를 위해, 우리가 사용할 서버는 SQL 명령을 수행하기 위해 5초의 시간이 걸린다고 상상합시다. 데모를 단순화하기 위해, 여기서는 Sleep(5000)명령어로 이 상황을 대체하겠습니다.

이미 앞서의 예제에서 많은 것을 다루었고, 따라서 거의 대부분을 이해할 수 있기 때문에 이 예제는 소스를 그다지 깊게 분석하지는 않겠습니다.

Source Code
unit main;

interface

uses
 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
 IdBaseComponent, IdComponent, IdTCPServer;

type
 TformMain = class(TForm)
   IdTCPServer1: TIdTCPServer;
   procedure IdTCPServer1Execute(AThread: TIdPeerThread);
 private
 public
 end;

var
 formMain: TformMain;

implementation
{R *.DFM}

procedure TformMain.IdTCPServer1Execute(AThread: TIdPeerThread);
var
 i: integer;
begin
 with AThread.Connection do begin
   WriteLn('Hello. DB Server ready.');
   i := StrToIntDef(ReadLn, 0);
   // Sleep is substituted for a long DB or other call
   Sleep(5000);
   WriteLn(IntToStr(i * 7));
   Disconnect; 
 end;
end;

end.
Execute 이벤트가 스레드 환경 안에서 발생하기 때문에, 프로세스 코드는 필요한만큼 시간을 소비할 수 있습니다. 각각의 클라이언트들은 각각 자신의 스레드를 가지고 있으며, 다른 클라이언트들을 막거나 하는 일은 벌어지지 않습니다.

-=-=-=-=-=-=-=-=-=-=-
테스팅
-=-=-=-=-=-=-=-=-=-=-
데이터베이스 서버를 테스팅해보시려면, 컴파일 후 실행하십시오. 6001번 포트로 텔넷 접속을 시도하십시오. 서버가 환영 메시지를 내보낼 것입니다. 숫자 한 개를 집어넣으시면 서버는 여러분의 요청을 "수행하고" 5초 후 그 답을 보낼 것입니다.

-=-=-=-=-=-=-=-=-=-=-
다른 자원들 (알파벳 순)
-=-=-=-=-=-=-=-=-=-=-
볼랜드 뉴스그룹 - forums.borland.com의 borland.public.delphi.internet.winsock과 borland.public.cbuilder.internet
RFC - 
http://www.rfc-editor.org/ 
Indy - 
http://www.nevrona.com/Indy/ 

-=-=-=-=-=-=-=-=-=-=-
저자에 대하여
-=-=-=-=-=-=-=-=-=-=-
Chad Z. Hower (aka Kudzu - 
Kudzu@pbe.com) - "프로그래밍은 저항과 싸우는 예술이다" - 
Chad는 Nevrona Designs에서 근무하며, 인디 프로젝트의 진행자 중 한 명입니다(
http://www.nevrona.com/Indy/). 과거에 Chad는 취업, 보안, 화학, 에너지, 유무선통신, 그리고 보험업계에서 근무한 경력을 갖고 있습니다. 그의 장기는 TCP/IP 네트워킹, 프로그래밍, 인터프로세스 커뮤니케이션, 웹 출판, 분산 컴퓨팅, 인터넷 프로토콜, 그리고 객채지향 프로그래밍입니다. 시간이 남을 때 Chad는 프로그래밍, 사이클, 카약(※역자 주: 원본에는 이 부분에 kayak(flat and whitewater)라고 써있는데, 이게 뭐죠?), 하이킹, 다운힐 스키, 드라이빙, 그리고 기타 여러가지 실외활동을 즐깁니다. Chad에겐 델파이라 이름지은 자녀는 없지만, 그의 테네시 주 운전면허판(※역자 주: license plate? -_-;)에는 "Delphi2"와 "CPPBldr"라 이름지어진 그의 자동차가 등록되어 있습니다. 또한 그는 http://www.hower.org/Kudzu/에 여러 글이나 프로그램, 유틸리티 등의 것들을 올려놓고 있습니다.

-=-=-=-=-=-=-=-=-=-=-
역자에 대하여(이건 원본에 없습니다! -_-;)
-=-=-=-=-=-=-=-=-=-=-
Robert Teminian, the Blue Phoenix:
-Yet, the travel cannot be done.
본명은 오현세. 이 글을 번역한 2003년부로 만 24세가 된 예비역 대학생입니다. 경영학을 전공하고 있으며, 델파이로 프로그래밍 한답시고 떠들고 다니지만 실상은 폼 위에 VCL 몇 개 얹어놓을줄밖에 모릅니다. 처음에 인디를 거부했던 이유는 단지 스레드를 어떻게 생성하는지 알지 못해서였다죠(-_-;). 그전에 쓰던 Delphi 5 시절의 TSocket 시리즈는 non-Blocking 모드로 놓고 이벤트 하나에다가 이것저것 전부 쑤셔넣으면 어떻게든 작동은 됐거든요(뭐, 가끔 Connection reset by peer란 메시지가 뜨긴 했지만..... -_-;).
델파이와는 군시절에 만났고, 이후 델파이의 맹신자가 되어 항상 "델파이 만세"와 "볼랜드 만세"를 외치는 극우파 추종자입니다. 코엑스몰에만 가면 오락실에서 몸집에 걸맞지 않는 날렵함으로 EZ2Dancer를 하는게 취미 중 하나이며, 아울러 헛소리와 나서기를 장기로 가지고 있습니다.
그럴 가치도 없기에 설마 그러시진 않으시겠지만, 이 글의 번역이 너무 XYZ해서 마음속 분노를 도대체 주체하지 못하시는 분들께서는 ilily@chollian.net으로 항의메일을 주시거나 
http://user.chollian.net/~ilily에 있는 방명록에다가 화풀이를 하시면 됩니다.

번역자 주소 : 
http://hompy.us/club/post.com?url=http%3A%2F%2Fblog.naver.com%2Ffangel3%2F50025697675


출처: http://jsigoogle.tistory.com/entry/델파이-Indy를-이용한-네트워크-프로그래밍 [글모음]



프로시저 결과를 화면에 출력하고 싶을때 사용


SQL> show serveroutput

serveroutput OFF

SQL> set serveroutput on


----테스트

SQL> begin

  2   dbms_output.put_line('확인');

  3  end;

  4  /

확인   >  출력되면 성공


PL/SQL procedure successfully completed.

 초보의 SQL 기초 - Oracle 접속


오라클 같은 DB는 별도의 서버에 설치하고 다른 서버나 PC에서 telnet이나 SSH로 접속하는 것이 일반적인 경우겠지만, 실습을 위해서 클라이언트 툴(토드, 오렌지 등) 없이 도스 프롬프트에서 sqlplus를 실행시키는 방법에 대해 알아본다.



Sqlplus로 접속하기


sqlplus를 통해 오라클에 로그인하기 위한 명령어는 아래와 같다. 


> sqlplus  [계정명]/[계정 패스워드][@<connect_identifier>] | 

           /  [AS {SYSDBA | SYSOPER | SYSASM}] 

( [ ] 부분 생략 가능 )

ex) sqlplus id/pw@ip:port/sid


- 원격접속이 아니고 오라클이 설치된 PC에서 직접 접속할 경우 @connect_identifier 생략가능 (ex1)

- sqlplus 계정명까지만 입력하면 패스워드 입력화면으로 넘어간다. (ex2)

- 또는 프로그램 실행시듯 sqlplus만 입력하고 다음 화면에서 계정명과 패스워드를 입력해도 된다. (ex3)

- 패스워드에 특수문자가 있을 경우 따옴표(" ")로 묶어주어야 한다.

- 오라클 설치할 때 변경하지 않았다면 초기 비밀번호는 다음과 같다. 

    system / manager

    sys / change_on_install

    scott/tiger


- scott 계정이 잠겨있는 경우라면 아래 Lock 해제 부분 참고


ex1) sqlplus scott/tiger




ex2) sqlplus scott




ex3) sqlplus





sys계정 접속하기


오라클에서는 관리자 계정으로 sys와 system 계정이 있다. 

sys 계정이 일단 두목쯤 된다고 보고, system은 관리자 계정이라고 해두자. 

사용자 계정을 추가하거나 database를 새로 만들 때는 sys 계정으로 접속해야 한다. 

(정확한 의미에서는 계정이라기보다는 권한의 문제겠지만 이건 나중에...)


접속방법은 계정명 뒤에 /as sysdba를 붙인다.

- /as sysdba를 붙일 때 OS의 시스템 계정 권한으로 접속할 경우 별도의 패스워드 없이 접속 가능하다

(보안 때문에 시스템 계정 권한으로 변경하길 권장하는데 이것도 나중에 정리...)


sysdba로 접속하는 방법은 아래 방법 중 편리한 방법을 사용하면 된다. 


> sqlplus

 사용자명 입력 : /as sysdba



> sqlplus "/as sysdba"





connect 또는 conn 명령


SQL> connect 계정명 / 패스워드

- connect(또는 conn)sqlplus로 오라클 DB에 접속한 상태에서 계정을 변경하는 명령어.

- show user 명령어로 현재 접속한 계정명 확인 가능

- 다음 명령어로 sqlprompt 프롬프트 변경 가능

SQL> set sqlprompt "_USER > "





scott 계정 활성화


- 만약 오라클 설치 직후라면 실습용 계정인 scott은 잠겨있는 상태(Lock)이다. 확인해 보고  락을 해제한다. 

(sqlplus 접속 방법과 sysdba접속에 대한 내용은 조금 뒤에)


> sqlplus "as /sysdba"

SQL> SELECT username, account_status FROM dba_users WHERE username='SCOTT';

SQL> ALTER user scott account unlock;


- scott계정의 디폴트 패스워드는 tiger이지만 다른 패스워드로 변경하고 싶다면 아래 명령어를 실행한다. 

패스워드에 특수문자가 있는 경우는 반드시 큰 따옴표(" ")로 묶어주어야 한다. 


SQL> ALTER user scott IDENTIFIED BY "pw!!"



출처: http://ttend.tistory.com/607 [toward the end · · ·]

오라클 리스너 확인하기


1. 리스너 구동하기

lsnrctl start  : 리스너 구동하기
lsnrctl stop  : 리스너 중단하기 

이 명령어는 오라클이 설치되어 있는 서버에서 실행해야 한다. 당연 oracle 유저로 실행해야 먹을 것이다. 윈도우 커맨드모드에서 실행하는 client 용 명령어는 아니다.

2. 리스너 환경 파일

$ORACLE_HOME/network/admin  아래에 있는 listener.ora 

3. tnsping 사용하여 외부에서 리스너 동작 확인하기 

사용법 : tnsping TNS명

C:\Documents and Settings\SHCHOI>  tnsping DBDCBS1

TNS Ping Utility for 32-bit Windows: Version 10.2.0.1.0 - Production on 28-8월 -2008 12:25:17

Copyright (c) 1997, 2005, Oracle.  All rights reserved.

사용된 매개변수 파일:
C:\oracle\product\10.2.0\client_1\network\admin\sqlnet.ora

별칭 분석을 위해 TNSNAMES 어댑터 사용
Attempting to contact (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = 111.60.1.61)(PORT = 1543))) (CONNECT_DATA = (SERVICE_NAME = DBDCBS1)))
확인(10밀리초)

여기서 사용한 DBDCBS1 는 tns명으로서 client pc의 $ORACLE_HOME/network/admin   아래에 있는 tnsname.ora 파일 속에 정의되어 있다. oracle 10g 의 경우 보통 C:\oracle\product\10.2.0\client_1\NETWORK\ADMIN  아래에 있다.

DBDCBS1 = 
  (DESCRIPTION = 
    (ADDRESS_LIST = 
      (ADDRESS = (PROTOCOL = TCP)(HOST = 111.60.1.61)(PORT = 1543))
    )
    (CONNECT_DATA = 
      (SERVICE_NAME = DBDCBS1)
    )
  )

위와 같은 파일을 직접 울트라에디터 같은 것으로 열어서 편집을 할 수도 있으나 주의할 점이 있다. tnsname명 앞에 스페이스 같은 문자가 끼어들어가면 안된다. 즉  "DBDCBS1=" 이라고 써야 되는데 실수로  " DBCBS1=" 이라고 D자 앞에 스페이스나 탭문자가 들어가면 제대로 인식되지 못 한다는 말이다.

사용자 삽입 이미지

오렌지나 토드 같은 SQL 쿼리 툴이 있다면 위의 그림과 같이 쉽게 tnsname.ora 파일을 편집할 수 있다. 

사용자 삽입 이미지

그러나 위의 그림과 같이 오라클에서 기본 제공하는 tnsname설정하는 용도의 Net Configuration Assistant 프로그램이 있지만 사용하기가 약간 어렵워 초보자에게 혼란만 주게 된다.

C:\Documents and Settings\SHCHOI> tnsping DBDCBS1

TNS Ping Utility for 32-bit Windows: Version 10.2.0.1.0 - Production on 28-8월 -2008 12:34:39

Copyright (c) 1997, 2005, Oracle.  All rights reserved.

사용된 매개변수 파일:
C:\oracle\product\10.2.0\client_1\network\admin\sqlnet.ora

별칭 분석을 위해 TNSNAMES 어댑터 사용
Attempting to contact (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = 111.60.1.61)(PORT = 154))) (CONNECT_DATA = (SERVICE_NAME = DBDCBS1)))
TNS-12541: TNS:리스너가 없습니다.

해당 지정된 포트에 리스너가 떠 있지 않을 경우 위와 같은 메시지를 볼 수 있다. 


3. telnet 사용하여 외부에서 리스너 동작 확인하기  

사용법 : telnet  IP 포트번호

이 때 방화벽은 뚫려 있으나 해당 포트에 리스너가 떠 있지 않을 경우에는 "연결하지 못했습니다."  라는 메시지가 바로 나오고 방화벽으로 막혀 있다면 20여초 가량 지체되다가 연결하지 못 했다는 메시지가 나오게 된다. 

사용자 삽입 이미지


이것은 tcp/ip 프로토콜의 특성상 해당 destination 장비로 연결이 되지 못 했을 경우 연결(패킷 전송)을 여러번 재시도를 하기 때문이다.
만약 지정된 포트에 정상적으로 리스너가 떠 있고 방화벽이 뚫려있다면 다음과 같은 black 화면이 뜰 것이다. 이것은 해당 포트에 떠 있는 daemon 프로세서에 접속이 되어 다음 전문을 기다리고 있는 상태이다. 

사용자 삽입 이미지


이렇게 연결된 상황을 탈출하고자 한다면 CTRL+] 문자를 입력한다. (Ctrl을 누른 상태에서 ] 를 친다) 그런 후에 quit 를 입력하면 다시 커맨드 모드로 빠져나올 수 있다.

사용자 삽입 이미지




출처: http://pangate.com/138 [호주 멜번스토리]

Story 24-1 패턴과 아키텍처는 잘 구성되어 있는가?

1. 패턴과 아키텍처는 잘 구성되어 있는가?

너무 많은 패턴을 적용하면 유지보수성이 떨어지고, 문제가 발생했을 때 추적하기가 어려워진다.
좋은 패턴이라고 무작정 사용하기 보다는 꼭 필요한 패턴만을 사용해야 한다.

2. 데이터를 리턴할 때 TO (혹은 VO) 패턴을 사용하였는가? 
    아니면 Collectioon 관련 클래스를 사용 하였는가?


데이터를 주고 받는 시간을 적약하기 위해서 TO 패턴을 일반적으로 사용한다.
그리고 때에 따라서는 Collection 관련 클래스를 사용하기도 한다.
이러한 패턴을 적용하지 않거나 관련 표준을 정하지 않고 개발을 할 경우 시스템의 응답시간도 
영향이 있겠지만, 유지보수성이 떨어진다. 게다가 HashMap 으로 데이터를 주고 받으면, 
소스를 완전히 뜯어 보지 않는 이상 개발자만 어떤 키와 값이 들어 있는지 알게 된다.

3. 서비스 로케이터 (Service Locator) 패턴은 적용 되었는가?

서비스 로케이터 패턴을 사용하면 어플리케이션에서 필요한 대상을 찾는 룩업(Lookup) 작업을 할 때 소요되는 대기 시간을 줄일 수 있다. 만약 서버의 CPU 사용량이 높지 않을 때 응답 속도가 느리다면, 서비스 로케이터를 적용해야 하는 부분이 있지 않은가 한번쯤 확인을 해 봐야 한다.



Story 24-2 기본적인 어플리케이션 코딩은 잘 되어 있는가?

1. 명명규칙은 잘 지켰는가? 

옛날 코딩방식처럼 AC0018 같은 클래스로 되어 있다면, 장애가 발생했을 때 정확히 인지하기 힘들어 진다.
시스템의 장애가 발생하면 클래스명과 업무명이 매핑되어 있는 문서를 일일이 보면서 확인 할 시간이 없다.
문제에 더 빨리 접근 할 수 있도록, 제발 자바의 기본 명명규칙을 따라주기 바란다.

2. 필요한 부분에 예외처리는 되어 있는가?

예외 처리를 제대로 하지 않으면 사용자는 아무런 응답을 받지 못하고, 
여러분이 운영하는 시스템을 더이상 사용하지 않을 수도 있다. 
문제가 발생했을 때 원인을 밝히기 위해서 예외 처리는 필수다.

3. 예외 화면은 지정되어 있는가?

예외 화면에 대한 표준이 있는지 확인해 보아야 한다.
만약 예외 화면을 구성하지 않고 지정하지도 않는다면, 사용자는 여러분이 사용하는 서버의 종류가 어떤 것인지 알게 될 것이다.
때에 따라서는 시스템에 어떤 클래스가 있는지도 확인할 수 있을 것이다.

4. 예외 정보를 혹시 e.printStackTrace() 로만 처리하고 있지 않은가?

e.printStackTrace() 메소드를 호출하면 서버에서 스택 정보를 취합하여야 하리 때문에
서버에 많은 부하가 발생하게 된다. 만약 여러분의 어플리케이션이 복잡하게 구성되어 있다면,
최대 100개의 스택 정보가 프린트 되기 위해서 서버에 부하를 줄 것이다.

5. system.gc() 메소드가 소스에 포함되어 있지 않은가?

만약 아무리 많은 요청이 들어오더라도 힙 메모리의 사용량이 전혀 늘어나지 않고 일직선을 유지하고 있다면,
System.gc() 메소드가 소스 안에 있는지 확인해 보기 바란다.
그리고 이 메소드를 웹 어플리케이션에서 사용하면 WAS 의 CPU 사용량은 상상도 못할 만큼 급격히 증가한다.

6. System.exit() 메소드가 소스에 포함되어 있지 않은가?

이 메소드가 수행되면 WAS 의 프로세스가 죽는다.
스레드가 죽는 것이 아니라 프로세스가 죽는 것이다.

7. 문자열을 계속 더하도록 코딩하지 않았는가?

String 클래스는 더할 경우 새로운 객체를 생성한다.
한두개를 더하는 것은 큰 문제가 안되겠지만, 
몇십 페이지 정도 되는 쿼리를 더하기로 처리할 경우 서버는 GC 를 하느라 바빠질 것이다. 

JDK 5.0 이상을 사용하면 자동으로 StringBuilder 클래스로 변환을 해 주기는 한다.
하지만, 루프를 수행하면서 문자열을 더할 경우에는 컴파일러도 어쩔 수가 없으니 반드시 필요에 따라서 StringBuffer 나 StringBuilder 클래스를 선택하여 사용하기 바란다.

8. StringBuffer 나 StringBuilder 클래스도 제대로 사용 했는가?

StringBuffer 나 StringBuilder 클래스 안에서 문자열을 더하면,
이 두개 클래스를 쓸 필요하 전혀 없다.
즉, StringBuffer의 객체가 sb 라고 했을때 sb.append("aaa"+"bbb"); 는 제대로 이해를 하지 못하고 사용 하는 것이다.

9. 무한루프가 작동할 만한 코드는 없는가?

for 루프는 일반적으로 반복 횟수를 지정하고 사용하기 때문에 큰 문제가 발생하지 않지만,
while(true) 의 구문을 사용한다면, 해당 루프는 언제 끝날지 아무도 보장 할 수 없다.
"해당 구문 안에서 조건을 만족시키지 못하고 무한 루프를 돌게 된다면" 이라는 가정을 할 필요가 없도록 코딩하자.

10. static 을 남발하지 않았는가?

메모리를 아낀다고 static 을 남발하다가는 시스템이 심각한 오류를 발생시킬 수 있다.
초급개발자라면 반드시 같이 일하는 중급이상의 경험이 많은 개발자나 
PL 에게 해당 static 의 사용이 적절한지 확인 받기를 권장한다.

11. 필요한 부분에 synchronized 블록을 사용하였는가?

그냥 동시에 해당 메소드가 호출 될 것 같아서 synchronized 블록을 사용한 것은 아닌지 확인해야 한다.
필요 없는 부분에서 synchronized 블록을 사용하게 될 경우 성능저하를 발생시킬 수 있다.

12. IO 가 계속 발생하도록 개발되어 있지 않은가?

가장 많이 실수하는 부분이 설정파일을 매번 파일에서 읽도록 개발하는 것이다.
한두명의 사용자가 사용하는 시스템이라면 상관 없을 수도 있겠지만,
100명 이상의 사용자가 사용하는 시스템에서 해당 설정파일을 매번 읽기 위해 IO 를 발생 시키면서
시스템의 아까운 자원을 낭비하지 말자.

13. 필요 없는 로그는 다 제거 했는가?

프린트하지도 않을 로그 데이터를 문자열로 만들기 위해서 아까운 메모리를 사용하고,
그 메모리를 청소하기 위해서 GC 를 해야 하는 불쌍한 JVM 을 생각해 보라.

14. 디버그용 System.out.println 은 다 제거 했는가?

로그 레벨을 바꾼다고 해도 지워지지 않는 것이 System.out.println 로그이다.
본인이 만든 어플리케이션이 조금이라도 빨라지기를 원한다면 이 디버그용 로그도 제발 지워주기 바란다.



Story 24-3  웹 관련 코딩은 잘 되어 있는가?

1. JSP 의 include 는 동적으로 했는가? 아니면 정적으로 했는가?

JSP 를 동적으로 include 하면 서버에도 부하를 줄 뿐만 아니라 응답 시간에도 영향을 준다.
꼭 필요한 부분에만 동적 include 를 하기 바라며, 그렇지 않은 경우에는 정적 include 의 사용을 권장한다.
그렇다고 무조건 정적으로 include 를 하면 변수를 공유하게 되어 예기치 못한 문제가 발생 할 수 있으므로 유의해서 사용하기 바란다.

2. 자바 빈즈를 너무 많이 사용하지 않았나?

하나의 화면에서 자바 빈즈를 수십 개씩 사용하지 않는가?
하나의 TO (Transfer Object) 를 사용해서 처리할 수도 있는 데이터라면, 여러개의 자바 빈즈를 사용해서 응답시간에 영향을 주는 일이 없도록 하자.

3. 태그 라이브러리는 적절하게 사용했나?

많은 양의 데이터를 태그 라이브러리로 처리할 경우에는 성능에 많은 영향을 끼친다.
데이터의 양이 적을 경우에는 상관이 없겠지만 무조건 태그 라이브러리를 사용한다면,
10,000 건이 넘는 여러분의 데이터 처리는 대부분 JSP 에서 소요될 것이다.

4. EJB 를 적절하게 사용 하였나?

EJB 하나 하나는 일반 클래스보다 많은 메모리를 점유해야 하며, 서버를 기동할 때에도 많은 시간이 소요된다.
반드시 필요한 경우에만 EJB 를 사용하자.

5. 이미지 서버를 사용할 수 있는 환경인가?

이미지, 자바스크립스 , css, 플래시 등 정적인 컨텐츠가 많고 사용자의 요청이 많은 경우에는
웹 서버 이외의 이미지 서버를 사용할 수 있다.
정적인 컨텐츠만을 제공하는 서버로서 , 웹 서버에서 파일을 읽어서 처리하는 것보다 효율적이다.

6. 사용중인 프레임웍은 검증되었는가?

검증되지 않은 프레임웍에서 많은 문제가 발생 할 수 있다.
가장 좋은 방법으로는 검증된 프레임웍을 사용하는 것이지만, 
만약 새로 개발된 프레임웍을 사용할 경우 에는 성능 테스트 및 메모리 프로파일링을 통해서 해당 프레임웍의 
안정성을 확인해 놓아야 한다.



Story 24-4  DB 관련 코딩은 잘 되어 있는가?

1. 적절한 JDBC 드라이버를 사용하였는가?

현재 DB 에 맞는 버전의 JDBC 를 사용하고 있는가?
간혹 버그가 있는 JDBC 를 사용하여 성능이 나오지 않는 경우가 발생한다.
되도록이면 가장 최신의 WAS 및 DB 벤더에서 추천하는 문제 없는 JDBC 를 사용하기를 권장한다.

2. DB Connection, Statement, ResultSet 은 잘 닫았는가?

"알아서 잘 닫히겠지" 하는 생각은 버려라.
반드시 finally 구문을 사용해서 Connection, Statement, ResultSet 을 명시적으로 닫아 주기 바란다.
그렇지 않으면 사용 가능한 연결이 부족해져 여러분들이 개발한 시스템이 사용 불가능한 상태가 되는 것은 시간 문제다.

3. DB Connection Pool 은 잘 사용하고 있는가?

프로젝트의 규모가 크면 클 수록 표준을 따르지 않을 확률도 커진다.
DB 연결 부분을 개발자의 역량에 맡기면 여러 가지 형태로 DB 에 연결하게 될 것이다.
관련되는 표준을 반드시 정하고,
DB Connection Pool 은 반드시 사용해야 DB 의 리소스를 보다 효율적으로 사용할 수 있다.


4. 자동 커밋 모드에 대한 고려는 했는가?

기본 커밋 모드는 자동커밋으로 되어 있다.
하지만 조회성 프로그램도 자동커밋 여부를 지정하게 되면, 약간의 응답시간 저하가
발생하게 된다. 반드시 필요하지 않은 경우에는 자동 커밋을 하도록 하자.

5. ResultSet.las() 메소드를 사용하였는가?

전체 건수를 처리하기 위해서 last() 메소드를 사용했는지 확인해 봐야 한다.
데이터의 건수가 많을수록 응답시간이 느려진다.
쿼리를 두번 수행하더라도 전체 건수를 가져오기 위해서 last() 메소드의 사용을 자제해 주기 바란다.


6. PreparedStatements 를 사용하였는가?

Statement 를 사용하면 매번 쿼리를 수행할 때마다 SQL 쿼리를 컴파일 하게 된다.
이작업은 DB 에 많은 부하를 준다.
그러므로 쿼리 문장이 계속 동적으로 변경되어야 하는 경우를 제외한 대부분의 경우에는
PreparedStatements 를 사용 하기를 바란다.




Story 24-5 서버의 설정은 잘 되어 있는가?

1. 자바 VM 관련 옵션들은 제대로 설정되어 있는가?

64 비트 기반의 시스템을 사용하면서 -d64 옵션을 사용하지 않는다면, 그냥 32 비트로 시스템이 운영된다.

클래스 패스는 순차적으로 인식된다.
여러 JAR 파일 중 만약 같은 패키지명에 같은 클래스가 존재할 경우에는 앞에 명시한 클래스 패스에 우선권이 있다.

2. 메모리는 몇 MB 로 설정해 놓았는가?

가끔 서버의 메모리 설정을 하지 않고, 성능이 나오지 않는다고 이야기하는 사이트가 있다.
설정하지 않으면 서버는 기본 64MB 로 시작한다는 사실을 반드시 기억하길 바란다.

3. GC 설정은 어떻게 되어 있는가?

혹시 -client 로 설정되어 있는지 확인해 보자.
GC 방식에 따라서 서버의 성능이 엄청난 차이가 발생할 수 있다.
가능하면 성능 테스트를 통해서 우리의 시스템과 서버에 맞는 GC 옵션을 설정하자.

4. 서버가 운영모드인지 개발모드인지 확인하였는가?

서버가 개발모드라면 WAS 는 주기적으로 변경된 클래스가 있는지 확인할 것이다.
이 작업은 서버에 많은 부하를 주게 된다.
서버가 주기적으로 느려진다면,  이 부분은 반드시 확인하자.

5. WAS 의 인스턴스가 몇 개 기동되고 있는가?

WAS 에서 하나의 인스턴스당 적어도 한 개는 있어야 제대로 된 운영이 가능하다.
CPU 는 4장인데, 인스턴스가 30개 정도 있는건 아닌지 확인하기 바란다.
자바는 코볼이나 포트란이 아니다.

WAS 의 CPU 개수가 적을 경우, 인스턴스 개수를 증가시키면 서버의 처리량이 증가될 수 있다.
이 인스턴스의 개수에 대해서는 정답이 없다.
그러므로 서버 및 어플리케이션의 상황에 맞게 성능 테스트를 통해서 시스템의 적절한 인스턴스 개수를 도출해야 된다.

6. JSP Precompile 옵션은 지정해 놓았는가?

서버를 기동할 때 JSP 를 미리 컴파일 하도록 해 높으면 사용자는 JSP 가 수정이 되었는지 여부와 상관없이 일정한 응답속도를 느낄 것 이다. 하지만 이 옵션을 지정해 놓으면 서버가 기동될 때 JSP 를 컴파일 하기 위한 시간도 많이 소요되기 때문에 상황에 맞게 옵션을 지정해야 한다.

7. DB Connection Pool 개수와 스래드 개수는 적절한가?

스래드 개수가 DB Connection Pool 의 수보다 절대로 적어서는 안된다.
스래드의 개수는 DB Connection Pool 의 개수보다 보통 10 개 정도 더 많이 설정한다.
만약 DB Connection Pool 의 설정을 서버 기본설정으로 해 놓고 운영을 한다면,
서버의 리소스도 별로 사용하지 않고 10명 이상의 동시 사용자를 처리하지 못할 것이다.
대부분의 WAS 의 DB Connection Pool 초기값을 10~ 20 개 이다.

8. 세션타임아웃 시간은 적절한가?

세션을 더이상 사용하지 않을 때 세션 정보는 삭제되어야 한다.

만약 세선타임아웃 시간을 하루 이상의 큰 값으로 지정하고, 해당 서버를 재시작하지 않으면
해당서버는 메모리 릭이 발생하여 더이상 사용할 수 없는 상황이 될 것이다.

9. 검색 서버가 있다면, 검색 서버에 대한 설정 및 성능 테스트를 하였는가?

일반적으로 검색 서버에서 주기적으로 데이터를 모으기 위해서 서버의 리소스를 많이 사용하게 된다.
만약 검색 서버의 세팅을 점검하지 않고,
성능 테스트를 하지 않는다면, 예기치 못한 부분에서 시스템의 리소스를 점유하고 있을지도 모른다.





출처: http://joke00.tistory.com/148 [Smile virus]

MapTool-callstack.zip

---------------------------------------------------------------------------------
★ 꼭 알아야할사항

- 일반적으로 API를 사용하시던 분들은 이개념만 이해해도 금방 쓰실수있을듯 ^^

DC TImage.Canvas.Handle

Bitmap = TBitmap
TBitmap TImage.Picture.Graphic

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

 

자세한 내용은 소스코드를 참고하십시오.

(소스코드는 간단하게 예제삼아 만든 맵툴(MapTool)입니다. Bitmap을 하나 열어서 사각형 타일로 잘라 또하나의 Bitmap 파일을 생성하는 프로그램입니다. 허접하지만 참고만 하세용.)

 

다음은 그림판을 만들기 위해 필요한 가장많이 쓰는 TImage의 속성과 함수들은 정리한것이다.

 

[TImage의 주요속성과 함수들]

 

1. 이미지컴포넌트가 그림의 크기에 맞춤
TImage.Picture.Autosize := Boolean

 

2. 그림을 이미지 컴포넌트 크기에 맞춤

TImage.Picture.Stretch := Boolean

 

3. 클립핑영역 처리로 이영역밖에 그려지는 영역은 출력되어지지 않으며 화면에도 표시되지 않는다.

TImage.Canvas.ClipRect := TRect 

4. 해당좌표에 점을찍는다.
TImage.Canvas.Pixels(x,y) 

 

5. 펜의 현재위치를 나타냄
TImage.Canvas.PenPos

 

6. 캔퍼스에서 다른켄퍼스로 이미지가 복사될때 복사되는 방법을 정의

TImage.Canvas.CopyMode

 

7. TImage에 TBitmap 출력하기

TImage.Picture.Graphic := TBitmap

 

8. TImage.Canvas에 펜모드 설정

TImage.Canvas.Pen.Mode := TPenMode
TImage.Canvas.Pen.Width := 굵기
(펜종류는 도움말 참조)

 

9. TImage.Canvas.Canvas의 도형 메소드들

1) 원호 : TImage.Canvas.Arc
2) 원호의 시작과 끝점을 연결 : TImage.Canvas.Chord
3) 파이 : TImage.Canvas.Pie
4) 원 : TImage.Canvas.Ellipse(X1,Y1,X2,Y2 : Integer);
5) 사각형 : TImage.Canvas.Rectangle(X1,Y1,X2,Y2 : Integer);
6) 타원사각형 : TImage.Canvas.RoundRect(X1,Y1,X2,Y2,X3,Y3 : Integer); // X3,Y3가 둥근정도를 의미
7) 선 : TImage.Canvas.LineTo(X,Y : Integer);
8) 점의위치이동 : TImage.Canvas.MoveTo(X,Y : Integer);
9) 테두리선만있는 사각형 : FrameRect(Const Rect : TRect);
10) XOR연산을 이용하여 포커스를 가진듯한 사각형을 그림 : DrawRect(Const Rect : TRect);

 

10. TImage.Canvas에 브러쉬 설정

TImage.Canvas.Brush := TBrush : 브러쉬 설정
TImage.Canvas.Brush.Color := TBrush.Color : 브러쉬 색설정

 

11. 채색 하기

TImage.Canvas.FloodFill(X, Y, TColor, TFillStyle)
(TColor, TFillStyle의 종류는 도움말 참조)

 

12. TImage.Canvas에 Bitmap 파일 열기

TImage.Picture.LoadFromFile(파일명)

 

13. TImage.Canvas에 Bitmap 파일 저장

TImage.Picture.SaveToFile(파일명)

 

14. 다른 켄버스의 사각영역을 복사한다.

Source는 원본 이미지 Dest는 복사한 이미지가 출력될영역이다.
CopyRect(var Dest: TRect, Canvas: TCanvas, var Source: TRect);

 

15. 그래픽을 출력한다.[BMP,ICO.EMF.WMF]

TImage.Canvas.Draw(X, Y, TGraphic);

 

16. 원하는 좌표에 문자열 출력

TImage.Canvas.TextOut(X, Y, String);

 

17. 사각영역을 정해서 꽉차게 출력

StretchDraw(Const Rect; Graphic : TGraphic); // 확대축소가능

18. TImage 내용 삭제

Image1.Picture.Assign(nil)


GROUP BY

 

Gravity DBA 이승연

 

Group by 는 특정열이나 특정 열을 연산한 결과를 집계 키로 정의 하여 그 집계 키의 Unique 값에 따라 그룹을 짓는 연산자라고 정의 할 수가 있다. 사용방법은 select 절의 가장 마지막 라인에 Group by 라는 절을 쓰고 컬럼 및 연산식을 지정하면 된다. 하나 이상이면 ,(콤마)를 이용하여 구분 할수도 있다. 앞서 이야기 했듯이 집계 키로 정의 된 특정열이나 특정 열을 연산한 결과 값이나 동일한 집계 키 즉, 동일한 값이면 오직 한번(Unique)만 나타난다.

 

Group by와 동일한 기능을 가진 연산자는 Distinct라는 연산자가 있다. Group by Distinct연산이 동일하다고 생각하는 사람이 있을 듯 하다. 하지만, 그것은 Distinct Group by를 잘못 이해를 하고 있는 것이다. Distinct Group는 다른 연산이다. Distinct는 단순히 unique값만을 추출하기 위해 사용하는 것이고, Group by는 집계 키 기준으로 집합 연산을 위해 사용하는 것이다. 집합 연산을 간략하게 짚고 넘어가면 우리가 흔히 잘 알고 잘 사용하고 있는 집합 연산자는 Count(*), SUM(), AVG(), MAX(), MIN()등을 말하는 것이다.

 

그럼 예제로 Distinct Group by를 비교하여 보자.

USE Northwind

GO

SELECT DISTINCT customerID, employeeid FROM Northwind..orders

SELECT DISTINCT customerID, employeeid, count(*) FROM Northwind..orders

SELECT customerID, count(*) FROM Northwind..orders GROUP BY customerID

 

첫번째 T-SQL 문장에서는 customerID employeeid가 중복제거가 되여 출력이 된다. , group by customerId, employeeid 한것과 동일한 결과값을 같는다. 여기에서 employeeid 빼고 T-SQL을 돌리면 customerID만 중복제거 되어 출력이 된다.

두번째 T-SQL 문장은 error가 출력이 된다.

메시지8120, 수준16, 상태1, 1

'Northwind..orders.CustomerID'() 집계함수나GROUP BY 절에없으므로SELECT 목록에서사용할수없습니다.

앞서 이야기 했듯이 Distinct Group by의 가장 큰 차이점은 집계 함수를 쓸수 있느냐? 없느냐? 이다. 집계 함수의 count(*)를 쓰려면 마지막 세번째 T-SQL문처럼 Group by를 써야 한다.

 

이번에는 Group by를 사용할 때 정렬은 어떻게 이루어지는지 알아보자. 오라클에서 Group by를 사용하면 Group by절에 명시된 컬럼의 기준으로 정렬이 이루어진다고 한다. SQL 7.0 이하 버전에서도 오라클과 같이 Group by의 명시된 컬럼의 순서대로 정렬이 되었다고 한다. 하지만 SQL 7.0이후 부터는 Group by를 할 때 비용을 따져서 정렬하여 그룹핑을 하는 경우도 있고, Hash Match를 통해서 정렬이 이루어지지 않을 수도 있다. 따라서 Group by를 할 때 반드시 Order by는 되지 않으므로 명시적으로 정렬을 원한다면 Order by를 사용하여야 하고 Order by를 사용 하므로써 추가 비용이 발생을 하게 된다.

 

SELECT를 할 때 특정 값을 얻기 위해서는 WHERE절을 쓸 것이다. Group by에서도 집계된 값에서 특정 값을 얻기 위해서 HAVING절이라는 것을 사용할수가있다. SELECT에서 WHERE절은 길을 찾는 Path의 역할을 수행한다고 하면, Group by에서 Having절은 전체의 큰 덩어리를 들고서 마음에 드는 것만을 골라내는 것과 같다고 볼수 있다. 따라서 WHERE절에서 우리가 고민하였던 Index Query성능을 좋게 하기위해서 고민했던것들을 Having절에서는 의미가 없을수도 있다. 아니 의미가 없다. Having절 사용법은 아래와 같다.

 

SELECT country, sum(money)

FROM #orders

GROUP BY country HAVING sum(money) > 2000

 

임시테이블 #orders 의 컬럼 country를 집계 키로 하여 Group by를 한 후 money컬럼의 합계가 2000이상인 Row만 추출하는 T-SQL문이다. 여기서 출력되는 총합을 내림차순으로 정렬을 하고 싶으면 맨 마지막절에 Order by sum(money) DESC를 하면 출력되는 값이 내림차순으로 정렬 되여 질 것이다. 참고로 Order by를 사용할 때 DESC(내림차순)또는 ASC(오름차순)을 명시적으로 지정을 하지 않았을 경우에는 ASC(오름차순)으로 정렬이 되어진다.

 

 

CUBE

 

Data cube라는 말은 OLTP(Online transaction processing)환경보다는 OLAP(Online analytical processing)환경에서 많이 쓰이는 말이다. OLAP에서 cube의 정의는 다음과 같다. “CUBE N개의 축으로 만들어지는 다면체이다.” 그럼, OLTP에서의 cube는 어떤 정의 일까? OLTP cube의 정의는 다음과 같이 말할수 있다. “Group by 집계 키 값에 대한 모든 가능한 조합을 row로 정리하는 연산이다.” 그리고, cube 연산을 하다 보면 NULL값을 보게 되는데, 이는 SELECT절에 포함되여진 NULL일수도 있고, cube연산중에 생긴 NULL일수도 있다.

 

잘 이해가 되지 않을 것 이다. 예를들어 집합 {(a,b),(a,c),(a,d),(b,d),(b,a),(c,b)}가 있다고 가정을 하고 a b를 집계 키로 하고 cube연산을 하면 (a,NULL) = {(a,b),(a,c),(a,d)}이고 (NULL,b) = {(a.b),(c,b)} 이다. 그럼 (NULL,NULL)은 무엇이냐? (NULL,NULL)은 전체 집합이다. 아래의 예제를 보면서 이야기를 하여 보자.

 

CREATE TABLE #나라(

               나라    varchar(10)

        ,       도시    varchar(10)

        ,       인구수  smallint

);

 

INSERT INTO #나라 VALUES(N'한국', N'서울', 100)

INSERT INTO #나라 VALUES(N'한국', N'대전', 120)

INSERT INTO #나라 VALUES(N'한국', N'대구', 130)

INSERT INTO #나라 VALUES(N'미국', N'오스틴', 1000)

INSERT INTO #나라 VALUES(N'미국', N'워싱턴', 1240)

INSERT INTO #나라 VALUES(N'미국', N'찰스턴', 740)

INSERT INTO #나라 VALUES(N'인도', N'뭄바이', 440)

INSERT INTO #나라 VALUES(N'인도', N'코친', 230)

INSERT INTO #나라 VALUES(N'인도', N'방갈로르', 440)

 

select 나라,도시,sum(인구수) from #나라 group by 나라,도시 with cube

 

위와 같은 예제를 실행을 시키면, 아래와 같이 출력이 될 것 이다.

 

나라           도시           (열 이름 없음)

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

한국           대구           130

NULL         대구           130

한국           대전           120

NULL         대전           120

인도           뭄바이        440

NULL         뭄바이        440

인도           방갈로르    440

NULL         방갈로르    440

한국           서울           100

NULL         서울           100

미국           오스틴        1000

NULL         오스틴        1000

미국           워싱턴        1240

NULL         워싱턴        1240

미국           찰스턴        740

NULL         찰스턴        740

인도           코친           230

NULL         코친           230

NULL         NULL         4440

미국           NULL         2980

인도           NULL         1110

한국           NULL         350

 

이제 cube가 어떻게 동작을 하는지 조금 감이 잡혔으리라 생각한다. 그럼 이제 우리는 여기서 Grouping함수를 사용하여 앞서 말한 저 NULL값을 컨트롤을 할 수가 있다. Grouping함수를 사용하여 값이 1이면 cube rollup연산중에 생긴 NULL값이고, 0이면 원래NULL값인 것이다. 자 이것도 잘 이해가 되지 않을것같다. 아래 예제를 통하여 이해해 보자.

 

select  (case when grouping(나라) = 1 then N'합계' else 나라 end) as '나라'

,       (case when grouping(도시) = 1 then N'합계' else 도시 end) as '도시'

,       sum(인구수) as '인구수'

from #나라 group by 나라,도시 with cube

 

위와 같이 case문을 사용하여 grouping한 결과가 1이면 cube rollup연산중에 발생한 값이니 합계를 찍어라. 라는 의미이다. 실제 결과 값을 보면 아래와 같다.

 

나라           도시           인구수

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

한국           대구           130

합계           대구           130

한국           대전           120

합계           대전           120

인도           뭄바이        440

합계           뭄바이        440

인도           방갈로르    440

합계           방갈로르    440

한국           서울           100

합계           서울           100

미국           오스틴        1000

합계           오스틴        1000

미국           워싱턴        1240

합계           워싱턴        1240

미국           찰스턴        740

합계           찰스턴        740

인도           코친           230

합계           코친           230

합계           합계           4440

미국           합계           2980

인도           합계           1110

한국           합계           350

 

ROLLUP

 

Rollup을 살펴보자. Cube group by의 집계 키를 구성하는 모든 값의 조합이였으면, Rollup group by의 앞의 값에 따른 하위 값을 기준으로 값을 조합하는 것이다. 이것도 이렇게 이론적으로 이야기 하면 우리는 잘 모르겠다. 아래 예제를 보면서 이야기 해보자. 우리는 아까 cube를 하면서 생성한 #나라를 계속 사용하는 것이다. 혹시나 지웠다면 다시 생성하기 바란다.

 

select 나라,도시,sum(인구수) from #나라 group by 나라,도시 with rollup

 

Cube와 사용법은 별로 다른 것이 없어 보인다. 하지만 결과 값을 보면 cube rollup의 차이점을 알수가 있다.

결과값은 아래와 같다.

 

 

나라           도시           (열 이름 없음)

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

미국           오스틴        1000

미국           워싱턴        1240

미국           찰스턴        740

미국           NULL         2980

인도           뭄바이        440

인도           방갈로르    440

인도           코친           230

인도           NULL         1110

한국           대구           130

한국           대전           120

한국           서울           100

한국           NULL         350

NULL         NULL         4440

 

Cube와 다르다. 무언가가 다르다. 무엇이 다른것일까? 앞에 이야기 했듯이 group by의 앞에 값에 따른 하위값을 조합한다고 하였다. 여기서 group by의 앞의 값이라는 것은 나라column이다. 나라column으로 도시를 조합을 한것이다. 실행 계획을 보아도 cube rollup의 차이점은 확연히 틀리다. Cube는 모든 조합에 대하여 연산을 하기 때문에 집계 키 즉, 차원이 2개이면 실행 계획은 차원은 2개로 나뉘고, 차원이 3개이면 실행 계획의 차원도 3개로 나뉜다. 이 부분은 직접 확인 해 보기 바란다.

 

또한, cube에서 나왔던 grouping함수를 사용하여 연산도중에 발생한 NULL값을 rollup에서도 cube와 동일하게 처리를 할 수가 있다. 다시 한번 이야기 하면 grouping함수를 사용하여 그 값이 1이면 cube rollup의 연산과정에서 발생한 NULL이고, 0이면 원래 NULL값인것이다. 그럼 grouping 함수를 사용하여 NULL 합계로 바꾸어 보자.

 

select  (case when grouping(나라) = 1 then N'합계' else 나라 end) as '나라'

,       (case when grouping(도시) = 1 then N'합계' else 도시 end) as '도시'

,       sum(인구수)

from #나라 group by 나라,도시 with rollup

 

이것도 cube와 다를것이 없다. 결과값은 아래와 같다.

 

나라           도시           (열 이름 없음)

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

미국           오스틴        1000

미국           워싱턴        1240

미국           찰스턴        740

미국           합계           2980

인도           뭄바이        440

인도           방갈로르    440

인도           코친           230

인도           합계           1110

한국           대구           130

한국           대전           120

한국           서울           100

한국           합계           350

합계           합계           4440

 

마지막으로 rollup에서 보면 집계 키별로 중간합계가 나오고 전체에 대한 합계도 나오고 있다. 여기에서 중간합계를 뺀 전체 합계만을 표현하려면 어떻게 해야 할까? 결과값은 아래와 같이 추출되어야 한다.

 

나라           도시           (열 이름 없음)

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

미국           오스틴        1000

미국           워싱턴        1240

미국           찰스턴        740

인도           뭄바이        440

인도           방갈로르    440

인도           코친           230

한국           대구           130

한국           대전           120

한국           서울           100

합계           합계           4440

 

Having절과 grouping()함수를 활용하면 위와 같이 출력할 수가 있다. 여러분이 꼭 한번씩 해보길 바라겠다.

출처: http://gdbt.tistory.com/13 [Gravity DB Team]

오라클 클라이언트(WIN)문제 [ ORA-12154: TNS:could not resolve the connect identifier specified ]

오라클 10g설치 후, 클라이언트를 설치 후 서버로 접속하면, 이런 메시지를 출력하고 접속이 안될 경우가 있다.

ORA-12154: TNS:could not resolve the connect identifier specified

TNS위치 설정을 제대로 해 주었고, 값이 정상일 경우에도 이런 메시지가 나와서 고생을 많이 했는데, 오라클 포럼에서 답을 찾을 수 있었다.

결론은 어떠한 이유에서인지 환경변수가 필요한데 등록이 안되어 있는 것이었고, 수동적으로 환경변수를 등록하면 된다.

내컴퓨터->속성->시스템등록정보->고급->환경변수->시스템 변수 란을 확인하고, 없다면...

새로만들기,

변수이름 : ORACLE_HOME
변수 값   : E:\oracle\product\10.2.0\client_1

Windows 98의 경우 C:\autoexec.bat파일에 다음 내용 추가
• set JDKHOME=c:\jdk1.3.1;
• set PATH=c:\jdk1.3.1\bin;C:\windows\command;
• set CLASSPATH=.;C:\JavaProject

• set ORACLE_HOME=.;D:\orawin95\bin;

( 실제 설치된 경로는 E:\oracle\product\10.2.0\client_1\network\admin\sqlnet.ora 입니다 )

경우에 따라서 재부팅이 필요할 수 있다.


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


Error while trying to retrieve text for error ORA-12154

얼마전 개발서버 DB 셋팅을 하던중 ORA-12154 에러가 발생하여 온갖 삽질을 다했다 -ㅅ-

원래 이 에러는 client 에서 DB 접근시 tnsnames.ora 에 db_alias 나 tnsnames.ora 설정이

잘못되어 있을 경우 나타난다

하지만!!! DB 셋팅 한두번 해보나 -ㅅ-)!!! 그런 초보적인 실수를 할리가 !!!

그래서 책자 찾아 보고 oracle 에 문의 해본 결과

SQLNET 설정에서 문제가 있을 수도 있다는 결론 도출!!!

(문제는  방화벽이었음.. orz... 당연히 방화벽이 막혀 있으니 alias 를 제대로 해도 못읽지..)

그래서 해결책 !!!

SQLNET.ORA 파일에서 해당 접속 IP 의 접근을 허용하여 주면된다!! (물론 방화벽 제거는 기본)


# tcp.invited_nodes=(DB로컬 아이피와 접속을 허용할 ip)
tcp.invited_nodes=(192.167.3.224)
# tcp.excluded_nodes=(접속을 차단할 ip)


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


sqlnet.ora 파일에 SQLNET.AUTHENTICATION_SERVICES= (NTS) 이부분을 주석처리.. 하란다.


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



(출처 : https://m.blog.naver.com/PostView.nhn?blogId=whiteme7&logNo=110151847636&proxyReferer=https%3A%2F%2Fwww.google.co.kr%2F)

'IT > ORACLE' 카테고리의 다른 글

[Oracle]오라클 DBMS_output 사용  (0) 2017.11.09
[]오라클 설치후 sqlplus 접속  (0) 2017.11.08
[ORACLE]오라클 리스너 확인하기  (0) 2017.11.08

[Delphi|델파이]콤보박스 스타일속성


style

style은 콤보박스의 형식을 나타내는데 다음과 같은 5가지의 선택이 가능하다.


 - csDropDown : 모론 항육의 높이가 일정하며 항목에 글의 입력이 가능하다. 콤보박스 우축의 버튼을 클릭하여 다른 항목을 선택할 수 있다.


 - csSimple: 항목에 글을 입력하는 것이 가능하지만 콤보박스 우축에 클릭하는 버튼이 사라지고 화살표 상하키로만 다른 항목을 선택합 수 있다.


 - csDropDownlist: 글을 입력하는 것이 불가능하고 다만 콤보박스 우측의 버튼을 클릭하여 다른 항목을 선택할 수 있다.


 - csOwnerDrawFixed: csDropDown과 동일한 기능이면서 콤보박스에 각 항목의 높이가 itemHeight에 따라 일정한 높이를 갖는다.


 - csOwnerDrawVariable: csDropDown과 동일한 기능이면서 콤보박스에 각 항목의 높이가 각각의 항목에 따라 다른 높이를 가질 수 있다.



( 출처 : http://www.delphipages.com/forum/showthread.php?t=87286 )

함수명만 알 경우 어떤 컴포넌트에 이벤트가 걸려있는지 확인 하는방법


1. 폼파일(.dfm) 열기 후

2. 함수명 검색


깔끔


(출처 : https://www.delmadang.com/
 [질문]함수명만 알 경우 어떤 컴포넌트에 이벤트가 걸려있는지...)

+ Recent posts