DEV-Language2009. 2. 7. 03:02

[ 출처 ] :
http://www.imaso.co.kr/?doc=bbs/gnuboard_pdf.php&bo_table=article&page=3&wr_id=796&publishdate=20030601

 
  조회 : 854  
입사한 지 갓 한 달이 된 S씨에게 회사에서 첫 번째 프로젝트를 맡겼다. 일명, 네트워크 로깅 서비스. 설명을 들어보니 네트워크 장비들이 서로 데이터를 주고받는 상황을 파일로 기록하여 로그를 남기는 서비스다. 신참 프로그래머인 S씨의 실력이 어느 정도인지 회사에서 테스트해 보려는 의도도 엿보였다. S씨는 패기 있는 신입 사원으로서 당당하게 프로젝트를 해낼 수 있다는 자신감을 보여줬다.

S씨의 ‘삽질’
네트워크 프로그래밍 경험이 있는 S씨가 봤을 때 이 프로젝트는 그야말로 ‘껌’이었다. 결과를 빨리 보여줘서 회사로부터 능력을 인정받고 싶어 한 S씨는 즉시 일에 착수했다. 먼저 소켓 API를 이용해 간단한 통신 프로그램을 만들어 보기로 했다.
그런데 생각만큼 쉽지가 않았다. 자바와 같이 소켓 클래스가 잘 만들어져 있는 언어는 네트워크 프로그램을 비교적 쉽게 만들 수 있었지만, 이 프로젝트는 C++를 사용해야 한다는 단서가 붙었다. S씨는 운영체제가 제공하는 API 함수들을 이용하기로 마음먹고 이리 저리 책도 뒤지고 도움말도 보면서 간단한 예제를 만들었다. 하지만 컴파일을 시켜보자 무수한 에러가 나는 것이 아닌가. S씨는 투덜거리면서 에러를 하나씩 수정하기 시작했다.
에러를 모두 수정한 S씨는 기분이 덜 풀렸는지 미간을 찌푸린 채 만든 예제를 실행했다. 그런데 이번에는 제대로 작동하지 않는 것이다. 뭘 잘못 했는지 소스를 꼼꼼히 훑어본 S씨는 얼마 후 실수들을 발견했다. C 소켓 API들을 직접 사용하다 보니 함수들을 호출하는 절차를 깜박 잊고 중간에 들어가는 함수 호출을 빼먹었거나, 인자로 잘못된 플래그를 대입한 것이었다. S씨는 또 투덜거린다. ‘이런 실수를 컴파일러가 잡아주면 얼마나 좋아. 간단한 예제 하나 짜는데 무슨 에러가 이렇게 많이 나는지…, 운영체제가 제공하는 API는 사용하기가 너무 까다롭군.’
소스를 모두 수정하고 예제를 실행해 봤더니 잘 됐다. 그제야 S씨는 빙긋 웃었다. S씨는 ‘머리가 나쁘면 손과 발이 고생한다’는 옛 성현들의 말씀을 떠올리고 잘 작동하는 이 소켓 코드를 편리하게 재사용하기 위해 소켓 클래스로 만들었다. 자바에서 제공하는 소켓 클래스만큼 잘 설계하진 않았지만 그래도 클래스로 만들어 놓으니 여러 군데서 편리하게 사용할 수 있을 것 같았다.
그런데, 어느 정도 작동하는 소켓 클래스 하나 만드는 데 생각보다 시간이 많이 걸렸다는 것을 깨달은 S씨는 다급해지기 시작했다. 프로토콜도 아직 설계되지 않았으며, 네트워크 로깅 서비스는 동시에 여러 로그를 한꺼번에 처리해야 하기 때문에 로그를 기록하는 코드를 멀티 쓰레드로 처리해야 할 것 같았다. 그렇게 하자니 옛 성현의 가르침에 따라 쓰레드 클래스도 만들어야 하고 파일 처리, 쓰레드 동기화, 이벤트 처리 구조, 속도와 성능, 응답성 등 다시 생각해보니 이만 저만 복잡한 것이 아니었다.
S씨는 생각했다. ‘견고하고 성능 좋은 서버를 만들기 위한 고민을 누군가가 이미 다 했을 텐데, 내가 소켓 클래스를 만든 것처럼 회사 선배들이 잘 만들어 놓은 라이브러리가 있다면 그걸 재사용하는 것이 훨씬 더 낫지 않을까?’
이 일화에서 만나본 S씨처럼 실제로 현업에서 많은 프로그래머들도 이와 같은 고민을 한다. 만일 S씨가 프로그래밍을 시작하기 전에 먼저 잘 만들어져 있는 모듈이 있는지 찾아보고 이를 사용했다면 어떻게 됐을까. 간단한 소켓 클래스와 소켓을 사용한 예제 프로그램을 만들기 위해 사용하기 까다로운 함수들을 머리 싸매고 공부할 필요가 없었을 것이고, 선배 프로그래머들이 겪었던 컴파일, 런타임 에러를 잡기 위해 얼굴을 찌푸릴 필요도 없을 것이며, 시스템의 기본 밑바탕을 만들기 위해 투자한 정력과 시간을 시스템의 핵심 코드를 작성하는 데 사용할 수 있었을 것이다. 간단히 말해 쓸데없이 ‘삽질’하는 시간은 없었을 것이다.

품질과 개발 속도를 동시에 만족시킬 수 있는 프레임워크
품질과 개발 속도라는 두 마리 토끼를 잡기 위해 재사용하기 쉽게 설계된 소프트웨어 부품, 이는 유사 이래 지금까지 소프트웨어 업계에서 가장 인기 있는 주제다. 그렇다면, 만일 소프트웨어 공학자들이 헛된 시간을 보낸 것이 아니라면 지금쯤은 빠르고 견고하고 확장성이 좋은 서버를 만들기 위해 재사용할 수 있는 소프트웨어 부품을 연구하고 개발해 놓지 않았을까?
이제 프레임워크에 대해 이야기할 시간이 온 것 같다. GoF의 한 사람으로 유명한 랄프 존슨은 프레임워크에 대해 다음과 같이 정의했다. “프레임워크는 재사용할 수 있고 ‘덜 완성된’ 애플리케이션이며, 이를 다른 애플리케이션을 만드는 데 사용할 수 있다.” 여러 가지 의미 있는 단어들이 나열되어 있지만 여기서 프레임워크를 다른 재사용 방법들과 구분하는 중요한 단어는 바로 ‘덜 완성된 애플리케이션’이다. 이것은 무슨 뜻일까? 프레임워크의 세 가지 특징에 대해 살펴보면 이 수수께끼를 푸는 데 좀더 근접할 수 있을 것이다.

