Java - 14. 네트워크: RMI

갓김치·2020년 10월 15일
0

고급자바

목록 보기
36/47
post-custom-banner

참고

RMI: Remote Method Invocation

  • 로컬 컴퓨터에서 원격 컴퓨터의 메서드를 호출하는 기술
  • 내부적으로는 TCP방식의 소켓프로그래밍이 돌고 있는 것

장점

  • 구현하기 쉽다
  • 신뢰성이 보장된다
    • TCP방식의 장점
  • Java 플랫폼을 사용한다.

아키텍쳐

Stub (클라이언트)

  • 실제 통신작업을 처리하는 객체
  • 소켓 및 스트림과 관련된 작업을 처리
  • 클라이언트에서 호출하려고 하는 메소드들을 가지고 있는 객체인척 하는 객체

Skeleton (서버)

  • 소켓 연결을 통해 클라이언트 보조 객체(stub)에 보낸 요청을 받아서 실제 서비스 객체에 있는 메서드를 호출
  • 서비스 객체로부터 리턴값을 받아서 포장해서 다시 클라이언트 보조 객체(Stub)로 보낸다.

방식

  • 클라이언트가 서버의 registry에 등록된것을 lookup()으로 찾아낼 수 있다
  • 찾아낸 정보를 가지고 클라이언트가 원격으로 호출할 수 있음 (멀리있는 시스템에서 실행된 결과를 받아낼 수 있음)

예제

