원격 객체 통신(RMI와 CORBA)
RMI와 CORBA는 네트웍을 가로지르는 객체들 간의 통신을 다룹니다.
URL과 소켓을 사용한 네트웍 통신 프로그래밍을 이전에 다룬 경험이 있으면 네트웍 환경에서의 프로그래밍이 낯설게 느껴지지는 않을 것입니다. 자바가 등장하기 이전에도 분산 네트웍 환경에서 보다 쉽게 프로그래밍을 하기 위한 방법들이 존재했습니다. 예를 들어, 유닉스 시스템에서 주로 사용되는 RPC(원격 프러시저 호출)와 같은 방법은 원격지의 기능을 사용하기 위해 마치 함수처럼 정의하여 사용할 수 있게 해주는 통신 규약입니다. RPC를 사용하면 프로그래머는 소켓 통신을 고려할 필요 없이 마치 같은 프로그램 내의 함수를 호출하듯이 원격 호스트의 함수를 사용할 수 있게 됩니다. 하지만 함수 수준의 이러한 기능은 자바와 같은 객체 세계에서 사용하기에는 적합하지 않습니다.
자바는 함수 수준의 통신이 아니라 지역 객체와 원격 객체 간의 메쏘드 호출을 통한 원격 객체 통신 방식을 제공합니다.
이러한 원격 객체들 간의 통신을 제공하는 방법에는 자바의 원격 메쏘드 호출(RMI) 외에도, 객체 관리 그룹(OMG)에서 만든 개방형 표준인 공용 객체 브로커 아키텍처(CORBA), 마이크로소프트 사에서 만든 분산 컴포넌트 객체 모델(DCOM) 등이 있습니다.
이 글은 자바 2 SDK 버전 1.2.x(흔히 JDK 1.2.x라고 부릅니다)에서 지원하는 RMI와 CORBA를 다룹니다.
1. 원격 메쏘드 호출 (RMI)
자바 프로그램을 실행하는 환경을 일반적으로 자바 가상 기계(Java Virtual Machine)라고 부릅니다. 자바 가상 기계는 윈도우나 유닉스 등의 운영 체제에 이식되어서 자바 프로그램 즉, 자바 클래스 파일들을 각 운영 체제에 맞게 실행하는 실행 환경입니다. 여러 개의 호스트에 나뉘어져 실행되는 자바 프로그램들로 구성되는 일종의 네트웍 서비스를 만든다고 생각해봅시다. 호스트가 다르므로 각각의 자바 프로그램들은 네트웍을 가로질러 별도의 통신 방법을 구현해야 합니다. TCP 소켓을 사용하여 통신한다고 하면, 서로의 통신 규약과 메시지 형식 등 정의하는 등 복잡한 문제들을 해결해야 할 것입니다.
원격 객체 간의 통신 방식을 사용하면 이러한 문제는 한결 쉽게 해결됩니다. 자바의 원격 객체 통신 방식인 원격 메쏘드 호출은 다른 호스트(엄밀하게 표현하면 다른 가상 기계)에 있는 객체의 메쏘드들을 마치 같은 가상 기계의 객체 메쏘드를 사용하듯이 쉽게 사용하게 해주는 기술입니다. 자바의 원격 메쏘드 호출 즉, RMI에 관련된 클래스들은 java.rmi, java.rmi.activation,java.rmi.dgc, java.rmi.registry, java.rmi.server 패키지들에 포함되어 있습니다.
1.1 RMI를 사용한 원격 객체 통신
자바 RMI를 사용하는 기본적인 방법은 먼저 원격으로 사용될 객체를 지정된 인터페이스로 정의한 다음, 원격 객체는 인터페이스를 구현하는 방식으로 설계하고, 지역 객체는 이 인터페이스의 메쏘드를 호출함으로써 실제 원격 객체의 메쏘드를 호출하는 것입니다. 실제 네트웍 통신 등에 관련된 복잡한 부분은 자바의 RMI 규약이 대신 처리해줍니다. 프로그래머는 RMI 규약이 정한 방법만 따르면 원격 호스트의 서버 객체를 마치 지역의 일반 자바 객체를 다루듯이 이용할 수 있습니다.
<그림> 자바의 원격 메쏘드 호출
자바의 원격 객체 통신에서 호출될 원격 객체를 서버 객체, 원격 객체를 사용하는 지역의 객체를 클라이언트 객체라고 부릅니다. 주의 깊게 볼 것은 지역의 클라이언트 객체는 절대 원격 호스트의 서버 객체를 직접 참조하지 않는다는 점입니다. 지역 객체가 참조하는 것은 서버 객체가 구현하고 있는 인터페이스입니다. 이 인터페이스를 원격 인터페이스라고 부르는데, 원격 인터페이스를 사용함으로써 클라이언트 객체를 구현할 때 실제 서버 객체 클래스를 요구하지 않고 원격 인터페이스만 있으면 컴파일이 가능해집니다. 따라서, 원격 인터페이스만 정의된다면 클라이언트 객체와 서버 객체가 완전히 서로 독립적으로 개발될 수 있으며, 하나의 서버 객체를 여러 클라이언트 객체들이 사용하는 것도 가능해집니다.
자바 RMI에서 이 원격 인터페이스는 java.rmi 패키지의 Remote 인터페이스를 상속하여 만들어야 합니다. Remote 인터페이스는 다음과 같이 선언되어 있습니다.
public abstract interface Remote( ) { }
이 Remote 인터페이스는 별도의 메쏘드를 선언하지 않습니다. 원격 객체를 구분하기 위해 사용하는 인터페이스입니다. 실제로 원격 서버의 인터페이스를 정의하려면 다음 세 가지 조건을 만족해야 합니다.
● 원격 인터페이스는 Remote 인터페이스를 상속합니다.
● 원격 인터페이스에 정의된 메쏘드들은 java.rmi.RemoteException 예외를 던질 수 있도록 명시적으로 선언해야 합니다.
● 원격 인터페이스에 정의된 메쏘드들은 인자나 반환 유형으로 다음 유형들만 가져야 합니다.
① byte, int 등 자바의 기본 유형 ② 원격 객체 ③ java.io.Serializable을 구현한 직렬화 가능한 객체
자바의 코어 패키지에 있는 객체들 중 대부분이 직렬화 가능한 객체이므로 대부분의 객체를 인자나 반환 유형으로 사용할 수 있습니다.
예를 들어, 다음 원격 인터페이스는 올바른 원격 인터페이스입니다.
import java.rmi.*; interface RemoteFile extends Remote { public boolean isFile() throws RemoteException; public boolean isDirectory() throws RemoteException; public String[] list() throws Exception; public boolean delete() throws RemoteException, SecurityException; public boolean renameTo(RemoteFile dest) throws java.io.IOException, SecurityException; }
<예제> RemoteFile 원격 인터페이스
RemoteFile 인터페이스는 먼저 Remote 인터페이스를 상속하고 있고, 각 메쏘드들은 RemoteException 혹은 RemoteException 클래스의 부모 클래스인 예외들을 던질 수 있도록 명시적으로 선언하고 있습니다. 따라서, 원격 인터페이스의 조건을 모두 만족하고 있습니다.
RemoteException은 어떤 예외를 표현하는 클래스일까요? 이름 그대로 원격 호출에 관련된 수행을 하는 도중에 발생하는 네트웍에 관련된 여러 예외 상황들을 총괄하는 예외 클래스입니다. 지역 객체를 사용하는 것과 달리, 원격지의 객체와의 통신은 항상 네트웍 상황에 따라 여러 가지 예외 상황들이 발생하게 마련입니다. 이러한 예외 상황들을 처리하기 위해 모든 원격 인터페이스는 RemoteException을 발생시킬 수 있음을 반드시 명시해야 합니다.
1.2 Hello RMI 프로그램
이제 RMI를 사용하는 서버/클라이언트 객체들을 만들어봅시다. 만들 프로그램은 원격 서버에 있는 hello()라는 메쏘드를 호출하면 원격 서버가 "Hello, Remote World!"라는 문자열을 건네주는 아주 간단한 프로그램입니다.
1.2.1 RMI 원격 서버 만들기
가장 먼저 할 일은 원격 인터페이스를 선언하는 일입니다.
// Hello.java 소스 코드 import java.rmi.*; public interface Hello extends Remote { String hello() throws RemoteException; }
<예제> Hello 원격 인터페이스 선언
앞에서 잠깐 언급했듯이 RMI 원격 객체 통신에서는 원격 인터페이스만 있으면 서버 프로그램과 클라이언트 프로그램은 서로 독립적으로 만들 수 있습니다. 먼저 이 원격 인터페이스를 구현하는 원격 서버를 만들어보도록 합시다.
원격 서버는 원격 인터페이스를 구현하는 객체로 나중에 클라이언트 객체가 호출할 인터페이스의 메쏘드를 실제 구현하고 있는 객체입니다.
원격 서버 객체는 다음과 같은 방식으로 구현할 수 있습니다.
● java.rmi.server 패키지에 있는 RemoteObject 클래스를 상속합니다. 보통 RemoteObject 클래스의 자식 클래스인 UnicastRemoteObject 클래스나 Activatable 클래스를 상속하여 구현합니다.
● 관계된 원격 인터페이스를 구현합니다.
● 서버 객체가 클라이언트의 접속을 처리할 수 있도록 객체를 익스포트합니다. 이 객체 익스포트는 자바의 RMI 실행환경이 클라이언트의 TCP 접속을 받아서 해당 객체에게 전달할 수 있도록 하는 것으로 UnicastRemoteObject.exportObject() 혹은 Activatable.exportObject() 정적 메쏘드를 사용합니다. 다만, UnicastRemoteObject 클래스나 Activatable 클래스를 상속한 경우에는 별도로 익스포트하지 않아도 됩니다.
UnicastRemoteObject 클래스와 Activatable 클래스 자바 RMI의 원격 서버 객체에는 크게 두 가지 유형이 있습니다. 항상 서버 프로그램이 실행되고 있을 때에만 서버 객체가 유효한 경우와 클라이언트가 서버 객체를 요청하면 동적으로 서버 객체를 적재하는 방식이 그것입니다. 전자의 경우에는 보통 java.rmi.server 패키지의 UnicastRemoteObject 클래스를 상속하여 구현하고, 후자의 경우에는 java.rmi.activation 패키지의 Activatable 클래스를 상속하여 원격 객체를 각각 구현하게 됩니다. 원격 객체 활성화라고 부르는 Activatable 클래스를 사용하는 방식은 JDK 1.2(자바 2 SDK 버전 1.2) 이후 버전부터 지원합니다. 클라이언트의 접속이 없을 때에도 항상 서버 프로그램이 실행되어야 하는 UnicastRemoteObject 방식에 비해 Activatable 방식은 클라이언트의 접속 요청에만 활성화되므로 훨씬 효율적이라고 볼 수 있습니다. Activatable 방식을 사용할 경우 직접 클라이언트의 접속 요청을 처리할 수 없으므로 클라이언트의 접속을 처리하는 rmid라는 RMI 데몬 프로그램이 항상 실행되고 있어야 합니다. |
Hello 인터페이스를 구현하는 원격 서버 객체 클래스 HelloImpl은 RemoteObject의 자식 클래스인 UnicastRemoteObject 클래스를 상속하였습니다. 주의할 부분은 생성자를 반드시 다시 선언하여 RemoteException을 던질 수 있도록 해야 하다는 점입니다. HelloImpl 클래스의 경우, 생성자 안에서는 아무런 일도 하지 않지만, 부모 클래스인 UnicastRemoteObject 클래스가 생성자에서 RemoteException을 던지도록 선언되어 있으므로 이 예외를 처리하지 않으면 컴파일 에러가 발생합니다.
// HelloImpl.java 소스 코드 import java.rmi.*; import java.rmi.server.UnicastRemoteObject; class HelloImpl extends UnicastRemoteObject implements Hello { /* * RemoteException을 처리해야 하기 때문에 * 생성자를 반드시 다시 선언해야 합니다. */ HelloImpl () throws RemoteException { } // Hello 인터페이스의 메쏘드들을 구현합니다. public String hello() throws RemoteException { return "Hello, Remote World!"; } }
<예제> HelloImpl 원격 서버 클래스
1.2.2 RMI 원격 서버 등록
원격 서버 객체를 만드는 것은 위에서 본 것처럼 UnicastRemoteObject 클래스를 상속할 경우 아주 간단합니다. 하지만, 이것으로 끝난 것은 아닙니다. RMI 원격 서버 객체들은 클라이언트들이 찾을 수 있도록 RMI 등록부에 등록을 해야 합니다. 또, 실행 환경이 원격 객체들의 클래스 파일들을 적재하기 위해서는 별도의 RMI 보안 관리자를 지정해야 합니다.
이 과정을 하나씩 구현해봅시다.
먼저 서버 프로그램이 실행되기 위해서는 main() 메쏘드를 정의해야 합니다. 별도의 클래스에서 main() 메쏘드를 정의할 수 있지만, 여기에서는 HelloImpl 원격 객체 클래스 안에 함께 정의합니다.
보안 관리자를 RMISecurityManager로 설정하는 것은 RMI 서버와 클라이언트 프로그램 모두에서 수행해야 합니다. 보안 관리자가 설정되지 않으면 지역의 CLASSPATH 환경변수에서 적재할 수 있는 클래스 외에 원격 객체들은 적재할 수 없게 됩니다.
if (System.getSecurityManager() == null) System.setSecurityManager(new RMISecurityManager());
원격 서버 객체를 RMI 등록부에 바인드하기 위해서 보통 java.rmi 패키지의 Naming 클래스나 java.rmi.registry 패키지의 Registry 인터페이스를 사용합니다. RMI 등록부는 원격 서버 객체들을 특정한 문자열로 검색할 수 있도록 등록하는 RMI 환경으로 rmiregistry라는 데몬 프로그램을 먼저 실행시켜야 사용 가능합니다. Naming 클래스나 Registry 인터페이스는 등록부에 객체를 등록하기 위한 bind()와 rebind(), 등록을 해지하기 위한 unbind(), 그리고 바인드된 문자열로 원격 서버 객체를 검색하는 lookup() 메쏘드, 등록된 문자열 목록을 보여주는 list() 메쏘드 등을 제공합니다. Naming 클래스와 Registry 인터페이스가 제공하는 각 메쏘드들의 차이점은 Naming 클래스는 URL을 사용한다는 점이고 Registry 인터페이스는 해당 등록부에 대한 참조를 가지고 있으므로 바인드된 문자열만을 사용한다는 점입니다.
RMI URL 표기
<그림> RMI의 URL 구성 요소 Naming 클래스는 URL을 사용하여 원격 서버 객체를 문자열로 바인드, 바인드 해지, 검색 등의 기능을 제공합니다. URL은 프로토콜, 호스트, 포트 번호, 리소스 위치로 구성되는데 RMI 프로토콜에서는 프로토콜 이름에 "rmi"가 사용되고, 리소스 위치에 바인드한 문자열을 사용합니다. Naming 클래스는 rmi 원격 객체만을 다루므로 URL 내용에 프로토콜을 나타내는 "rmi:" 부분은 생략할 수 있습니다. 또, 포트 번호는 생략되면 RMI 프로토콜의 기본 포트인 1099번이 사용되며, 호스트 이름이 생략되면 지역 호스트로 간주합니다. 따라서, Naming 클래스는 다음을 모두 같은 URL로 인식합니다. rmi://localhost:1099/HelloServer //localhost:1099/HelloServer //localhost/HelloServer /HelloServer |
원격 서버 객체를 RMI 등록부에 등록하려면 먼저 원격 서버 객체의 인스턴스를 생성한 다음 Naming 클래스의 bind() 혹은 rebind() 정적 메쏘드를 사용하면 됩니다. 주의할 점은 bind(), rebind(), unbind()는 보안 문제로 지역 호스트에서만 사용 가능하다는 점입니다. 검색 기능을 제공하는 lookup()이나 list() 메쏘드는 원격 호스트에 대해서도 사용 가능합니다.
try { // 원격 서버 객체 인스턴스를 생성 HelloImpl impl = new HelloImpl(); // 이 객체를 지역 호스트에 "HelloServer"라는 이름으로 바인드합니다. Naming.rebind("/HelloServer", impl); System.out.println("HelloServer successfully bound."); } catch (Exception e) { System.err.println("Error:"+e.getMessage()); e.printStackTrace(); }
Naming 클래스 대신에 Registry 인터페이스를 사용하려면 다음과 같이 LocateRegistry 클래스를 사용하여 원하는 호스트의 Registry 객체 인스턴스를 구한 다음에 rebind() 메쏘드를 사용하면 됩니다.
// 인자 없는 getRegistry() 메쏘드는 지역 호스트의 등록부에 대한 Registry 객체를 되돌립니다. LocateRegistry.getRegistry().rebind("HelloServer", impl);
bind() 메쏘드는 RMI 등록부에 같은 문자열로 등록된 원격 객체가 있으면 에러를 되돌리지만 rebind() 메쏘드는 기존의 원격 객체를 덮어씁니다.
그리고, 일단 RMI 등록부에 바인드가 성공하면 main() 메쏘드가 종료한 후에도 RMI 실행환경이 프로세스가 종료하지 않도록 도와주므로, main() 메쏘드가 종료함을 걱정할 필요는 없습니다.
간단한 RMI 등록부 관리 프로그램 지역 호스트에 rmiregistry를 실행하고 여러 개의 서버 객체를 등록할 수 있습니다. 이때 현재 등록된 객체들을 알고 싶거나, 지금 바인드된 서버 객체를 바인드 해지하고 싶을 때가 있습니다. 이러한 경우에 간단하게 사용할 수 있는 프로그램을 소개합니다. 간단하게 Registry 인터페이스나 Naming 클래스의 list() 메쏘드와 unbind() 메쏘드를 사용하여 구현할 수 있습니다. // RegUtil.java import java.rmi.*; import java.rmi.registry.*; public class RegUtil { public static void main(String args[]) { String server = null; boolean listing = true; if (args.length < 1) { System.out.println ("Usage: java RegUtil <remote host> [list|unbind <name>]"); System.exit(1); } else { server = args[0]; if (args.length >= 3 && args[1].equals("unbind")) listing = false; } try { if (listing) { String[] list = LocateRegistry.getRegistry(server).list(); if (list.length == 0) System.out.println("Nothing bound"); else { for (int i=0; i<list.length; i++) System.out.println("name["+i+"]:"+list[i]); } } else { // unbind mode try { LocateRegistry.getRegistry(server).unbind(args[2]); System.out.println(args[2]+" unbound successfully."); } catch (NotBoundException nbe) { System.err.println("Error:"+args[2]+" is not bound."); } } > } catch (Exception e) { e.printStackTrace(); } } } <예제> RegUtil.java 이 프로그램을 사용하여 현재 지역 호스트에 등록된 이름들을 보려면 도스 창에서 다음과 같이 실행합니다. java RegUtil localhost list 등록된 HelloServer를 바인드 해지하려면 다음과 같이 실행합니다. java RegUtil localhost unbind HelloServer unbind 모드는 보안 문제로 지역 호스트에서만 실행 가능하며 list 모드는 다른 원격 서버에 대해서도 실행할 수 있습니다. |
1.2.3 RMI 원격 서버 컴파일
HelloImpl.java 소스 코드를 컴파일하는 방법을 모르는 독자는 없을 것입니다. javac 컴파일러를 사용하여 간단하게 컴파일하면 됩니다. 물론 이에 앞서 인터페이스 파일인 Hello.java도 컴파일해둬야 합니다.
javac Hello.java HelloImpl.java
하지만, RMI 프로그램은 한번의 컴파일을 더 필요로 합니다. RMI 컴파일러인 rmic는 RMI 서버 객체의 스텁과 스켈러튼 클래스 파일들을 생성해줍니다. rmic는 인자로 원격 서버 객체의 클래스 이름을 필요로 합니다.
rmic HelloImpl
컴파일 결과로 스텁 파일인 HelloImpl_Stub.class와 스켈러튼 파일 HelloImpl_Skel.class가 생성됩니다.
스텁 파일과 스켈러튼 파일 원격 객체의 스켈러튼 파일은 실제 구현된 원격 객체를 호출하는 메쏘드를 포함하는 서버쪽 객체입니다. 따라서 스켈러튼 파일은 서버쪽에서만 필요로 합니다. JDK 1.2 버전 이후부터는 별도의 스켈러튼 파일을 필요로 하지 않기 때문에, 이전 버전과의 호환성을 위해서만 존재합니다. rmic 컴파일러를 사용할 때, -v1.2 인자를 주면 스켈러튼 파일을 만들지 않고 JDK 1.2 이후 버전에서만 사용할 수 있는 스텁 파일을 만들게 됩니다. 반면 스텁은 원격 객체에 대한 메쏘드 호출을 실제 구현된 원격 객체가 있는 서버로 전송하는 일을 담당하는 대리자 역할을 하는 객체입니다. 클라이언트 객체가 가지게 되는 원격 객체에 대한 참조는 실제로는 지역의 스텁에 대한 참조입니다. 스텁 파일은 서버쪽과 클라이언트쪽 모두에 필요합니다. Naming 클래스나 Registry 인터페이스의 lookup() 메쏘드를 사용하여 RMI 등록부에 등록된 원격 객체에 대한 참조를 얻을 때 이 메쏘드가 반환하는 객체는 실제 원격 서버 객체가 아니라 원격 서버에 대한 스텁 객체가 됩니다.
<그림> 자바 분산 객체 모델에서 스텁과 스켈러튼의 역할 |
1.2.4 RMI 원격 서버 실행
먼저 원격 서버를 실행하기에 앞서 RMI 등록부 프로그램인 rmiregistry가 실행되어 있어야 바인드를 할 수 있습니다.
원격 서버 객체들을 실행할 때 원격 서버 객체가 위치한 클래스 경로를 지정하는데, rmiregistry가 원격 서버 객체들을 지정한 클래스 경로로부터 적재하기 위해서는 rmiregistry를 실행할 때 CLASSPATH 환경변수에 지정된 디렉토리들에 같은 이름의 클래스가 없어야 합니다.
따라서, rmiregistry 프로그램을 실행할 때에는 CLASSPATH 환경변수를 지운 다음 실행하는 것이 좋습니다. 즉, 도스 창에서 다음과 같은 방식으로 실행합니다. 유닉스 셸에서도 이와 유사한 방법으로 실행할 수 있습니다.
C:\> unset CLASSPATH C:\> rmiregistry
또 하나 서버를 실행하기에 앞서 주의할 점은 네트웍 보안 제약에 관련된 것입니다. 자바 2 이후부터는 애플릿이 아닌 일반 자바 프로그램에도 보안 제약 사항이 적용됩니다.
JDK 1.2가 자바 응용 프로그램에 기본값으로 적용하는 네트웍 관련 정책은 다음과 같습니다.
permission java.net.SocketPermission "localhost:1024-", "listen";
이 정책은 지역 호스트의 응용 프로그램은 1024번 이후의 포트 번호에 대해서 소켓의 listen 기능만을 사용할 수 있음을 나타냅니다. RMI 서버 프로그램은 외부 호스트의 클라이언트로부터 접속을 허용해야 하므로 이 보안 정책을 변경할 필요가 있습니다.
따라서 다음과 같이 보안 정책 파일을 별도로 만듭니다. 아래에서 "클라이언트 주소"는 실제 접속을 허용할 클라이언트의 주소로 바꾸어줍니다.
grant { permission java.net.SocketPermission "localhost:1024-", "listen,connect,resolve"; permission java.net.SocketPermission "클라이언트 주소:1024-", "connect,accept,resolve"; };<예제> server.policy 보안 정책 파일
이제 RMI 원격 서버를 실행할 수 있습니다. 실행할 때에 보안 정책 파일과 RMI 환경이 원격 서버 클래스 파일들을 찾을 수 있는 경로를 지정해줘야 합니다. 다음은 해당 클래스 파일들이 C:\javaworks\rmi\hello\server라는 디렉토리에 위치할 때의 경우입니다. 세 줄로 나뉘어 보이지만 도스창에 입력할 때에는 모두 같은 줄에 써주어햐 합니다. 디렉토리를 지정할 때 마지막에 디렉토리 구분자('\')가 하나 더 추가되어야 한다는 점도 명심하기 바랍니다. 매번 이렇게 입력해주는 것이 귀찮다면 배치 파일을 만들어 실행하는 것도 좋은 방법입니다.
java -Djava.security.policy=server.policy -Djava.rmi.server.codebase=file:C:\javaworks\rmi\hello\server\ HelloImpl
1.2.5 RMI 클라이언트 만들기
RMI 클라이언트 프로그램은 Naming 클래스나 Registry 인터페이스의 lookup() 메쏘드를 사용하여 원격 서버의 등록부에 등록되어 있는 원격 객체에 대한 참조를 구하면 원격 객체를 마음대로 사용할 수 있습니다.
try { // 먼저 원격 객체에 대한 참조를 구한 다음 Hello obj=(Hello) Naming.lookup("//원격호스트이름/HelloServer"); // 원격 객체의 메쏘드를 호출합니다. String message = obj.hello(); } catch (RemoteException e) { }
다음은 애플릿으로 만든 RMI 클라이언트 예제입니다.
// HelloApplet.java import java.rmi.*; import java.applet.Applet; import java.awt.*; public class HelloApplet extends Applet { String message = null; public void init() { try { // RMI 원격 호스트와 애플릿 바이트코드가 위치한 호스트가 같습니다. String url = "rmi://" + getCodeBase().getHost() + "/HelloServer"; Hello obj = (Hello) Naming.lookup(url); message = "HelloServer said \""+obj.hello()+'"'; } catch (Exception e) { message = "Error:"+e.toString(); } } public void paint(Graphics g) { if (message != null) { g.setFont(new Font("SansSerif", Font.BOLD, 20)); g.drawString(message, 10, 100); } } }<예제> RMI 클라이언트 애플릿
RMI 클라이언트를 실행시키려면 앞에서도 언급했듯이 원격 인터페이스와 원격 스텁의 클래스 파일이 필요합니다. 즉, Hello.class와 HelloImpl_Stub.class 파일이 애플릿 바이트코드가 있는 디렉토리에 함께 있어야 합니다.
RMI 원격 호스트를 애플릿 바이트코드가 존재하는 호스트 즉, getCodeBase().getHost()를 사용하여 지정하였으므로 별도의 보안 정책 파일은 필요하지 않습니다.
만약 RMI 호스트가 애플릿 바이트코드가 존재하는 호스트와 다르다면 다음과 같은 별도의 보안 정책 파일을 만들어 애플릿뷰어를 실행시킬 수 있습니다. 아래에서 "애플릿 바이트코드가 위치하는 URL"은 실제 애플릿의 기반 URL을 넣고, "RMI 서버 주소"에는 실제 RMI 서버가 실행되고 있는 호스트의 주소를 넣어주면 됩니다.
grant codeBase "http://애플릿 바이트코드가 위치하는 URL/-" { permission java.net.SocketPermission "RMI 서버 주소:1024-", "connect,resolve"; };
<예제> RMI 클라이언트 애플릿의 보안 정책 파일
애플릿뷰어를 실행시킬 때에는 다음과 같이 보안 정책 파일을 지정하면 됩니다. (위의 보안 정책 파일 이름을 applet.policy라고 가정합니다.)
appletviewer -J-Djava.security.policy=applet.policy <애플릿을 포함한 HTML 문서의 URL>
팁 : 넷스케이프 커뮤니케이터에서 RMI 애플릿 실행시키기 마이크로소프트 사의 인터넷 익스플로러는 아직까지 RMI 기술을 지원하지 않고 있기 때문에 익스플로러에서는 RMI 클라이언트 애플릿을 실행시킬 수 없습니다. 넷스케이프 커뮤니케이터에서는 RMI 애플릿을 실행시킬 수 있는데 이를 위해서는 RMI 관련 코드를 실행하기 전에 다음과 같은 메쏘드를 호출하여 보안 관리 허락을 받아야 합니다. netscape.security.PrivilegeManager.enablePrivilege("UniversalConnect"); 이 메쏘드를 선언할 경우 컴파일할 때 다음과 같이 넷스케이프 패키지를 클래스 경로에 포함시켜줘야 합니다. javac -classpath ".;C:\Program Files\NETSCAPE\Communicator\Program\java\classes\java40.jar" HelloApplet.java 넷스케이프 패키지가 없을 경우 자바의 코어 리플렉션(Core Reflection) 기법을 사용하여 다음과 같이 코드를 고치면 넷스케이프 패키지 없이도 컴파일할 수 있습니다. try { Class mgr = Class.forName("netscape.security.PrivilegeManager"); Class[] classes=new Class[1]; classes[0]=String.class; java.lang.reflect.Method method = mgr.getMethod("enablePrivilege", classes); Object[] args=new Object[1]; args[0]="UniversalConnect"; method.invoke(null /*static method */, args); } catch (Exception e) { e.printStackTrace(); } 이 애플릿을 넷스케이프 커뮤니케이터로 실행시키면 보안 경고 대화상자가 나타납니다. 여기에서 "Grant" 버튼을 눌러 보안 사항을 허용하면 RMI 애플릿이 실행됨을 볼 수 있습니다.
<그림> 넷스케이프의 보안 관리자
<그림> 넷스케이프에서 실행된 RMI 클라이언트 애플릿 하지만, 이것은 지역 호스트에서 RMI 애플릿을 실행한 경우이며 원격 호스트의 RMI 애플릿을 실행하려면 별도의 전자 서명이 필요합니다. |
1.3 원격 객체 활성화
앞의 HelloImpl 원격 서버 객체는 원격 서버 프로그램이 계속 살아 있어야 하는 방식인 UnicastRemoteObject 클래스를 상속하여 구현하였습니다. 만약 많은 원격 서버 객체가 필요하고, 이들은 가끔씩 호출될 때, 이 모든 서버 객체 별로 프로세스가 항상 구동되고 있다면 심각하게 리소스가 낭비될 것입니다. JDK 1.2 버전부터는 이러한 문제점을 해결하기 위해 필요할 때에만 원격 서버 객체를 적재하는 원격 객체 활성화 기술을 지원하고 있습니다.
1.3.1 활성화 가능한 원격 서버 만들기
원격 객체 활성화가 가능하도록 원격 서버를 만드는 방법 중 간단한 것은 UnicastRemoteObject 클래스 대신에 Activatable 클래스를 상속하는 것입니다.
원격 서버 객체를 구현할 때 UnicastRemoteObject 클래스를 상속할 때와 비교해서 달라지는 부분은 생성자입니다. Activatable 클래스를 상속하거나 혹은 다른 방법으로 원격 객체 활성화 방식으로 원격 서버 객체를 구현할 경우에는 반드시 다음과 같이 두 개의 인자를 가지며 public인 생성자를 구현하여야 합니다. 나중에 자바 가상 기계가 실제로 원격 객체를 생성할 때 이 생성자를 사용하기 때문입니다.
public AnActivatableRemoteServer (ActivationID id, MarshalledObject data);
또, Activatable 클래스를 상속하여 구현하는 경우, 이 생성자의 첫 번째 줄에는 반드시 다음과 같은 형태의 Activatable 클래스 생성자를 호출하여야 합니다.
protected Activatable(ActivationID id, int port) throws java.rmi.RemoteException;
이 생성자는 객체를 지정된 포트로 익스포트하는 일을 수행합니다.
이 외에 원격 활성화가 가능한 서버 객체를 구현할 때에 주의할 점으로는 자바 가상 기계가 필요할 때에 이 객체를 호출하기 때문에 외부 패키지에서도 접근할 수 있도록 클래스가 public으로 선언되어야 한다는 점입니다.
이런 점들을 염두에 두고 앞의 HelloImpl 서버 객체를 원격 활성화가 가능한 서버 객체로 바꿔봅시다. 바뀐 부분은 볼드체로 표시하였습니다.
// ActivatableHelloImpl.java import java.rmi.*; import java.rmi.activation.*; public class ActivatableHelloImpl extends Activatable implements Hello { // 원격 활성화를 지원하는 객체를 위한 특별한 생성자 // 나중에 자바 VM이 호출할 수 있도록 반드시 public이어야 합니다. public ActivatableHelloImpl (ActivationID id, MarshalledObject data) throws RemoteException { // 반드시 부모인 Activatable 클래스의 이 생성자를 호출 super(id, 0/* 0번은 불특정 포트 */); } // Hello 인터페이스 메쏘드를 구현합니다. public String hello() throws RemoteException { return "Hello, Remotely Activatable World!"; } }
<예제> 활성화 가능한 ActivatableHelloImpl 원격 서버 클래스
1.3.2 활성화 가능한 원격 서버 등록 프로그램 만들기
원격 서버 객체를 생성하는 방법은 어렵지 않지만, 클라이언트 객체의 접속 요청에 따라 이 서버 객체를 자바의 RMI 활성화 데몬 즉, rmid가 활성화시킬 수 있도록 하는 데에는 몇 가지 필요한 것들이 있습니다.
원격 객체를 동적으로 생성하는 데 필요한 클래스 이름, 보안 정책 파일, 클래스 경로 등의 정보를 자바의 활성화 시스템에 등록해주어야 하고, 또, 특정한 포트로 익스포트하여야 합니다.
원격 활성화에 관계된 여러 인터페이스와 클래스들은 java.rmi.activation 패키지에 있습니다.
클래스/인터페이스 |
설명 |
Activatable 클래스 |
활성 가능한 원격 객체를 등록부에 등록하고 특정한 포트에 익스포트하는 일을 합니다. |
ActivationGroup 클래스 |
그룹 안에서 활성화 가능한 객체들을 생성하는 일을 합니다. |
ActivationGroupDesc 클래스 |
같은 자바 가상 기계 안의 객체들을 활성화시키는 활성화 그룹을 생성하거나 다시 생성하는 데 필요한 정보(클래스 이름과 클래스 경로, 객체에 특정한 초기화 데이터 등)를 유지합니다. |
ActivationGroupID 클래스 |
활성화 시스템 안에서 그룹을 식별하는 정보와 활성화 시스템에 대한 참조를 가지고 있습니다. |
ActivationDesc 클래스 |
그룹 식별자, 객체의 클래스 이름, 클래스 경로, 초기화 데이터 등 객체를 활성화하는 데 필요한 정보를 유지합니다. |
ActivationID 클래스 |
객체의 식별자와 원격 참조 정보를 유지합니다. |
ActivationSystem 인터페이스 |
활성화 그룹과 활성화 가능 원격 객체를 등록 또는 등록 해지하는 일을 수행합니다. |
<표> activation 패키지의 주요 클래스와 인터페이스
활성화 가능한 원격 객체를 등록하고 익스포트하는 방법은 일반적으로 다음 순서를 따릅니다.
① 먼저 RMI에 필요한 보안 관리자를 지정합니다.
System.setSecurityManager(new RMISecurityManager( ));
② 활성화 가능한 원격 서버 객체를 생성하는 책임을 지는 ActivationGroup 객체를 생성합니다.
ActivationGroup 객체를 생성하기 위해서는 먼저 ActivationGroupDesc 객체를 등록하여 자바의 활성화 시스템이 지정해주는 그룹의 식별자를 알아야 합니다. ActivationGroupDesc 클래스는 ActivationGroup을 생성하는 데 필요한 여러 가지 환경 정보를 유지하는 클래스입니다.
// 보안 정책 파일을 환경 속성에서 지정 Properties props = new Properties(); props.put("java.security.policy", "C:\\javaworks\\rmi\\hello\\server\\server.policy"); // 지정한 속성을 가지는 활성화 그룹 환경 객체 생성 ActivationGroupDesc actGroup = new ActivationGroupDesc(props, null); // 활성화 그룹을 등록하고 ID를 저장 ActivationGroupID gid = ActivationGroup.getSystem().registerGroup(actGroup); // 활성화 그룹을 생성 ActivationGroup.createGroup(gid, actGroup, 0);
③ ActivationDesc 클래스는 원격 서버 객체를 활성화하는 데 필요한 환경 정보 즉, 그룹 ID, 서버 객체의 클래스 이름, 클래스 위치, 초기 데이터 객체 등을 담고 있습니다. 따라서, ActivationDesc 객체가 서버 객체 등록에 필요합니다.
// 활성화 환경 객체 생성 ActivationDesc desc = new ActivationDesc("ActivatableHelloImpl", // 서버 객체의 클래스 이름 "file:C:\\javaworks\\rmi\\hello\\server\\", // 클래스 경로 (MarshalledObject) null // 초기화 데이터 );
④ 생성한 ActivationDesc 객체를 사용하여 원격 서버 객체를 등록합니다. Activatable 클래스의 register() 메쏘드를 사용하여 서버 객체를 rmid 원격 활성화 데몬에 등록할 때에는 UnicastRemoteObject 클래스를 상속할 때처럼 원격 서버 객체를 직접 생성할 필요가 없습니다. 일단 등록되고 나면 클라이언트의 요청에 따라 자바 가상 기계가 ActivationDesc 객체 정보를 참조하여 원격 서버 객체를 활성화하게 됩니다. register() 메쏘드가 반환하는 객체는 원격 서버의 스텁 객체입니다.
Hello hello = (Hello) Activatable.register(desc);
⑤ register() 메쏘드가 반환한 원격 서버의 스텁 객체를 rmiregistry 즉, RMI 등록부에 바인드하여 클라이언트가 특정한 문자열로 원격 서버 객체에 대한 참조를 얻을 수 있도록 합니다.
Naming.rebind("HelloServer", hello);
⑥ 앞에서 배웠듯이 자바의 RMI 시스템에 일단 바인드되면 main() 메쏘드가 종료하더라도 프로세스가 종료하지 않습니다. 원격 활성화 방식에서는 프로세스가 계속 살아 있을 필요가 없으므로 명시적으로 종료하면 원격 서버 등록 및 익스포트 과정이 끝납니다.
System.exit(0);
다음은 ActivatableHelloImpl 원격 서버를 등록하고 익스포트하는 Setup 클래스입니다.
// Setup.java class Setup { public static void main(String args[]) { try { // RMI 보안 관리자 지정 System.setSecurityManager(new RMISecurityManager()); // 활성화 그룹 만들기 Properties properties = new Properties(); properties.put("java.security.policy", "C:\\javaworks\\rmi\\hello\\server\\server.policy"); ActivationGroupDesc actGroup = new ActivationGroupDesc(properties, (ActivationGroupDesc.CommandEnvironment) null); ActivationGroupID groupID = ActivationGroup.getSystem().registerGroup(actGroup); ActivationGroup.createGroup(groupID, actGroup, 0); // 활성화 환경 객체 생성 ActivationDesc desc = new ActivationDesc ("ActivatableHelloImpl", "file:C:\\javaworks\\rmi\\hello\\server\\", (MarshalledObject) null); // 원격 객체 등록하여 스텁에 대한 참조를 구합니다. Hello hello = (Hello) Activatable.register(desc); System.out.println("Registered and got stub"); // 스텁을 RMI 등록부에 바인드합니다. Naming.rebind("HelloServer", hello); System.out.println("Successfully bound"); // 명시적으로 종료합니다. System.exit(0); } catch (Exception e) { System.err.println("Error:"+e.getMessage()); e.printStackTrace(); } } }
<예제> ActivatableHelloImpl 원격 서버를 등록하고 익스포트하는 Setup 클래스
1.3.3 활성화 가능한 원격 서버 컴파일과 실행
컴파일은 활성화가 아닌 원격 서버 객체의 경우와 다를 것이 없습니다.
javac Hello.java ActivatableHelloImpl.java Setup.java rmic ActivatableHelloImpl
원격 활성화 기능을 사용하기 위해서는 먼저 RMI 등록부 프로그램과 RMI 활성화 데몬이 실행되어 있어야 합니다.
별도의 도스 창에서 rmiregistry와 rmid를 각각 실행하도록 합니다. 이 때에도 rmiregistry와 rmid의 클래스 경로에 원격 서버 클래스가 있으면 각 원격 서버 객체가 등록할 때 지정한 경로를 사용하지 않고 자신의 클래스 경로에서 우선적으로 원격 서버 클래스를 적재하려 하므로 CLASSPATH 환경변수를 설정 해지한 후에 실행하는 게 좋습니다.
unset CLASSPATH rmiregistry rmid
원격 서버 객체를 등록하고 익스포트하기 위해서는 Setup 클래스를 사용하면 됩니다. 그런데 Setup 클래스를 실행하는 데에는 다음과 같이 몇 가지 권한이 필요합니다.
grant { permission java.net.SocketPermission "localhost:1024-", "listen,connect,resolve"; permission java.net.SocketPermission "자신의 호스트 IP 주소:1024-", "listen,connect,resolve"; permission java.lang.RuntimePermission "setFactory"; };
<예제> Setup 클래스를 실행하는 데 필요한 보안 정책 파일
이 권한들은 서버 객체를 등록하고 익스포트하는 데 필요한 것인데 Setup 클래스는 서버 객체를 등록하기 위해 한 번만 실행할 것이므로 모든 권한을 열어줘도 문제되지 않을 것입니다. 실제 원격 서버를 실행할 때 사용되는 보안 정책 파일은 코드에서 ActivationGroup 객체를 만들 때 이미 지정했다는 것을 잊지 마십시오.
grant { permission java.security.AllPermission; };
<예제> 모든 권한을 주는 보안 정책 파일 setup.policy
Setup 클래스를 실행할 때에는 위의 보안 정책 파일을 보안 정책으로 지정하여 다음과 같이 실행합니다.
java -Djava.security.policy=setup.policy -Djava.rmi.server.codebase=file:C:\javaworks\rmi\hello\actserver\ Setup
세 줄로 나누어서 표시했지만 도스 창에서 입력할 때에는 모두 같은 줄에 써주어야 합니다. 제대로 수행이 완료되었으면 다음과 같은 메시지를 출력하면서 종료할 것입니다.
Registered and got stub Successfully bound
이제 클라이언트를 실행하여 그 결과를 알아볼 차례입니다. 클라이언트는 HelloImpl 원격 서버를 사용하기 위해 만들었던 HelloApplet을 그대로 사용할 수 있습니다. 단지 ActivatableHelloImpl 클래스를 rmic 컴파일러로 컴파일하여 만든 ActivatableHelloImpl_Stub.class 스텁 파일을 추가로 애플릿의 클래스 경로에 복사하기만 하면 됩니다.
애플릿에서 다음 문자열을 볼 수 있습니까?
HelloServer said "Hello, Remotely Activatable World!"
1.4 동적 클래스 다운로드
RMI는 스텁 파일이나 인자로 넘겨진 클래스 파일을 동적으로 다운로드할 수 있도록 하고 있습니다. 즉, 클라이언트가 원격 객체를 lookup할 때 스텁 파일이 클라이언트 쪽에 없을 경우 지정된 URL을 통해 찾게 되는데 http url로 되어 있으면 해당 서버의 웹서버로부터 자동으로 다운로드하여 로컬에 설치하게 됩니다.
이 때 사용하는 것이 java.rmi.server.codebase 옵션입니다. 아래 실행 예는 앞에서 만든 rmi unicast server을 실행하는 커맨드 라인에서 java.rmi.server.codebase 옵션을 http url로 주는 예입니다. 이 때 클라이언트가 로컬 클래스경로에 스텁 클래스 파일이 없을 경우 지정된 url로부터 해당 스텁 클래스를 다운로드를 시도하게 됩니다. 직접 테스트해보시기 바랍니다.
java -Djava.security.policy=server.policy -Djava.rmi.server.codebase=http://java.freehosting.co.kr/tutorial/rmi/hello/server/ HelloImpl
2.CORBA
자바의 원격 메쏘드 호출은 네트웍을 가로질러 자바 객체들 간의 통신을 가능하게 해주는 프로토콜입니다. 이에 반해 공용 객체 요청 브로커 아키텍처 즉, CORBA는 네트웍을 가로질러 다양한 언어로 작성된 객체들 간의 통신을 가능하게 해줍니다. 즉, 상대방 객체가 자바 객체가 아니라 C/C++, 코볼과 같은 언어로 작성된 객체일 때에도 객체 수준의 통신을 가능하게 해주는 것입니다.
CORBA의 또하나 중요한 특징은 OMG(Object Management Group의 약자)라는 여러 회사와 조직들로 구성된 컨소시엄에서 개발하고 발전시켜나가는 개방형 표준이라는 점입니다. OMG는 1989년에 설립된 이후 여러 객체 지향 소프트웨어 표준과 지침을 만들어 컴포넌트 기반의 소프트웨어 개발을 도모하는 일을 해왔습니다. CORBA는 이 노력의 산물 중 하나로, 분산 환경의 객체 지향 시스템을 개발하는 표준 아키텍처를 제공합니다. 즉, CORBA는 어떤 특정 언어로 작성된 클라이언트 객체가 다른 언어로 작성된 원격지의 서버 객체의 메쏘드를 호출하는 표준적인 방법을 제공하는 것입니다.
OMG의 공식 URL은 http://www.omg.org입니다. 여기에서 CORBA를 비롯한 여러 가지 객체 표준에 대한 정보 및 관련된 공개 소프트웨어들을 구할 수 있습니다.
2.1 CORBA를 사용한 원격 객체 통신
CORBA는 원격 객체 통신을 위해 RMI의 경우와 비슷한 방식으로 스텁과 스켈러튼을 사용합니다.
CORBA에서 스텁은 원격 객체에 대응되는 지역의 대리자이며 서버 객체와 같은 인터페이스를 구현하고 있지만 클라이언트와 같은 지역 호스트에 존재합니다.
스켈러튼은 구현된 서버 객체에 대한 원격지의 인터페이스입니다. 서버 객체와 같은 호스트에 존재하며 구현된 서버 객체와 다른 객체 사이의 인터페이스 역할을 합니다.
스텁과 스켈러튼은 객체 요청 브로커(ORB)를 통해 연결됩니다. ORB는 CORBA 객체들이 서로 통신할 수 있는 분산 환경을 제공하는 프로세스와 라이브러리 등의 통칭입니다.
ORB는 그림에서 볼 수 있듯이 스텁으로부터의 메쏘드 호출을 스켈러튼으로 전달합니다. 이때 서버와 같은 호스트에 존재하는 객체 어댑터(OA)라는 특별한 객체가 사용됩니다. 객체 어댑터는 필요할 때 서버 객체를 활성화시키고 서버 객체 동작 관리를 돕는 역할을 합니다. 객체 어댑터는 자바 RMI의 원격 등록부와 비슷한 역할을 한다고 생각하면 됩니다.
<그림> CORBA를 사용한 원격 객체 통신
2.2 IDL과 Java의 매핑
CORBA는 프로그래밍 언어로부터 독립적으로 원격 통신에 사용되는 객체를 정의하여 사용합니다. CORBA는 객체를 정의하기 위해 별도의 인터페이스 정의 언어(IDL)를 사용합니다. 자바를 비롯한 각종 프로그래밍 언어로 이 객체를 이용하려면 IDL로 정의된 코드를 각 프로그래밍 언어 소스 코드로 변환해야 합니다.
IDL 객체를 자바 소스 코드로 변환하는 프로그램인 idltojava 프로그램은 다음 URL에서 다운로드할 수 있습니다. JDK 1.3에는 idlj라는 이름으로 포함되어 있습니다.
http://java.sun.com/products/jdk/idl/
RMI에서 원격 객체를 정의하기 위해 원격 인터페이스를 정의하듯이 CORBA를 사용하려면 먼저 IDL 언어로 된 원격 객체 인터페이스를 정의하여야 합니다.
module hello // module은 자바의 package에 매핑 { interface Hello // interface는 자바의 interface로 매핑 { string hello(); // string은 자바의 java.lang.String 클래스로 매핑 }; };
<예제> hello.idl
IDL의 각 구성요소가 어떻게 자바 언어로 매핑되는지를 보려면 표를 참조할 수 있습니다.
IDL 구성 요소 |
Java의 대응되는 표현 |
module |
package |
interface |
interface, helper 클래스, holder 클래스 |
constant |
public static final |
boolean |
boolean |
char, wchar |
char |
octet |
byte |
string, wstring |
java.lang.String 클래스 |
short, unsigned short |
short |
long, unsigned long |
int |
long long, unsigned long long |
long |
float |
float |
double |
double |
enum, struct, union |
class |
sequence, 배열 |
배열 |
exception |
클래스 |
readonly attribute |
속성의 값을 구하는 메쏘드 |
readwrite attribute |
속성의 값을 구하고 또 설정하는 메쏘드 |
연산 |
메쏘드 |
<표> IDL의 자바 매핑
hello.idl 파일을 idltojava 컴파일러로 컴파일하면 모듈 이름에 해당하는 디렉토리 구조를 생성한 후 다음과 같이 다섯 개의 자바 파일을 생성합니다.
JDK 1.3의 idlj를 사용하실 때에는 옵션으로 -fall을 주셔야 클라이언트, 서버쪽 스텁을 모두 생성합니다.
Hello.java : 원격 객체 인터페이스 파일 HelloHelper.java : narrow() 메쏘드 등 보조 기능을 제공하는 클래스 HelloHolder.java : 자바에 없는 개념인 CORBA의 out, inout 인자에 대한 연산을 지원하기 위한 보조 클래스 _HelloImplBase.java : 서버 스켈러튼 클래스 (서버쪽에만 필요) _HelloStub.java : 클라이언트 스텁 클래스 (클라이언트쪽에만 필요)
이 중 직접 hello.idl 파일과 직접 매핑되는 자바 인터페이스는 Hello.java입니다. 이 파일의 내용을 보면 hello.idl이 어떻게 자바 코드로 변환되었는지 확인할 수 있습니다.
package hello; public interface Hello extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity { String hello(); }
<예제> 생성된 Hello.java 소스 코드
2.3 CORBA를 사용한 원격 객체 구현
hello.idl을 컴파일하여 생긴 자바 코드를 사용하여 원격 객체를 구현해봅시다. 먼저 실제 원격 서버 역할을 할 HelloServant 클래스를 구현합니다.
RMI와 비교하여 특이한 점은 원격 서버 객체가 직접 Hello 인터페이스를 구현하는 것이 아니라, Hello 인터페이스를 구현한 스켈러튼 클래스인 _HelloImplBase 추상 클래스를 상속하여 구현해야 한다는 것입니다.
class HelloServant extends _HelloImplBase // 서버쪽 스켈러튼 클래스를 상속 { // IDL의 인터페이스에 선언된 메소드를 실제로 구현 public String hello() { return "Hello, CORBA!"; } }
<예제> HelloServant 클래스
원격 서버 객체를 설치하려면 ORB에 등록하고, 또 네임 서비스에 바인드하는 과정이 필요합니다. RMI에서 rmiregistry에 바인드하고 특정 포트로 익스포트하는 것을 연상하면 이해하기 쉽습니다.
ORB를 초기화하여 원격 서버 객체를 등록하는 과정은 다음과 같습니다.
// 먼저 ORB를 초기화하여 인스턴스를 생성합니다. ORB orb = ORB.init(args, null); // 서비스를 할 원격 객체를 생성하여 ORB에 등록합니다. HelloServant helloRef = new HelloServant(); orb.connect(helloRef);
네임 서비스에 바인드하는 과정은 다음과 같습니다. CORBA의 네임 서비스는
// 네임 서비스에 대한 초기 참조를 구합니다. org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef); // 네임 서비스에 "HelloServer"라는 문자열을 바인딩합니다. // 네임 서비스는 가장 꼭대기에 "NameService"라는 고정된 문자열을 위치시키고 // 그 아래로 문자열들을 바인드하는 트리 구조를 가지고 있습니다. NameComponent nc = new NameComponent("HelloServer", " "); NameComponent path[] = {nc}; ncRef.rebind(path, helloRef);
ORB 등록과 네임 서비스에 바인드가 성공하였으면 클라이언트가 접속할 때까지 프로세스가 종료하지 않도록 무한히 기다리면 됩니다.
java.lang.Object waiter = new java.lang.Object(); synchronized(waiter){ waiter.wait(); }
클라이언트의 경우에는 먼저 ORB를 초기화한 다음 네임 서비스에서 "HelloServer"로 등록된 객체에 대한 참조를 구하여 메쏘드를 호출하면 됩니다. 네임 서비스로부터 원격 객체에 대한 참조를 구하는 데에는 NamingContext 클래스의 resolve() 메쏘드를 사용하고, 이 참조를 실제의 객체로 형 변환하는 데에는 IDL 컴파일러가 생성한 HelloHelper 클래스의 narrow() 메쏘드를 사용합니다.
// 먼저 ORB를 초기화하여 인스턴스를 생성합니다. ORB orb = ORB.init(args, null); // 이름 서비스에 대한 참조를 구합니다. org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef); // 네임 서비스에서 "HelloServer"에 대한 참조를 구하여 // 서버쪽 Hello 객체에 대한 참조를 얻습니다. NameComponent nc = new NameComponent("HelloServer", " "); NameComponent path[] = {nc}; Hello helloRef = HelloHelper.narrow(ncRef.resolve(path)); // 서버쪽 Hello 객체의 hello() 메소드를 호출합니다. String Hello = helloRef.hello(); System.out.println(Hello);
CORBA를 사용한 HelloServer와 HelloClient의 전체 소스 코드는 소스 디렉토리를 참조하기 바랍니다.
2.4 CORBA를 사용한 원격 객체 실행
JDK 1.2에는 자바로 구현된 ORB가 포함되어 있습니다. 보통 ORB는 여러 가지 객체 서비스를 제공하는데 JDK 1.2의 ORB는 네임 서비스만 제공합니다. 네임 서비스를 제공하는 유틸리티는 tnameserv로 RMI의 rmiregistry와 비슷한 일을 합니다. tnameserv는 기본 포트로 900번을 사용합니다. 특정 포트를 지정하려면 -ORBInitialPort 인자를 사용하면 됩니다. 900번이 1024보다 작기 때문에 대부분의 유닉스 시스템에서는 슈퍼유저 권한을 가져야만 기본 포트로 tnameserv를 실행할 수 있습니다.
먼저 tnameserv를 실행합니다.
start tnameserv
다른 도스 창을 열어 서버를 실행합니다. 서버를 실행할 때에는 IDL 컴파일러가 생성한 파일 중 스텁을 제외한 인터페이스, 보조 클래스와 스켈러튼 클래스의 클래스 파일들이 함께 있어야 합니다.
java HelloServer
다른 도스 창을 열어 클라이언트를 실행합니다. 클라이언트 실행에는 IDL 컴파일러가 생성한 파일 중 스켈러튼을 제외한 인터페이스, 보조 클래스, 그리고 스텁 클래스의 클래스 파일들이 함께 필요합니다.
java HelloClient
이제 그 결과를 볼 수 있을 것입니다.
Hello, CORBA!
2.5 RMI와 CORBA
RMI와 CORBA는 모두 원격 객체와의 통신을 가능하게 하는 프로토콜이라는 점에서 같은 역할을 한다고 볼 수 있습니다. 그렇기 때문에 프로그래밍을 하다보면 RMI를 사용할 것인가, CORBA를 사용할 것인가 고민할 경우도 생깁니다.
이럴 경우에는 여러 가지 조건을 따져볼 필요가 있습니다.
만약 순수한 자바 객체들 간의 통신이라면 굳이 CORBA를 사용할 필요는 없을 것입니다. CORBA를 통해 넘겨진 객체들은 조작할 수 없지만 RMI를 통해 넘겨진 객체들은 자바 객체이므로 자유롭게 처리할 수 있습니다. 따라서, 자바 객체들 간의 통신에서는 RMI를 사용하는 것이 훨씬 유리합니다.
반면 CORBA는 자바가 아닌 다른 언어로 만들어진 객체와의 통신을 가능하게 합니다. 또, 전송 프로토콜인 IIOP(인터넷 ORB간 프로토콜)가 개방형 표준이기 때문에 표준에 기반한 객체 통신을 위해서는 CORBA를 선택해야 합니다.
팁: idltojava 컴파일러가 동작하지 않을 때 idltojava 컴파일러는 전처리기를 필요로 합니다. WIN32 시스템의 idltojava 컴파일러는 기본값으로 비주얼 C++의 전처리기를 사용합니다. 비주얼 C++가 설치되어 있지 않거나 전처리기를 사용하지 않으려면 다음과 같이 옵션을 주면 됩니다. idltojava -fno-cpp Hello.idl JDK 1.3에는 idlj라는 이름으로 포함되어 있습니다. 이 툴의 사용법은 http://java.sun.com/products/jdk/1.3/docs/guide/rmi-iiop/toJavaPortableUG.html를 참고하시면 됩니다. |