◆ 프레임워크는 특정 애플리케이션을 개발하는 데 적합한 기능과 구조를 통합된 세트로 제공한다. 가령, 네트워크 애플리케이션을 제작하기 위한 프레임워크에는 네트워크 I/O 이벤트 처리, 메시지 버퍼링과 큐잉, 다중 쓰레드 및 커넥션 관리와 같이 네트워크 애플리케이션을 개발하는 개발자들이 고민하는 기능들이 들어 있다. 즉, 프레임워크는 이미 설계와 개발 단계에서부터 특정 애플리케이션 분야를 목표로 제작됐다.
◆ 프레임워크의 작동 방식을 살펴보자. 프레임워크는 사용자로부터 관심 있어 하는 이벤트에 대해 콜백 객체를 등록 받는다. 그리고 프레임워크는 스스로 작동하다가 어떤 이벤트가 일어났을 때 내부의 실행 흐름이 콜백 객체의 내용을 수행한다. 이해를 돕기 위해 자바의 GUI 프레임워크인 스윙을 예로 들어보자. 스윙을 이용해 GUI 애플리케이션을 개발하려는 개발자는 JButtion 객체를 하나 만들어서 패널에 삽입한 후, 그 버튼이 눌려졌을 때 어떤 동작을 할 것인지 지정하기 위해 리스너라는 콜백 객체를 등록하게 된다. 개발자는 이런 식으로 GUI 컴포넌트를 생성해 이벤트가 발생했을 때 어떻게 처리할 것인지 방법을 담은 콜백 객체를 그 컴포넌트에 등록한다. 여기서 프로그래머가 해야 할 일은 이것뿐이다. 이제 다 완성된 GUI 애플리케이션을 실행해 애플리케이션의 사용자가 마우스 커서를 버튼에 대고 클릭했다고 가정하자. 그러면, GUI 애플리케이션 내에 들어 있는 스윙 프레임워크가 클릭 이벤트를 감지하고 해당 버튼에 등록되어 있는 리스너의 내용을 실행한다. 이 예에서 알 수 있듯이 프레임워크는 내부에 독립적인 실행 흐름이 있어서 스스로 작동하고 있다가 사용자가 관심 있어 하는 이벤트가 발생했을 때 그 사용자가 등록한 콜백 객체를 호출하는 능동적인 기능을 가진 부품인 셈이다. 바로 이 기저가 콜백 객체를 사용하는 프레임워크의 핵심이며 이를 ‘inversion of control’이라 부른다. inversion of control은 ‘실행 흐름의 역전’이라고 직역할 수 있다. 부연하자면, 이벤트가 발생했을 경우 이를 처리하는 실행 흐름(쓰레드)은 애플리케이션의 메인 쓰레드가 아닌 프레임워크를 작동시키는 쓰레드라는 의미다. 즉, 애플리케이션 입장에서 보면 평상시에는 프로그램이 실행되는 주 흐름이 애플리케이션이 생성한 쓰레드들인데, 이벤트가 발생되면 이런 ‘주 흐름이 역전’되어 프레임워크의 쓰레드가 된다는 의미다.
◆ 그렇다면 프레임워크를 ‘덜 완성된 애플리케이션’이라고 부르는 이유는 뭘까. 그것은 바로 프레임워크는 개발자가 콜백 객체만 등록하면 완성된 애플리케이션으로서 작동할 수 있는 준비를 이미 다 갖추고 있기 때문이다. ‘완성된 애플리케이션’으로 다시 태어난 프레임워크는 다른 애플리케이션을 작성하는 데 부품으로 사용되고 그렇게 해서 만들어진 애플리케이션이 작동하게 되면 그 안에 존재하는 프레임워크는 스스로 작동하면서 발생하는 이벤트에 대해 적절한 처리를 하게 된다.

‘덜 완성된 애플리케이션’을 완성하는 방법은 두 가지가 있다. 하나는 앞의 예와 같이 프레임워크가 제공하는 인터페이스를 가지고 콜백 객체를 만들어 등록하는 방법과 프레임워크 내에 존재하는 클래스를 상속을 받아 적절한 메쏘드를 오버라이드하는 것이다. 프레임워크의 특성마다 서로 다른 방법을 사용하겠지만 그 목표는 매한가지다. ‘덜 완성된’ 부분에 사용자의 의도가 담긴 코드를 투명하게 삽입하는 것이다.
잘 만들어진 프레임워크는 그 특정 애플리케이션 분야에서 필요한 클래스들의 역할·관계·구조·기능들을 잘 정의해 놓고 있기 때문에 애플리케이션 개발에 프레임워크를 사용하면 개발자는 재사용하는 코드를 늘리고 재작성하는 코드를 줄임으로써 개발 속도를 크게 향상시킬 수 있다. 또한 ‘선배’들이 같은 문제에 대해 먼저 연구 개발한 성과물을 이용하는 것이며 동시에 이러한 프레임워크들은 여러 프로젝트에서 이미 그 유용성이 검증됐으므로 프레임워크를 사용한다면 소프트웨어 품질도 보장할 수 있게 된다.
이러한 프레임워크를 구축해서 소프트웨어 개발에 먼저 사용한 분야는 다름 아닌 그래픽 사용자 인터페이스(GUI) 분야이며, 가장 널리 알려진 GUI 프레임워크는 다름 아닌 마이크로소프트의 MFC다. 이렇게 복잡하고 까다로운 개발 분야인 GUI 애플리케이션 분야에서 프레임워크가 성공적으로 사용되면서 생산성과 품질이 크게 향상되자 애플리케이션 개발자들은 좀더 복잡한 애플리케이션 분야에서도 프레임워크를 연구, 개발하기 시작했고 역시 가장 많은 개발 프로젝트가 진행되는 비즈니스 애플리케이션 분야가 활기를 띄었다. 그 결과로 ORB(Object Request Broker) 프레임워크가 개발됐다. ORB 프레임워크는 네트워크의 원격지에 있는 객체를 마치 로컬 객체처럼 다룰 수 있게 한다. 그 때까지만 해도 분산 애플리케이션을 개발하는 일은 지겹고 에러도 많이 발생하고 이식성도 보장받지 못했으나 ORB 프레임워크가 등장하면서 빠르고 견고하면서 소켓 C API, POSIX 쓰레드와 같이 저수준 개념들과 씨름할 필요 없이 분산 애플리케이션을 개발할 수 있게 됐다. 조금 과장된 표현을 써서 네트워크 개념을 모르는 개발자라도 얼마든지 좋은 품질의 네트워크 애플리케이션들을 개발할 수 있는 세상이 온 것이다. 대표적인 ORB 프레임워크는 CORBA(Common Object Request Broker Architecture), DCOM, 자바 RMI가 있다.
그렇다면 여기서 한 가지 의문이 생긴다. 도대체 프레임워크는 일반 클래스 라이브러리나 컴포넌트, 미들웨어와 같이 잘 알려져 있는 재사용 기술들과 무슨 차이가 있을까?