TestVO.java

  • RMI에서 데이터 전달용으로 사용할 클래스
  • 네트워크로 객체를 보낼 것이기 때문에 직렬화가 필요
    • Serializable을 구현해야함
      • Serializable: Marker Interface (메서드 하나도 없고 단지 표식만 하는 역할
public class TestVO implements Serializable {
  private String testId;
  private int testNum;

  public String getTestId() {
      return testId;
  }

  public void setTestId(String testId) {
      this.testId = testId;
  }

  public int getTestNum() {
      return testNum;
  }

  public void setTestNum(int testNum) {
      this.testNum = testNum;
  }
}

FileInfo.java

  • 역시 직렬화 필요
public class FileInfoVO implements Serializable{
  private String fileName;
  private byte[] fileData;

  public String getFileName() {
      return fileName;
  }

  public void setFileName(String fileName) {
      this.fileName = fileName;
  }

  public byte[] getFileData() {
      return fileData;
  }

  public void setFileData(byte[] fileData) {
      this.fileData = fileData;
  }
}

RemoteInterface.java

  • 서버와 클라이언트가 서로 가지고 있는 메서드를 공유할 목적으로 원격에서 호출할 수 있도록 만든 인터페이스
    • 클라이언트 입장에서는 이 인터페이스만 알고 있으면 원격에 있는 객체를 가지고 오고싶을 때 인터페이스 방식대로 호출만 하면 되서 좋당
  • 인터페이스 작성 규칙
    • Remote 인터페이스 상속받아야함
      • 이 인터페이스에 있는 메소드는 로컬이 아닌 원격에서 사용할 수 있어야 한다는 것을 명시하기 위해
      • Remote: 마커 인터페이스
    • 무조건 public
      • 원격 접속이 가능하여야 하기 때문
    • 메소드는 무조건 RemoteException 처리
      • 원격처리를 하다가 인터넷상에서 예외가 발생할 수 있기 때문
  • 인터페이스 기반 코딩은 유지 보수가 좋다!
public interface RemoteInterface extends Remote { 
  public int doRemotePrint(String str) throws RemoteException;

  public void doPrintList(List<String> list) throws RemoteException;

  public void doPrintVO(TestVO vo) throws RemoteException;

  // 파일 전송을 위한 메서드
  public void setFiles(FileInfoVO[] fInfo) throws RemoteException;
}

RemoteServer.java

  • 인터페이스 구현 클래스 작성 규칙
    • 무조건 public
    • RMI용 인터페이스를 구현해야함
      • RMI용 서비스를 제공하는 객체이기때문
    • UnicastRemoteObject클래스 상속
      • 해당 통신의 하부 구조를 일부 구현해주고 이 클래스가 객체의 직렬화를 만들어주기 때문
    • RemoteException 처리를 위해 디폴트 생성자 정의
      • 상속받은 UnicastRemoteObject 생성자에서 해당 예외가 발생하기때문
public class RemoteServer extends UnicastRemoteObject implements RemoteInterface {

  // 기본생성자는 자동으로 RemoteException을 throw할 수 없음. 해줘야함
  protected RemoteServer() throws RemoteException {
    super;
  }

  public static void main(String[] args) {
    try {
      // 1. 구현한 RMI용 객체를 클라이언트에서 사용할 수 있도록 RMI서버(Registry)에 등록

      // 1-1. RMI용 인터페이스를 구현한 원격객체 생성
      RemoteInterface inf = new RemoteServer();

      // 1-2. 구현한 객체를 클라이언트에서 찾을 수 있도록 Registry객체를 생성해서 등록한다
      //     - 포트번호를 지정하여 Registry 객체 생성 (기본포트값: 1099)
      Registry reg = LocateRegistry.createRegistry(8888);

      // 1-3. Registry서버에 제공하는 객체 등록
      // 형식) Registry객체변수.rebind("객체Alias", 원격객체변수);
      reg.rebind("server", inf);
      // bind도 쓸 수 있으나 bind는 이미 bind되어 있으면 잘 안될때가 있어서 확실한 rebind 사용
      // 아무것도 bind 되어있지 않은 상태에서도 rebind 사용 가능

      System.out.println("RMI서버가 준비되었습니다");
    } catch (RemoteException e) {
      e.printStackTrace();
    }
  } // main()
  
  @Override
  public int doRemotePrint(String str) throws RemoteException {
    int length = str.length();
    System.out.println("클라이언트에서 보내온 메시지 : " + str);
    System.out.println("출력 끝...");
  }
  
  @Override
  public void doPrintList(List<String> list) throws RemoteException {
    System.out.println("클라이언트에서 보낸 List값들...");
    for (int i = 0; i < list.size(); i++) {
      System.out.println((i+1)+"번째 : " + list.get(i));
    }
    System.out.println("List 출력 끝...");
  }
  
  @Override
  public void doPrintVO(TestVO vo) throws RemoteException {
    System.out.println("클라이언트에서 보내온 TestVO객체의 값 출력");
    System.out.println("testId : " + vo.getTestId());
    System.out.println("testNum : " + vo.getTestNum());
    System.out.println("TestVO객체 출력 끝...");
  }
} // class

@Override
public void setFiles(FileInfoVO[] fInfo) throws RemoteException {
  FileOutputStream fos = null;
  String dir = "d:/C_Lib/"; // 파일이 저장될 위치
  System.out.println("파일 저장 시작...");

  for (int i = 0; i < fInfo.length; i++) {
    try {
      fos = new FileOutputStream(dir + fInfo[i].getFileName());

      // 클라이언트에서 전달한 파일데이터(byte[])를 서버측에 저장한다.
      fos.write(fInfo[i].getFileDate()); // writes binary data
      fos.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  System.out.println("파일 저장 완료...");
}

RemoteClient.java

  • RMI 규칙
    • 클라이언트의 프로젝트에도 서버의 패키지와 같은 구조로 interface와 VO파일이 있어야 한다
    • Client가 interface를 알아야 Server객체를 잘 이용할 수 있음
    • 패키지명, 클래스명 전부 동일하게
public class RemoteClient {
  public static void main(String[] args) {
    try {
      // 1. 등록된 서버를 찾기 위해 Registry 객체를 생성한 후 사용할 객체를 불러온다.
      Registry reg = LocateRegistry.getRegistry("127.0.0.1",8888);
      // 패키지 구조 및 다른 구조가 완전히 동일하단 가정하에.

      RemoteInterface inf = (RemoteInterface) reg.lookup("server");
      // Remote의 객체로 리턴해주기때문에 다운캐스팅해준다.
      // RemoteInterface는 Remote를 상속받고 있기때문에 캐스팅이 가능하다.

      // 2. 이제부터는 불러온 원격객체의 메서드를 호출해서 사용할 수 있다.
      int a = inf.doRemotePrint("떤땡님 졸려요");
      System.out.println("반환값=> " + a );
      System.out.println("---------------------");

      List<String> list = new ArrayList<String>();
      list.add("꽉자바");
      list.add("자바칩프라푸치노");
      list.add("가나다라자바사");
      inf.doPrintList(list);
      System.out.println("List 호출 끝...");
      System.out.println("---------------------");

      TestVO vo = new TestVO();
      vo.setTestId("담임땜");
      vo.setTestNum(1004);
      inf.doPrintVO(vo);
      System.out.println("VO 출력 메서드 호출 끝...");
      System.out.println("---------------------");

      // 파일 전송하기
      File[] files = new File[2];
      files[0] = new File("d:/D_Other/Tulips.jpg");
      files[1] = new File("d:/D_Other/out.txt");

      FileInfoVO[] fInfo = new FileInfoVO[files.length];

      // 2개의 파일을 읽어서 byte[]에 담아 서버측 메서드에 전달
      FileInputStream fis = null;
      for (int i = 0; i < files.length; i++) {
        int len = (int) files[i].length(); // 파일 사이즈
        fis = new FileInputStream(files[i]);
        byte[] data = new byte[len];

        fis.read(data); // 파일 크기씩 파일 내용을 읽어 byte배열에 저장

        fInfo[i] = new FileInfoVO();
        fInfo[i].setFileName(files[i].getName());
        fInfo[i].setFileData(data);
      }

      inf.setFiles(fInfo); // 서버의 파일 저장(출력) 메서드 호출
      System.out.println("파일 전송 작업 끝...");
      System.out.println("---------------------");
    } catch (Exception e) {
      e.printStackTrace();
    }
  } // main()
}

결과

profile
갈 길이 멀다
post-custom-banner

0개의 댓글