본 글은 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를-이용한-네트워크-프로그래밍 [글모음]
'IT > Delphi/C#' 카테고리의 다른 글
[Delphi|델파이] 콤보박스 스타일속성 (0) | 2017.09.26 |
---|---|
[Delphi|델파이] 함수명으로 컴포넌트 이벤트찾기 (0) | 2017.09.26 |
[Delphi | 델파이] 프로시저와 함수 (0) | 2017.09.13 |
[Delphi | 델파이] 델파이에서 사용되는 파일 확장자명 (0) | 2017.09.13 |
[Delphi | 델파이] 유닛의 정의 (0) | 2017.09.13 |