클래스 라이브러리와 프레임워크
클래스 라이브러리는 프레임워크와는 다르게 특정 애플리케이션 분야를 위해 제작된 것이 아니다. 이들은 비교적 보편적인 기능과 구조를 제공하는 경우가 많다. 우리가 잘 아는 클래스 라이브러리로는 C++ 표준 라이브러리나 표준 템플릿 라이브러리(STL)를 들 수 있다. 이 라이브러리들은 잘 설계되어 있으며 뛰어난 재사용성을 가지고 있으며 어떠한 애플리케이션 분야든 사용할 수 있다.
주의 깊은 독자는 S씨가 만든 소켓 클래스 라이브러리는 네트워크 애플리케이션 개발에 사용되므로 이 전제에 맞지 않는다고 지적할 것이다. 그렇지만 여전히 프레임워크와 클래스 라이브러리는 결정적인 차이점이 있다. 클래스 라이브러리의 클래스들은 스스로 작동하지 않는다는 것이다. 단지 개발자가 애플리케이션을 작성하면서 필요할 때 이런 라이브러리를 호출해 사용할 뿐이다. String, Vector, Hashtable과 같은 클래스들을 생각해 보면 이해할 수 있다. 게다가 String과 같은 클래스들은 상대적으로 낮은 수준의 재사용성을 가진다. 따라서 개발자는 애플리케이션을 만들 때 이러한 라이브러리들의 기능을 조합하는 코드를 작성해야 한다.
이렇게 클래스나 함수들을 조합하는 코드를 ‘풀 코드(glue code)’라고 부르는데, 이러한 코드들은 재사용되지 못하고 애플리케이션을 개발할 때마다 다시 재작성해야 한다. 반면에 프레임워크는 특정 애플리케이션 분야에 특화된 기능과 구조를 미리 제공하고 있기 때문에 그 아키텍처를 재사용할 수 있다. 그리고 독자적인 흐름을 가지고 스스로 작동하기 때문에 사용자에게는 단지 콜백 객체만을 요구한다. 따라서 우리가 작성해야 하는 ‘풀 코드’는 단지 콜백 객체를 프레임워크에 등록하는 것뿐이다.
클래스 라이브러리와 프레임워크의 차이점을 좀더 알기 쉽게 설명하기 위해 비유를 들어보자. 클래스 라이브러리를 이용해 프로그래밍을 하는 것은 마치 제주도 여행을 가기 위해 직접 항공사에 가서 비행기 티켓을 사고 호텔에 전화를 걸어 방을 예약하고 렌터카 회사에서 차를 빌리며 주변에 제주도를 다녀온 사람에게 관광 코스를 물어 봐서 여행 계획을 세우는 것과 같다. 반면, 프레임워크 방식은 제주도 여행 패키지를 제공하는 전문 여행사에 찾아가서 전문 직원과 상담하면서 그 곳에서 여행 준비를 단 번에 끝내는 것이다. 공항의 예처럼 여행사 비유는 프레임워크의 세 가지 특성을 모두 만족하고 있다.
클래스 라이브러리와 프레임워크는 개발자 입장에서 분명히 서로 구분되는 특징을 가지고 있지만 한 애플리케이션 내에서 이들 둘은 서로 공존하고 있다. 프레임워크 내부에서, 그리고 사용자가 등록하는 콜백 객체에서 클래스 라이브러리들을 사용하기도 한다. 마치 여행사 직원이 손님을 대신해 직접 비행기와 호텔 방을 예약하는 것처럼 말이다.

컴포넌트와 프레임워크
컴포넌트와는 어떤 차이점이 있을까. 컴포넌트는 시스템을 구현하는 데 필요한 특정 서비스들을 담고 있으며 하나 혹은 그 이상의 인터페이스를 외부에 공개하여 외부에서는 이를 통해서만 접근해 사용할 수 있도록 제한한다. 시스템을 구축하는 데 있어 이러한 컴포넌트들은 마치 집을 짓는 벽돌과 같은 기능을 하며 컴포넌트가 공개하는 인터페이스에 대한 사용법만 잘 알고 있다면 다른 시스템에서도 쉽게 재사용할 수 있다는 장점이 있다. 대표적인 컴포넌트로는 COM, 닷넷 웹서비스, 엔터프라이즈 자바 빈즈(EJB), CORBA 컴포넌트 모델, 액티브X 컨트롤이 있다.
프레임워크와 컴포넌트는 상호 보완적이다. 복잡한 기능을 가진 컴포넌트를 제작할 때 내부에 프레임워크를 사용해서 구현할 수 있으며, 또한 콜백 객체를 컴포넌트로 제작해서 프레임워크에 등록해 사용할 수 있다. 그리고 네트워크 애플리케이션 프레임워크는 컴포넌트를 담는 컨테이너를 제작하는 데 사용할 수 있다. J2EE 컨테이너가 이러한 경우에 해당한다.

