java-school logo
Last Modified 2014.8.27

RMI

일반적으로 객체의 메서드 호출은 같은 JVM 환경의 객체와 객체 사이에서 이루어진다. 다른 JVM에서 동작하고 있는 객체의 메서드를 호출할 수 있을까? 우리는 이미 소켓에 대해 알고 있다. 소켓을 이용한다면 서버와 클라이언트가 사용하는 프로토콜을 개발해야 하지만 RMI를 이용한다면 프로토콜을 정해야 하는 수고 없이 다른 JVM에서 활동하고 있는 객체의 메서드를 호출할 수 있다. 즉, 소켓을 이용하는 경우처럼 프로토콜에 맞게 메시지를 만들고, 메시지를 해석할 필요가 없다.

RMI 프로그래밍 방법

  1. 원격 인터페이스를 정의
  2. 서버 구현
  3. 클라이언트 구현
  4. 자바 RMI 레지스트리 시작 , 서버 시작, 클라이언트 시작

RMI 첫 번째 예제는 다음 클래스로 구성된다.

1. 원격 인터페이스 정의 - Hello.java

원격 인터페이스에서는 클라이언트에서 원격으로 호출할 수 있는 메서드를 정의한다. 원격 인터페이스는 java.rmi.Remote를 상속해야 하며, 원격 인터페이스의 원격 메서드는 throws java.rmi.RemoteException을 선언해야 한다. 이때 RemoteException 이외에 다른 예외도 추가할 수 있다.

Hello.java
package example.hello;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello extends Remote {
	String sayHello() throws RemoteException;
}

2. 서버 구현 - Server.java

Server.java는 원격 인터페이스 Hello.java를 구현한다. 원격 인터페이스의 sayHello()를 구현하는데 RemoteException 예외를 선언할 필요가 없다. 왜냐하면 원격 객체의 sayHello()가 RemoteException 객체를 던지지 않기 때문이다.

Server.java
package example.hello;

import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;

public class Server implements Hello {

	public Server(){}
	
	public String sayHello() {
		return "안녕하세요 홍길동입니다.";
	}
	
	public static void main(String[] args) {
		Server obj = new Server();
		try {
			Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0);
			
			// Bind the remote object's stub in the registry
			Registry registry = LocateRegistry.getRegistry();
			registry.bind("Hello", stub);
			System.out.println("Server ready");
		} catch (Exception e) {
			System.out.println("Server exception: " + e.toString());
		}
	}

}

원격 객체는 자바 RMI 런타임으로 익스포트export되어야 한다. 이 과정을 통해 서버에 해당하는 RMI 런타임RMI Runtime에 스텁이 만들어진다. 이를 수행하는 코드는 아래와 같다.

Server obj = new Server();
Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0);
RMI 런타임
RMI 런타임(서버 측 JVM의 일부분으로 동작한다)은 특정 포트에서 들어오는 RMI 요청을 수신하고, 수신할 때 네트워크를 통해 온 메서드 호출을 원격 객체에 전달한다.

위 코드로 만들어진 서버 측 스텁을 클라이언트에서 찾을 수 있도록 자바 RMI 레지스트리에 등록한다. 다음은 이를 수행하는 코드다.

Registry registry = LocateRegistry.getRegistry();
registry.bind("Hello", stub);

LocateRegistry의 getRegistry() 메서드가 인자 없이 호출된다면 디폴트 포트인 1099를 사용한다. 따라서 테스트를 위해 1099 포트를 개방해야 한다.

3. 클라이언트 구현

클라이언트는 서버 측에 등록된 스텁을 등록된 이름으로 찾아서 클라이언트 측 JVM에 다운로드한다. 그 후 스텁의 sayHello() 메서드를 호출한다.

package example.hello;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Client {

	private Client() {}

	public static void main(String[] args) {
		String host = (args.length < 1) ? null : args[0];
		
		try {
			Registry registry = LocateRegistry.getRegistry(host);
			Hello stub = (Hello) registry.lookup("Hello");
			String response = stub.sayHello();
			System.out.println("response: " + response);
		} catch (Exception e) {
			System.err.println("Client exception: " + e.toString());
			e.printStackTrace();
		}
	}
}

4. 테스트

윈도

윈도 시스템에서 자바 클래스 파일 위치가 C:/java/rmi/bin이라면 명령 프롬프트에서 C:/java/rmi/bin으로 이동한다. C:/java/rmi/bin에서 다음을 차례로 실행한다.

start rmiregistry

start java -classpath c:/java/rmi/bin ^
-Djava.rmi.server.codebase=file:c:/java/rmi/bin ^
example.hello.Server

위에서 ^는 윈도 커맨드 명령어에서 줄바꿈 문자이다. 새로운 명령 프롬프트를 띄우고 클라이언트를 실행한다. 이때 디렉터리 위치는 상관없다.

java -classpath c:/java/rmi/bin example.hello.Client

리눅스

리눅스 시스템에서의 자바 클래스 파일 위치가 /home/kim/java/rmi/bin이라면 터미널을 실행하고 /home/kim/java/rmi/bin으로 이동한다. /home/kim/java/rmi/bin에서 다음을 실행한다.

rmiregistry &

java -classpath /home/kim/java/rmi/bin \
-Djava.rmi.server.codebase=file:/home/kim/java/rmi/bin \
example.hello.Server &

위에서 역슬래시는 리눅스 bash의 줄바꿈 문자이다. 새로운 명령 프롬프트를 띄우고 아래처럼 클라이언트를 실행한다. 이때 디렉터리는 어디든 상관없다.

java -classpath /home/kim/java/rmi/bin example.hello.Client

컴퓨터가 2대 이상에서 테스트

서버 IP가 192.168.0.8이라면 클라이언트에서 아래처럼 실행한다.

java -classpath /home/kim/java/rmi/bin example.hello.Client 192.168.0.8

위의 예는 윈도 시스템이 서버이고 리눅스 시스템이 클라이언트가 되는 경우이다. 클라이언트 측에서는 Hello와 Client의 바이트 코드가 있어야 한다. 서버 측에서는 Hello와 Server의 바이트 코드가 있어야 한다.

테스트 실패시 체크 리스트

  1. 윈도 시스템에서 테스트가 실패할 경우 loopback adapter를 사용 중지한 후 시도한다.
  2. 윈도를 서버로 테스트할 때 실패했다면 1099(RMI 디폴트 포트) 포트를 개방한 후 시도한다.
  3. 리눅스를 서버로 테스트할 때 실패했고 공유기를 사용하는 경우라면, /etc/hosts 파일에서 127.0.0.1을 모두 리눅스가 실제 할당받은 사설 IP로 변경한 후 테스트한다.
참고