미들웨어와 프레임워크
미들웨어에 대해 들어 본 독자나 사용해 본 개발자라면 아마도 프레임워크와 미들웨어가 어떤 차이가 있는지 궁금해 할 것이다. 아마도 미들웨어와 프레임워크라는 두 개념이 서로 충돌하는 면이 있기 때문일 텐데, 간단히 설명하자면 미들웨어는 추상적인 개념일 뿐이다.
미들웨어는 사용자 애플리케이션과 OS 사이에서 서로 간에 다리(bridge) 개념, 혹은 레이어 개념을 가진 프로그램을 지칭한다. OS 입장에서는 미들웨어를 통해 사용자에게 좀더 수준 높은 서비스를 제공할 수 있으며, 사용자 애플리케이션은 미들웨어를 통해 OS와 OS가 제공하는 서비스에 대해 상세한 것까지 알 필요 없이 쉽게 애플리케이션을 작성할 수 있게 한다. 한때 유행했던 광고 카피를 패러디해 봤다. ‘복잡한 고민은 미들웨어에 맡기시고 당신은 비즈니스 로직에 투자하십시오.’
반면, 프레임워크는 재사용 기술의 한 종류이며 우리가 애플리케이션을 만드는 데 사용되는 부품이다. 실제로 미들웨어 제품의 내부를 들여다보면 여러 가지 재사용 기술이 사용되고 있는 것을 확인할 수 있으며 프레임워크도 그 중 하나다.
CORBA를 사용해 본 독자라면 CORBA의 예를 통해 이들 둘의 차이점을 쉽게 이해할 수 있을 것이다. 미들웨어인 CORBA는 ORB라는 프레임워크를 가지고 있는데, 이 ORB는 그 이름이 뜻하는 것처럼 개발자로부터 객체를 등록받아 이를 네트워크 상의 원격 객체로 작동하도록 한다. 앞서 예로 들었던 네트워크 로깅 서비스를 CORBA를 이용해 제작한다면 S씨는 장비가 전달한 로그 메시지를 파일에 저장하는 객체를 만들고 이를 ORB 프레임워크에 등록하면 된다(EJB 개발자는 컨테이너에 EJB 컴포넌트를 설치하는 것을 생각해도 좋다). 이렇게 해서 완성된 애플리케이션을 실행하면 애플리케이션 내부에서 ORB 프레임워크가 스스로 작동하다가 로그 메시지를 수신하게 되고 그리고 나서 S씨가 만든 객체를 호출해서 파일에 저장하는 코드를 실행한다.
여기서 CORBA는 OS 및 네트워크의 세부 기능과 동작을 개발자에게 감추고 자신이 모든 일을 대신 처리해 주는 아키텍처의 개념이기 때문에 미들웨어이며, ORB는 개발자로부터 비즈니스 로직이 담긴 객체를 등록을 받아 ‘완성된 애플리케이션’으로 작동하므로 프레임워크다.
그런데, CORBA, EJB, 닷넷과 같은 주요 미들웨어 제품은 강력하지만 모든 경우에서 위력을 발휘하는 만능은 아니다. 다음과 같은 두 가지 이유 때문이다.

◆ 주요 상업용 미들웨어 제품들은 공통적으로 부피가 크다. 일부 경험이 많은 프로그래머들은 이 미들웨어들이 필요 이상으로 너무 많은 기능을 가지고 있기 때문에 필요한 모듈을 직접 만들어 쓰는 것이 더 가볍고 효율적이며 빠르다고 주장한다. 충분히 일리가 있다. 간단한 채팅 프로그램을 짜고 싶어하는 프로그래머가 그런 고성능 미들웨어를 사용한다면 그야말로 ‘소 잡는 칼로 닭 잡는 격’이 된다. 낭비가 심하다는 이야기다.
◆ 이들 미들웨어 제품들은 특정 목적에 맞게 커스터마이즈하기가 힘들다. 이들 제품들은 사용자들에게 좀더 편리한 개발 환경을 제공하고자 추상화 수준을 높게 설정했는데, 그것이 오히려 다양하고 고급 기능을 요구하는 개발자들에게는 불편한 환경이 되어 버린 셈이다. 만일 개발자가 애플리케이션의 쓰레드 관리 및 커넥션 관리, I/O 이벤트 핸들링 정책을 직접 설정해서 자신만의 방법으로 작성하고 싶다면 어떻게 해야 할까. 개발자가 인터넷에서 구할 수 있는 공개된 CORBA 소스를 구해서 자신의 목적에 맞게 직접 수정해 쓴다고 생각해 보자. 필자는 그 개발자의 자신감을 높이 평가하고 싶지만 그 개발자의 매니저에게는 위로를 보내고 싶다. 이들 미들웨어들은 옛날부터 ‘black art’라고 불려 왔다. 이는 미들웨어 전문가나 아키텍트들이 미들웨어를 개발하고 설치하고 지원하는 데 필요한 기술들을 그들의 머리 속에다가 꽁꽁 숨겨 놓았다는 의미다. 따라서 소스 코드를 받아다가 나름대로 고쳐서 사용하기란 정말 힘든 일이며 오랜 시간이 걸릴 것이다. 그렇다면 어떻게 해야 할까. 처음부터 전부 다 만들어 써야 할까?

고품질 네트워크 애플리케이션 개발을 위한 대안, ACE
이 문제를 해결하기 위해 상대적으로 부피가 작고 추상화 수준이 낮은 미들웨어를 찾는 수밖에 없다. 이에 따라 필자는 ACE(The ADAPTIVE Communication Environment)를 추천한다. ACE는 자매품이자 ACE를 기반으로 제작된 실시간 CORBA 미들웨어인 TAO와 함께 네트워크 애플리케이션 개발 분야에서는 이미 수많은 프로젝트를 성공으로 이끌고 있는 미들웨어다.
차차 살펴보겠지만 ACE는 여러 레이어로 나누어져 있으며 개발자는 입맛에 맞는 추상화 수준에 따라 레이어들을 선택할 수 있다는 점에서 앞에서 제기한 문제에 대한 해결책을 제시한다. 게다가 유연성, 성능, 견고함, 플랫폼 이식성과 같은 특징들로 인해 개발자들이 높은 품질의 네트워크 애플리케이션을 쉽고 빠르게 개발할 수 있도록 돕는다. 또한 ACE는 소스와 관련 문서가 모두 공개되어 있기 때문에 언제든지 소스를 다운로드해 프로젝트에 사용할 수 있다. 이미 빌드되어 있는 제품과 사후 지원을 받기 위해선 ACE를 구입해야 한다.
<그림 2>는 ACE의 전체 아키텍처를 나타낸 모습이다. ACE는 크게 세 개의 레이어로 구성되어 있다. OS 어댑테이션 레이어와 C++ 랩퍼 퍼서드 레이어, 그리고 프레임워크 레이어다. 지금부터 각 레이어를 하나씩 살펴보자.

ACE OS 어댑테이션 레이어
OS 어댑테이션 레이어는 ACE_OS라는 이름으로 되어 있는 클래스에 약 500개의 C++ 정적 메쏘드로 구성되어 있다. 이 메쏘드들은 OS가 제공하는 네이티브 C 함수들, 플랫폼마다 다르게 작동하는 함수 또는 함수 이름은 다르지만 같은 기능을 수행하는 함수들에 대해 표준 구현을 제공하고 있다.
OS 어댑테이션 레이어는 플랫폼들마다 서로 다른 모습의 인터페이스를 통일해 주고 일정한 표준 구현을 제공하기 때문에 이를 이용해 작성된 상위 레이어들은 공통 인터페이스를 통해 OS 메커니즘을 사용하게 되며, 따라서 상위 레이어에는 플랫폼 종속적인 코드가 거의 없게 된다. 그러므로 ACE 미들웨어를 사용하여 제작된 애플리케이션을 다른 플랫폼에 이식할 때는 OS 어댑테이션 레이어만 새 플랫폼에 맞게 변경하면 된다. 즉, OS 어댑테이션 레이어는 이식성을 극대화하기 위해 존재하는 레이어라고 볼 수 있다(POSIX를 생각하면 쉽게 이해가 될 듯 하다).

ACE C++ 랩퍼 퍼서드 레이어
랩퍼 퍼서드는 비객체지향 함수와 데이터들을 객체지향 클래스와 인터페이스로 감싸는 패턴이다. ACE C++ 랩퍼 퍼서드 레이어는 하위 레이어인 OS 어댑테이션 레이어로 표준화된 정적 함수와 데이터들을 클래스로 포장한 레이어다. 이렇게 잘 디자인된 클래스들은 ACE 프레임워크를 제작하기 위한 기초 부품을 제공한다는 의미를 가지고 있을 뿐만 아니라 그 자체로도 활용도가 매우 높게 설계되어 있다. 게다가 랩퍼 퍼서드 레이어에서 제공하는 클래스들은 오버헤드를 최소화하도록 세심하게 디자인됐다. 따라서 많은 ACE 사용자들은 이 랩퍼 퍼서드 레이어에서 제공하는 여러 클래스를 직접 이용해 애플리케이션을 제작하기도 한다.

ACE 프레임워크 레이어
ACE에는 여섯 개의 프레임워크가 있다. 이들 프레임워크는 네트워크 애플리케이션과 서비스를 효율적이고 견고하면서 유연하게 개발할 수 있도록 지원한다. ACE의 프레임워크들은 총 다섯 개의 카테고리로 분류되는데, 이들을 간단히 살펴보면서 동시에 네트워크 애플리케이션을 개발하면서 고민하게 되는 여러 아키텍처, 디자인 문제들을 함께 알아보자.

이벤트 디멀티플렉싱과 디스패칭
네트워크 애플리케이션이란 외부 애플리케이션이나 장비와 통신하는 프로그램이다. 애플리케이션이 외부와 통하는 관문은 OS와 네트워크 프로토콜과 접해 있으면서 네트워크 이벤트와 외부에서 보내온 정보들을 수집하고, 또 외부에 전달할 정보를 내려 보내는 곳이 된다. 따라서 단독(stand-alone) 애플리케이션과는 다르게 성능과 확장성, 견고성의 초점이 바로 이 외부 통신 주체들과의 관문을 어떻게 설계하는지에 달려 있다. 쉽게 말해 통신 엔진을 잘 설계해야 빠르고 튼튼하고 확장 가능한 네트워크 애플리케이션이 되는 것이다.
이벤트 디멀티플렉싱이란 쉽게 말해 이 관문에서 애플리케이션이 관심 있는 정보나 이벤트들만 선별하는 작업이다. 좀더 자세히 설명하자면, 이벤트를 발생할 수 있는 소스들(소켓, 파일, 타이머, GUI 컴포넌트 등)을 한데 모아 놓고 감시하고 있다가, 이벤트가 발생했을 경우 해당 이벤트를 발생시킨 소스를 골라내는 일을 말한다. 그리고 이벤트 디스패칭은 이렇게 선별한 정보나 이벤트를 누구에게 처리하도록 맡길 것인지 결정하는 일이다. 그러므로 이벤트 디멀티플렉싱과 디스패칭을 어떻게 할 것인지 그 정책과 구조를 결정하는 일은 네트워크 애플리케이션 개발의 핵심 과정이라고 할 수 있으며 이에 대해 ACE에서는 Reactor와 Proactor 프레임워크를 제공하고 있다.
Reactor 프레임워크는 소켓이나 파일과 같은 이벤트 소스가 동기 I/O를 수행할 수 있는 상태가 되면 적절한 핸들러를 호출한다. 가령 서버 소켓(passive socket)으로 새로운 커넥션 요청이 들어왔다면, Reactor는 내부 쓰레드를 통해 새로 들어온 커넥션을 처리하는 핸들러를 호출해 커넥션을 처리한다.
클라이언트 소켓(active socket)과 함께 쓰기 이벤트에 대한 핸들러를 Reactor에 등록했다고 가정하자. 이 때 Reactor는 클라이언트 소켓에 쓰기 버퍼가 여유가 있는지를 검사하고 만일 쓰기 I/O를 수행할 수 있다면 등록된 핸들러를 실행한다.
Proactor 프레임워크는 하나 이상의 비동기 I/O가 초기화됐거나 또는 수행이 완료되어서 발생되는 이벤트에 대해 핸들러를 등록받아 처리한다. Proactor 프레임워크를 이용해서 비동기 I/O를 사용할 경우, 사용자 쓰레드가 I/O 작업을 직접 수행하지 않기 때문에 동시성 문제에 있어서 많은 이점을 누릴 수 있게 되어 성능 향상을 꾀할 수 있다.

커넥션 관리와 서비스 초기화
TCP/IP와 같은 커넥션 기반 프로토콜을 사용하는 서비스들을 커넥션 연결 측면에서 봤을 때 크게 서버와 클라이언트, 이 두 가지 기능으로 나누어진다. 서버는 TCP 포트를 지켜보면서 커넥션이 들어오기를 기다리며, 클라이언트는 능동적으로 서버에 커넥션을 연결하기를 요청한다.
최근에 유행하고 있는 피어 투 피어 애플리케이션의 경우 서버와 클라이언트의 구분이 없어졌다고 하지만, 각 애플리케이션이 클라이언트와 서버 기능을 동시에 수행하고 있을 뿐 이 이분법은 여전히 유효하다. 그런데 이러한 피어 투 피어 애플리케이션과 같이 서버와 클라이언트 기능을 동시에 수행해야 하는 애플리케이션들은 커넥션이 연결된 후 수행하는 코드와 새로 커넥션을 연결하는 일을 수행하는 코드들이 서로 섞여 있게 된다. 즉, 한 애플리케이션 코드에 커넥션과 통신 기능이 서로 구분되어 있지 않다. 따라서 이러한 경우 새로운 종류의 서비스나 통신 프로토콜을 추가하기가 쉽지 않기 때문에 이들을 서로 분리해야 할 필요가 있다.
이에 따라 ACE에서는 Acceptor-Connector 프레임워크를 제공하고 있다. Acceptor는 커넥션 연결을 수동적으로 기다리고 있으며, 새 커넥션 요청이 들어오면 이를 처리하기 위해 사용자가 등록한 핸들러를 호출한다. Connector는 상대방에 커넥션을 능동적으로 연결하며 연결됐을 경우 역시 마찬가지로 사용자가 등록한 핸들러를 수행한다. 언뜻 보기에 자바의 서버 소켓의 기능과 비슷하기도 하지만 이들은 앞에서 살펴본 Reactor 프레임워크와 연계하여 작동하면서 커넥션에 대한 작업이 완료됐을 경우 스스로 핸들러를 수행한다는 특징이 있다. 게다가 Proactor 프레임워크와 연계할 경우 비동기 커넥션 작업을 유용하게 사용할 수 있다.

동시성
수많은 사용자들이 서로 메시지를 주고받는 메시지 시스템은 사용자 PC에 설치된 메신저 프로그램과 중앙에서 메시지들을 서로 중계하는 서버로 구성되어 있다. 여기서 우리의 관심사인 메시지 서버는 동시에 수많은 메시지를 중계해야 하는 임무를 맡고 있다.
만일 메시지를 보내는 쪽이 받는 쪽보다 메시지를 전달하는 속도가 더 빠를 경우 서버는 메시지의 흐름을 조절해야 한다. 이 흐름을 조절하지 못하고 메시지가 중간에 유실될 경우 ‘자기야 끔찍’, ‘이 사랑’, ‘해’라고 도착한 메시지를 ‘자기야 끔찍해’로 오해하는 심각한 상황이 발생할 수 있다. 또한, 어느 한 사용자의 네트워크가 느려져서 메시지를 빠르게 전달할 수 없을 경우, 수많은 사용자가 메시지를 똑같이 늦게 받는 상황이 발생할 수 있다. 이렇듯 수많은 데이터를 동시에 처리해야 하는 서버를 개발해야 하는 경우 서비스의 품질(Quality Of Service)을 위해 동시성을 고려해야 한다. ACE Task 프레임워크를 통해 이러한 고민을 해결할 수 있다. Task 프레임워크에는 <표 4>와 같은 클래스들이 있다.
ACE_Task는 자바의 Runnable과 Thread 클래스를 연상하면 쉽게 이해할 수 있다. ACE_Task의 svc() 메쏘드는 Runnable의 run()에, activate() 메쏘드는 Thread의 start()에 대응된다. 따라서 사용자는 ACE_Task를 상속받아 svc() 메쏘드를 구현해 쓰레드로 실행할 서비스를 결정한다. 그리고 activate()를 호출해 발생된 쓰레드가 svc()의 내용을 실행하게 된다.
이 내용을 보면 자바 Runnable 및 Thread와 ACE_Task와는 차이점이 없어 보인다. 하지만 엄연히 다른 점이 있다. ACE_Task는 내부에 쓰레드 풀을 가지고 있어서 svc()에 있는 코드를 여러 쓰레드가 동시에 실행할 수 있으며 타입 매개 변수를 통해 ACE_Task 내에 있는 쓰레드 풀에 정책을 지정할 수 있다. 또한 ACE_Task는 메시지 큐를 내장하고 있어서 쓰레드나 Task 간에 메시지를 교환하거나 버퍼링하는 방법을 제공한다.

서비스 설정
애플리케이션이 장시간 작동하는 상황에서 그 기능이나 구조, 알고리즘을 교체하는 일이란 쉽지 않다. 고객을 상대로 서비스를 하는 서버 프로그램의 경우 이러한 변경 사항을 반영하려면 이용률이 낮은 새벽에 서비스를 중지해야 한다. 여러 컴포넌트를 조합하여 대규모 시스템을 개발하다가 일부 컴포넌트에 버그가 발견되어 이를 수정, 교체해야 할 경우에는 어떻게 해야 할까. 또는 서비스 기능을 이리 저리 바꿔보면서 애플리케이션을 테스트해야 하는 경우에는 바꿀 때마다 전체 시스템을 재빌드해야 하는 상황이 발생할 수 있다. 빌드 시간이 짧다면 큰 문제가 되지 않겠지만 만일 그렇지 않다면 이는 때에 따라 개발 일정을 연기시킬 수 있는 결과를 초래할 수도 있다.
만일 이러한 애플리케이션의 컴포넌트들이 동적으로 연결되어 있어서 애플리케이션을 설치할 때, 심지어 실행중일 때 컴포넌트를 교체하는 방법을 제공한다면 어떨까? 아마도 시간과 비용적인 측면에서 커다란 이득을 볼 수 있을 것이다. 마치 축구 경기에서 게임이 잠시 멈추었을 때 선수를 교체하고 다시 게임을 진행하듯이 말이다. 이런 마법과 같은 일이 과연 가능할까?
ACE에서는 Service Configurator 프레임워크를 통해 이러한 기능을 지원하며 이를 통해 애플리케이션의 확장성을 증가시킬 수 있다.
여기서 주의해서 봐야 할 클래스는 ACE_Service_Config다. 이 클래스는 사용자가 서비스 구현을 교체, 임시 정지, 서비스 재개, 재초기화 또는 종료시키기 위해 작성한 스크립트를 해석하고 수행하는 기능을 한다. 그 과정에 대해 간단히 살펴보자. 먼저 스크립트를 담고 있는 파일이 수정되면 애플리케이션이 시그널을 발생시키고, Reactor 프레임워크는 시그널을 감지한다. Reactor는 애플리케이션을 재설정해야 함을 알고 ACE_Service_Manager에 ‘recon figuration’ 명령을 내리며 ACE_Service_Manager는 ACE_ Service_Config가 새로운 설정 파일을 읽어서 수행하도록 명령한다. 스크립트에 사용되는 주요 명령어는 다음과 같다.

스트림
유닉스 환경에 익숙한 개발자라면 유닉스 셸에서 파이프가 가져다 주는 편리함에 매료된 경험이 있을 것이다. 파이프에 대해 잘 모르는 독자를 위해 간단히 설명하자면 파이프는 한 명령어를 실행하고 난 결과를 그 다음 명령어의 입력 값으로 전달한다. 이를 통해 초기 원시 데이터를 동시에 여러 명령어를 이용해 손쉽게 가공할 수 있다.
가령 find . -name “*.cpp” | xargs grep “cout” 이 명령에 대해 살펴보자. 이러한 명령어는 ‘|’를 기준으로 크게 두 개의 명령어로 구분할 수 있다. 하나는 현재 디렉토리에서부터 하위 디렉토리를 모두 뒤져 확장자가 .cpp인 파일을 모두 찾아 그 목록을 출력하는 find 명령어, 그리고 파일 목록에서 cout이라는 문자열을 가진 파일을 골라서 cout이 포함되어 있는 행을 출력하는 grep 명령어로 나눌 수 있다(중요하진 않지만 xargs에 대해 설명하자면, xargs는 파이프로부터 넘어온 문자열을 스페이스를 기준으로 잘라낸 후 xargs 후에 오는 명령어의 끝 부분에 붙여주는 명령어다). 이처럼 파이프는 앞 명령어의 결과를 뒤 명령어에 전달해 명령어 조합을 통해 복잡한 일을 쉽게 처리할 수 있도록 도와준다.
이러한 개념은 사실 유닉스 셸에만 존재하는 것이 아니라 애플리케이션의 아키텍처를 설계할 때도 사용된다. 가령 데이터를 처리하고 가공하는 일들을 단계별로 나눈 후 각 단계를 모듈로 작성하고 이들을 서로 연결하는 방식으로 애플리케이션을 개발할 수 있다. 대표적인 예가 바로 OSI 7 레이어다. 익히 알다시피 각 레이어는 전달받은 데이터를 자신의 기능에 따라 가공하고 다음 레이어에 전달하는 일을 하도록 설계되어 있다. 전체 네트워크 프로토콜을 구현하는 일은 이러한 레이어들의 조합을 통해 이뤄진다.
이렇게 전체 데이터 처리 과정을 모듈별로 단순화해 각각을 개발한 후 이들 모듈을 연결하고 조합해 전체 애플리케이션을 개발하면 재사용성 및 유연성을 증대시킬 수 있으며, 개발 과정을 투명하고 점진적으로 만들 수 있다. 파이프와 필터는 이러한 아키텍처 설계 방법을 공식화한 패턴이다. 파이프와 필터는 전체 애플리케이션을 파이프와 필터의 조합으로 생각한다. 각 필터는 데이터를 어떻게 처리할 것인지 그 방법을 제시하며 파이프는 이러한 필터를 연결하여 필터 간에 데이터를 전달하는 기능을 한다. 애플리케이션은 이러한 필터들을 파이프로 연결함으로써 완성되며, 이렇게 설계된 애플리케이션은 필터만 단순히 바꿔 끼우는 것으로 그 기능을 변경하거나 버그를 수정할 수 있는 높은 유연성을 제공한다. ACE의 Stream 프레임워크는 이러한 패턴을 구현하고 있다.
만일 파일을 읽어서 그 내용을 사용자가 보기 쉽게 가공한 뒤 네트워크를 통해 전송하는 애플리케이션을 만든다면 ACE Stream 프레임워크를 이용해 다음과 같이 작성할 수 있다.

짾 전체 작업을 세 단계(파일 읽기, 메시지 가공하기, 네트워크로 전송하기)로 나누고, 각 단계별로 ACE_Task 클래스를 확장해 모듈을 작성한다.
짿 각 ACE_Task 객체를 ACE_Module로 포장한다.
쨁 세 개의 ACE_Module을 ACE_Stream에 순서대로 등록한 후 각 Task를 실행한다.

그밖에 유용한 ACE 클래스 라이브러리들
ACE 프레임워크 레이어 아래에 위치하고 있는 ACE 랩퍼 퍼서드 레이어에는 이식성이 뛰어나고 잘 설계된 많은 클래스들이 있다. 이들에 대해 간단히 정리해 봤다(<표 8>).
그 외에 타이머, STL 스타일의 컨테이너 클래스들(Map, Hash Map, Set, List, Array 등), 시그널 처리, 파일 I/O, 파일 비동기 I/O와 같은 유용한 기능들이 클래스 라이브러리로 제공된다. 지면상 ACE의 수많은 유용한 클래스를 모두 소개하지 못한 아쉬움이 남지만 이런 유용한 기능들이 클래스와 프레임워크로 잘 설계되어 사용하기 편리하다는 점과 모두 ACE OS 어댑테이션 레이어 위에서 만들어진 클래스들이라 훌륭한 이식성을 보장한다는 점만큼은 특히 강조하고 싶다. 만일 ACE에 대한 더 많은 정보가 필요하다면 참고 자료 목록을 이용하기 바란다.

재사용 기술을 적극 활용하기를
세간의 평가와는 다르게 필자는 소프트웨어 산업이 다른 산업 분야에 비해 뒤쳐져 있다고 생각한다. 겉으로는 첨단 기술의 중심에 서 있는 것처럼 외치면서도 실상 속 내용을 들여다 보면 산업이라는 말이 부끄러울 정도로 아직도 개발 과정이 수공업 수준에서 벗어나지 못하고 있다. 게다가 이런 현실을 개선하려는 여러 실험적 개혁에 업계 관행이라는 이유로 차가운 시선을 보내는 한국 사회의 후진성도 소프트웨어 산업의 발전을 저해하는 걸림돌 중 하나다. 역사가 짧기 때문이라는 변명도 해본다.
지금 시대의 화두는 ‘개혁’이다. 소프트웨어 산업을 개혁, 발전시키기 위해서는 무엇보다 밑바탕이 되는 개발자와 관리자들의 인식 전환이 필요하다. 프로페셔널 프로그래머라면 소프트웨어 개발은 개인의 창작이 아니라 제품을 만드는 산업의 과정이라는 생각을 가져야 하며, 관리자들은 성공한 프로젝트들을 분석해 그들의 개발 프로세스를 면밀히 분석할 수 있어야 한다. 그리고 소프트웨어 산업계는 다른 산업 분야의 장점에 대해 열린 마음으로 수용하는 자세를 가져야 한다. 건축학에서 유래한 디자인 패턴은 다른 분야의 개념을 적극 수용한 예다.
예를 들어 자동차 산업의 장점에서 소프트웨어 산업이 배워야 할 점은 무엇일까? 지극히 상식적인 이야기이지만 자동차 회사는 자동차를 생산하기 위해 들어가는 모든 재료와 부품을 스스로 만들지 않는다. 좋은 자동차를 만들기 위해 자동차 회사들은 수많은 전문 부품 제작 업체들로부터 양질의 부품을 공급 받아 자동차를 제작한다. 이들은 한 마디로 그들의 자동차 생산 과정을 철저히 전문화, 분업화한 시스템을 갖추고 있는 것이다.
소프트웨어 개발 프로젝트에서 중요한 것은 제품의 질과 타임 투 마켓(time to market : 시장의 요구를 미리 감지하고 이익을 극대화하기 위한 제품의 출시 시기)이다. 현 소프트웨어 산업의 사정은 이 두 가지를 함께 고려해야 한다는 목소리에 공감은 하면서도 정작 개발 단계에서는 다시 수공업 체제로 돌아가는 오류를 범하고 있다. 그렇다면 소프트웨어 산업이 개혁되어야 하는 방향은 정해졌다. 개발 과정을 철저히 전문화, 분업화해야 한다. 그 첫 걸음은 미들웨어, 프레임워크와 같은 전문 업체에서 만든 부품을 사용하는 것부터 시작된다.

정리 | 송우일 | wooil@korea.cnet.com


문상철 | Ohkiss@hitel.net
프로그래머의 진정한 능력은 코드로만 볼 수 있고 코드로만 보여줄 수 있다고 믿는 필자는 미들웨어 연구에 관심이 많으며 로코즌 객체 기술 연구소에서 CORBA ORB와 트레이더 서비스를 개발했다. 축구 경기 보고 토론하는 것을 좋아하며 로또에 당첨되어 삶에 여유가 생기면 꼭 피아노를 배울 생각이다.


[ 교통 시스템에 비유해 보기 ]
네트워크 애플리케이션을 개발하는 것을 종종 교통 시스템에 비유한다. 아마도 두 시스템이 모두 전송·규모·속도를 다루기 때문이 아닌가 싶은데, 공항·기차·버스·터미널·도로와 같은 하드웨어를 비롯해 열차나 비행기, 버스 시간표 만들기, 교통 정리하기, 티켓 발급하기, 유지보수하기, 감시하기와 같은 소프트웨어적인 일들로 나뉜다.
그런데 전체 교통 시스템을 설계하는 아키텍트 입장에서는 이러한 요소가 모두 갖춰져 있다고 해서 좋은 시스템을 만들 수 있는 것은 아니다. 각 요소에 대한 성격을 정확히 파악하고 이들을 효과적으로 배치하고 서로 연결하는 일이 무엇보다 중요하다. 공항은 시 외곽에 위치하는 것이 좋으며, 그렇기 때문에 도심으로 빠르게 접근할 수 있는 고속도로나 철도망이 잘 갖춰져 있어야 하며, 이러한 접근로들은 다른 지역으로 갈 수 있는 철도역이나 버스 터미널과 연계되어야 이용객들이 교통 시설들을 편리하고 빠르게 사용할 수 있을 것이다.
광역 교통 시스템을 설계하는 것을 소프트웨어 개발에 비유한다면 공항, 기차역, 버스 터미널과 같은 요소들은 프레임워크에 비유할 수 있다. 예를 들어 공항에 대해 살펴보자. 공항은 최대한 효율적이고 편리한 항공 수송을 위해 항공 서비스에 특화되어 있다. 입구를 통해 공항에 들어가면 우선 비행기 이착륙 시간표가 눈에 들어오며, 티켓을 끊고 탑승 수속을 밟을 수 있는 코너가 있으며, 짐을 맡길 수 있는 곳, 출입국 심사를 하는 곳, 전염병 예방을 위한 보건소, 기념품 판매소 등 승객들이 비행기를 통해 여행을 떠나거나 도착하는 데 필요로 하는 모든 서비스를 공항에 전부 한데 모아 놓았다.
만일 비행기 티켓을 비행기 회사에 가서 끊어야 한다면? 예방 주사를 안 맞은 사람은 다시 돌아가서 맞고 오라고 한다면 얼마나 불편할까? 이러한 서비스들은 시너지 효과를 발생하도록 최대한 모아 놓는 것이 좋다. 마치 프레임워크가 특정 애플리케이션 개발에 필요한 기능과 구조를 한데 모아 두듯 말이다.
그리고 공항에서 주목해야 할 점은 공항 내 모든 서비스를 승객들에게 제공하기 위해 공항은 직원들을 두어서 스스로 작동한다는 것이다. 이 직원들은 외부에서 이용객들이 찾아오면 그들에게 편리한 항공 서비스를 하기 위해 열심히 일하게 된다. 비행기가 떠날 준비가 되면 비행기를 기다리는 승객들을 위해 자체 안내 방송을 해주고, 사스와 같은 전염병 ‘이벤트’가 발생하면 승객들에게 자체 방역 서비스를 실시한다.
공항 직원이 손님이 오기를 기다리는 것은 프레임워크가 이벤트를 기다리는 것과 같으며, 손님에게 서비스를 제공하는 것은 프레임워크가 이벤트를 처리하기 위해 적절한 콜백 객체를 호출하는 것과 같다. 그리고 공항 전체 직원들은 손님들에게 각종 서비스를 제공하기 위해 분주하게 움직이며, 이는 ‘inversion of control’이라 할 수 있다. 이러한 비유는 공항뿐만 아니라 기차역, 버스 터미널과 같은 사회 간접 자손 시설들에 모두 적용될 수 있으며, 이러한 시설들을 연계해 교통 시스템을 구축하는 일은 적절한 프레임워크들을 연결해 애플리케이션을 제작하는 일로 생각할 수 있다.


[ ACE 지원 OS 플랫폼 ]

ACE는 다음과 같은 플랫폼을 지원하며, 이들 플랫폼에서 작동하는 대부분의 C++ 컴파일러를 이용할 수 있다.

◆ PC : MS 윈도우(32/64비트), 윈도우 CE, 리눅스(레드햇, 데비안), Mac OS Ⅹ
◆ 대부분의 유닉스 : SunOS 4.x, 솔라리스, SGI IRIX, HP-UX, Tru64, AIX, DG/UX, SCO 오픈서버·유닉스웨어, NetBSD, FreeBSD
◆ 리얼타임 운영체제 : VxWorks, OS/9, Chorus, LynxOS, Pharlap TNT, QNX Neutrino, RTEMS, pSoS
◆ 대규모 엔터프라이즈 시스템 : OpenVMS, MVS OpenEdition, Tandem NonStop-UX, Cray UNICOS

Posted by 몽센트