자바 객체와 XML간의 변환에관해서는 몇 가지 라이브러리들이 존재하고 있습니다만 이번 시간에는 Java SDK에 기본으로 포함되어 있는 java.beans.XMLDecoder와 java.beans.XMLEncoder를 이용하여 자바객체<->XML간 변환 방법을 알아보겠습니다.

java.beans.XMLDecoder와 java.beans.XMLEncoder클래스는 J2SE 1.4 버전부터 이용할 수 있습니다.

우선, XML로 변환하고 XML로부터 복원할  적당한 자바 클래스를 작성합니다.보시면 아시겠지만 int 배열과 String, Point, ArrayList 객체를 갖는 전형적인 자바빈 객체 하나를 만들었습니다.

이제, 이 SampleBean 클래스를 xml로 변환하고 변환한 xml로 부터 SampleBean객체를 복원하는 코드를 작성합니다.

 

package xml.test;

 

import java.awt.Point;

import java.util.ArrayList;

 

/**

 *

 * @author 신윤섭

 */ 

public class SampleBean {

    private int[] scores;

    private String name;

    private Point seat;

    private ArrayList entry;

 

    public int[] getScores() {

        return scores;

    }

 

    public void setScores(int[] scores) {

        this.scores = scores;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public Point getSeat() {

        return seat;

    }

 

    public void setSeat(Point seat) {

        this.seat = seat;

    }

 

    public ArrayList getEntry() {

        return entry;

    }

 

    public void setEntry(ArrayList entry) {

        this.entry = entry;

    }

 

}

 

보시면 아시겠지만 int 배열과 String, Point, ArrayList 객체를 갖는 전형적인 자바빈 객체 하나를 만들었습니다.
이제, 이 SampleBean 클래스를 xml로 변환하고 변환한 xml로 부터 SampleBean객체를 복원하는 코드를 작성합니다.

 

package xml.test;

 

import java.awt.Point;

import java.beans.XMLDecoder;

import java.beans.XMLEncoder;

import java.io.BufferedInputStream;

import java.io.BufferedOutputStream;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.util.ArrayList;

 

/**

 *

 * @author 신윤섭

 */

public class BeanXmlTranslator {

 

       public static void main(String args[]) throws Exception {

 

              // xml 변환할 클래스를 생성

              SampleBean sample = new SampleBean();

              // 파라메터를 세팅해 봅시다.

              sample.setScores(new int[] { 100, 90, 75 }); // int[]

              sample.setName("Gore"); // String

              sample.setSeat(new Point(5, 3)); // java.awt.Point

              // ArrayList String Point 넣어보구요..

              ArrayList entry = new ArrayList();

              entry.add("우리는");

              entry.add("하나다");

              entry.add(new Point(1, 1));

              sample.setEntry(entry);

 

              // .. 객체를 XML 변환 시킵니다.

              XMLEncoder encoder = new XMLEncoder(new BufferedOutputStream(new FileOutputStream("C:\\Sample.xml")));

              encoder.writeObject(sample);

              encoder.close();

              // 객체 레퍼런스를 찍어보구요..

              System.out.println(sample);

 

              // 이젠 XML에서 객체로 복원시켜 봅니다.

              XMLDecoder decoder = new XMLDecoder(new BufferedInputStream(new FileInputStream("C:\\Sample.xml")));

              SampleBean sample2 = (SampleBean) decoder.readObject();

              decoder.close();

              // 객체 레퍼런스를 찍어보구요..

              System.out.println(sample2);

       }

}

 

예.. 변환의 핵심은 XMLEncoder 클래스의 writeObject() 메소드와 XMLDecoder  클래스의 readObject() 메소드에 있습니다.

객체를 xml로 변환한 결과를 한번 볼까요?

 

 

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.5.0_13" class="java.beans.XMLDecoder">
 
<object class="com.yunsobi.beanxmltranslate.SampleBean">
 
<void property="entry">
   
<object class="java.util.ArrayList">
   
<void method="add">
     
<string>우리는</string>
   
</void>
   
<void method="add">
     
<string>하나다</string>
   
</void>
   
<void method="add">
     
<object class="java.awt.Point">
     
<int>1</int>
     
<int>1</int>
     
</object>
   
</void>
   
</object>
 
</void>
 
<void property="name">
   
<string>Gore</string>
 
</void>
 
<void property="scores">
   
<array class="int" length="3">
   
<void index="0">
     
<int>100</int>
   
</void>
   
<void index="1">
     
<int>90</int>
   
</void>
   
<void index="2">
     
<int>75</int>
   
</void>
   
</array>
 
</void>
 
<void property="seat">
   
<object class="java.awt.Point">
   
<int>5</int>
   
<int>3</int>
   
</object>
 
</void>
 
</object>
</java>

 

객체의 타입과 할당된 값이 깔끔한 xml문서로 작성된것을 확인 하실 수 있습니다.

자바에는 객체 직렬화(Serialize) 란게 있지요..
위 XMLDecoder와 XMLEncoder가 직렬화 기능을 이용하는건지 아닌지 확인 해 볼까요?
가장 정확한 방법은 XMLDecoder와 XMLEncoder의 소스를 보는것이겠지만 이미 위 예제 소스만으로도
충분히 예상 하실 수 있습니다. 위 예제의 SampleBean 이 Serializable 하지 않음에도 xml문서로 생성되었습니다.
예.. XMLDecoder와 XMLEncoder 내부적으로 java Reflection을 이용하여 xml, 객체간 변환을 수행하고 있습니다.

java Reflection을 이용한다고 했습니다. 이는 getXXX, setXXX 메소드가 없다면 해당 변수를 xml로 만들어내지
못하고 객체로 복원해 내지도 못함을 의미합니다.

또 하나, XMLEncoder를 통해 변환된 xml은 문서의 형태로 보아도 외부 시스템과의 정보의 교환보다는
객체의 상태저장/복원을 목적으로 사용하는게 더 어울릴거란 생각이 드네요.

 

출처 : http://www.yunsobi.com/blog/search/XMLDecoder [섭이의 다락방]

'Java > Java for XML' 카테고리의 다른 글

XML-RPC  (0) 2013.02.01
Posted by Steven J.S Min
,

Java Servlet Spec 2.3 부터 지원되는 필터는 request 또는 response 에 포함된 정보를 변형하거나 사용하기 위해 사용됩니다.

 

필터 사용의 가장 큰 이득은 반복적인 작업을 재사용 가능한 단위로 캡슐화 할 수 있는 것입니다.

 

필터를 사용하여 다음과 같이 다양한 형태의 기능들을 수행할 수 있습니다.

  • 사용자 정보를 근거로 요청 인증 및 요청 차단
  • 웹 어플리케이션의 유저 작업 추적(로깅 및 감사)
  • Data compression
  • Localization
  • XLST 를 사용하여 XML 데이터의 변형
  • Encryption
  • mime-type 변경
  • Caching

 

Filter API 구성

  • javax.servlet.Filter Interface
    filter 는 Filter 인터페이스를 구현하는 것으로서 정의됩니다.
  • javax.servlet.FilterChain Interface
    filter chain 은 필터 통과 및 필터 시리즈를 호출하기 위한 수단을 제공합니다.
  • javax.servlet.FilterConfig Interface
    filter config 는 초기화 데이터를 가지고 있습니다.

 

Programming Filter

 

Filter 인터페이스에서 가장 중요한 메서드는 doFilter() 입니다.

doFilter() 내에서 다음과 같은 작업들을 수행하도록 구현합니다.

  • request 헤더 검사
  • request 헤더 또는 데이터의 변경 및 request 차단
  • response 헤덩 또는 데이터의 변경
  • filter chain 에서 다음 필터 호출
  • chain 에서 다음 필터가 호출된 이후에 response 헤더 검사
  • 처리 과정 중에 발생한 에러의 Exception 을 throw

그리고 Filter 인터페이스의 init(), destory() 메서드를 구현해야 합니다.
init() 메서드는 필터가 인스터스화 되어 될 때 컨테이너에 의해서 호출됩니다.
초기화 파라미터를 전달하기 위해서는 FilterConfig 에서 초기화 파라미터를 검색하여 init 에 전달합니다.


 

Example: Request Character Encoding
많은 브라우저들이 HTTP request 의 Content-Type 헤더에서 character encoding 정보를 보내지 않습니다.
클라이언트 요청에 character encoding 이 설정되어 있지 않으면, 컨테이너는 default encoding 을 사용하여 request parameter 를 파싱합니다.
클라이언트가 character encoding 을 설정하지 않고 request parameter 가 컨테이너의 기본 character encoding 이 아닌 다른 encoding 으로 되었으면 파라미터 값은 올바르게 파싱되지 않습니다.

이번 샘플은 필터를 사용하여 모든 request 에 강제적으로 올바른 character encoding 을 설정하는 방법을 제공합니다.

 

[SetCharacterEncodingFilter.java]

package filters;


import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;


public class SetCharacterEncodingFilter implements Filter {
    protected String encoding = null;

    protected FilterConfig filterConfig = null;

    protected boolean ignore = true;


    public void destroy() {

        this.encoding = null;
        this.filterConfig = null;

    }

 

      // Servlet은 chain.doFilter() 기준으로 실행전과 실행후로 나뉜다.

      // chain.doFilter() 이전은 Servlet 실행 전 이후는 Servlet 실행 후로 request와 response를 설정.
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {

        // Conditionally select and set the character encoding to be used
        if (ignore || (request.getCharacterEncoding() == null)) {
            String encoding = selectEncoding(request);
            if (encoding != null)
                request.setCharacterEncoding(encoding);
        }

        // Pass control on to the next filter
        chain.doFilter(request, response);

    }



    public void init(FilterConfig filterConfig) throws ServletException {

        this.filterConfig = filterConfig;
        this.encoding = filterConfig.getInitParameter("encoding"); // web.xml 내의 필터 설정값 구함
        String value = filterConfig.getInitParameter("ignore");
        if (value == null)
            this.ignore = true;
        else if (value.equalsIgnoreCase("true"))
            this.ignore = true;
        else if (value.equalsIgnoreCase("yes"))
            this.ignore = true;
        else
            this.ignore = false;

    }



    protected String selectEncoding(ServletRequest request) {

        return (this.encoding);

    }
}

 

[web.xml]

    <filter>
        <filter-name>Set Character Encoding</filter-name>
        <filter-class>filters.SetCharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>EUC_KR</param-value>
        </init-param>
    </filter>

 

    <filter-mapping>
        <filter-name>Set Character Encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

 

출처 : http://blog.naver.com/dalbong97?Redirect=Log&logNo=130026519237 그리고 약간편집

'Java > Java for the Web' 카테고리의 다른 글

A Simple Filter  (0) 2013.02.12
Cookie정보 보기  (0) 2013.02.12
Session정보 보기  (0) 2013.02.12
Hello Servlet  (0) 2013.02.12
SSL 통신원리  (0) 2013.02.03
Posted by Steven J.S Min
,

 

웹을 이용한 전자 상거래, 인터넷 뱅킹 등이 더욱더 증대되면서 이제 인터넷은 정보의 바다가 아니라 하나의 커다란 시장이라 부를 수도 있는 상황이다. 온라인 입금이나 최근에는 소액의 경우 휴대폰 결재를 선택하기도 하지만 뭐니뭐니 해도 국내 인터넷 쇼핑 이용자들이 가장 선호하는 수단은 신용 카드다. 그런데 바로 이점이 보안적인 허점을 가져오는 원인이 있다.

실제로 신용카드로 결제하는 경우 인터넷이라는 공용망을 타고 서버에 전송되는 고객의 신용카드 정보는 신용카드 번호, 유효기간 등을 포함하고 있기 때문에 실제 전자 상거래 필요한 대부분의 데이터가 언제 어떻게 도용될지도 모르는 위험에 노출되어 있는 것이다. 그렇다면 개인의 금융정보를 공용망인 인터넷상에서 보호할 것인가?

질문에 대한 답으로 현재 가장 많이 사용되는 방법이 SSL(Secure Socket Layer)이다.

1. SSL(Secure Socket Layer) 개념

SSL Netsape에서 개발된 프로토콜로서 1995 히크만(Hickman) 의해서 개발되었으며 현재 인터넷 사용자들에게 안전한 개인 정보를 교환하기 위한 사실상의 표준 프로토콜로 인정되어 많은 온라인 상거래에 사용되고 있다. SSL 실제로 다양한 장점을 지닌 암호화 기법들을 사용해 세계 각국에서 사용되는 대부분의 암호화 기법들을 지원할 있다.

SSL 버전 3.0 이후 IETF(Internet Engineering Task Force)에서 표준화되어 TLS(Transport Layer Security) 명명되었지만 실제 내용은 SSL 3. TLSv1.0 같으며 MS Explorer Netscape 대부분의 브라우저에서 지원하고 있다. SSL 프로토콜에서 사용되어질 있는 다양한 애플리케이션 프로토콜이 있지만 주로 사용되는 부분은 WEB(HTTP)이라고 있다.

SSL 크게 3가지 기능들을 제공함으로 공개되어 있는 인터넷상에서 일어나는 트랜잭션의 기밀성(Privacy) 보장한다.

○ Site Authentication

User 선택한 상대편 Web Site 인증한다는 의미다. 우리가 인터넷 뱅킹이나 인터넷 쇼핑몰을 사용할 상대편이 실제 신뢰할 있는 은행이나 쇼핑몰의 웹서버 인지를 인증한다는 것으로, 상대 사이트에 대한 신뢰성 있는 인증이 없다면 우리는 불확실한 누군가에게 우리의 금융정보를 넘기는 위험에 처하게 된다.

○ Data Privacy(기밀성)

전달되는 데이터가 도중에 누군가에 의해 판돈되지 않는 다는 것을 보장한다. SSL 다양한 암호화 알고리즘을 사용하여 인터넷을 통해 전송되는 개인의 사적인 정보를 외부로부터 불법적인 판독을 막는다.

○ Data Integrity(무결성)

사용자의 브라우저로부터 상대방 웹서버까지 전달되는 동안 데이터가 도중에 누군가에 의해 변경되지 않도록 보장한다.

SSL 위와 같은 요소들을 보장하여 보다 안전한 커뮤니케이션을 있도록 도와주며 또한 MS 익스플로러나 넷스케이프 등과 같이 널리 보급된 대부분의 웹브라우저와 웹서버들에서 지원된다. 웹브라우저가 SSL 통신을 하는지 여부는 브라우저 오른쪽 하단의 잠금쇠 표시를 가지고 판별할 있는데 경우에 따라서는 브라우저 대신에 다른 보안 애플리케이션이 대신하기도 한다.

HTTPS SSL상에서 HTTP 구현한 형태로 실제 HTTP 기본 포트 80 사용하는 대신 HTTPS 433 포트를 사용한다.

SSL 다양한 어플리케이션들을 지원하기 위해 각각의 응용된 어플리케이션 프로토콜을 가지고 있는데, 이는 SSL 구조를 보면 이해하기 쉽다. SSL OSI 7 계층에서 5 계층인 Session Layer 속하며, 지원 가능한 프로토콜은 어플리케이션 레이어 상에 위한 하부 프로토콜로 한정될 밖에 없는데 HTTP, IMAP, FTP, NNTP 등이다.

 

2. SSL 동작 원리

SSL 수행되는 단계는 3가지로 분류된다.

 ○ SSL Server Authentication

사용자의 웹브라우저가 상대방의 웹서버를 인증하는 단계이다.

SSL 지원하는 웹브라우저는 표준 공용키 암호화 기법을 사용하여 서버의 인증서와 공용 ID 실제로 브라우져가 신뢰하는 인증기관(Trusted CA)으로부터 발급받았는지 여부를 인증하는 기능을 내장하고 있다.

 ○ SSL Client Authentication

웹서버가 자신에게 요청한 클라이언트를 인증하는 단계이다.

서버 인증 시에 사용했던 동일한 기법으로 인증하는데 서버에 내장된 SSL 지원 소프트웨어나 서버 앞에 배치된 SSL 하드웨어는 클라이언트의 인증서와 공용 ID 실제로 서버가 신뢰하는 인증기관(Trusted CA)으로부터 발급 받았는지 여부를 인증하는 기능을 내장하고 있다.

○ Encrypt Connection

서로에 대한 인증단계 이후 정상적으로 종결되면 클라이언트와 서버사이에 교환되는 모든 데이터는 사적인 내용을 보호하기 위한 암호화를 요구받는다. 또한 SSL커넥션을 통해 암호화된 데이터 역시 전송 변경을 방지(Message Integrity)하기 위해 Hash 알고리즘이라고 불리는 기술에 의해 보호된다.

위의 3가지 단계를 기반으로 실제로 구현되는 순서는 다음과 같으며, 다음 순서를 진행하기에 앞서 SSL 또한 TCP 프로토콜에 기반을 두고 있으므로 반드시 TCP 3 Handshake 이루어져 있어만 한다.

A. 클라이언트는 서버에게 Client Hello Message 전송

일반적으로 브라우저를 통해 서버에게 SSL 연결 요청을 하기 위한 초기 단계이다.

B. 서버는 클라이언트로 Server Hello Message 서버 인증서를 전송하며, 만약 클라이언트 인증서가 필요한 경우에 인증서 요청도 함께 전송

서버는 클라이언트가 자신이 적합한 서버인지를 인증할 있도록 공신력 있는 기관으로부터 발급받은 자신의 공인 인증서를 발송한다. 이때 일반적으로 서버 인증서와 함께 서버의 공용키가 클라이언트측에 전달된다. 만약 클라이언트에 대한 인증을 필요로 하는 트랜잭션이라면 이에 대한 요청도 함께 발송한다.

C. 클라이언트는 암호화에 사용되는 세션 키와 함께 클라이언트에서 지원하는 Cipher Suite 서버로 전송하며, 서버가 인증서를 요청한 경우에는 클라이언트의 인증서도 함께 전송

서버의 인증서에 대해 클라이언트는 브라우저내에 저장되어 있는 신뢰기관으로부터의 발급여부를 확인하고 암호화에 사용될 Session Key 생성해 서버 공용키로 Session Key 암호화 하여 서버에게 전달한다. 또한 암호화된 Session Key 함께 브라우저가 지원할 있는 Chiper Suite, 암호화 기법 리스트와 함께 서버측에서 클라이언트의 인증서를 요청한 경우 스스로의 인증서를 발송한다.

D. 서버는 Chiper Suite 받아들이고(또는 거부하고) Finished message 클라이언트로 전송한 데이터 전송단계로 이동

서버는 클라이언트로부터 클라이언트 브라우저가 지원하는 암호화 기법 리스트를 받고 클라이언트에게 종결 메시지를 보내고 데이터 전송단계로 돌입한다. 여기까지 정상적으로 완료가 되면 클라이언트가 생성한 Session Key 클라이언트와 서버가 모두 공유하게 된다.

E. 클라이언트는 최종메시지를 서버로 전송하고 데이터 전송단계로 이동

단계까지 정상적으로 완료되면 클라이언트는 종결 메시지를 서버에 보내고 실제 데이터를 전송하기 위한 단계로 돌입한다.

F. 상호 합의한 사이퍼 수트에 의해서 암호화된 메시지를 교환

단계에서 서로 나누어 가진 Session Key 암호화 데이터를 교환하게 된다.

 

3. SSL 암호화

SSL 수행되는 단계에 보면 Session Key라는 것이 등장한다. Session Key 한마디로 클라이언트 측에서 생성하여 서버로 전달된 하나의 키로, 하나의 동일한 키를 클라이언트와 서버가 각각 보관함으로 이후 전달되는 암호화된 데이터를 복호화 있도록 한다. Session Key 생성 전달 과정에 대한 보다 깊은 이해는 사실상 암호화 기법에 대한 이해를 바탕으로 한다.

이번에는 주로 SSL 대한 개략적인 부분만 다루고 있기 때문에 암호화에 대한 부분은 간단하게만 살펴보도록 하겠다.

SSL 수행단계에서 교환되는 Session Key 비밀키 암호화(Secret key cryptography). , 대칭적 암호화에서 사용되는 키로서 하나의 키를 양쪽 상대방이 각기 나누어 가짐으로써 하나의 키로 암호화한 데이터를 송신측에서 전송하면 수신측에서 암호화 사용한 동일한 키로 복호화하는 단순한 구조를 가진다.

비밀키 암호화 기법은 사용이 간단하고 속도가 빠른 반면 크게 가지 문제를 가지고 있다.

첫번째는 어떻게 동일한 키를 서버와 클라이언트가 서로 공유하여 가질 것인가이며,

두번째는 서버측에서 하나의 클라이언트당 각기 다른 세션키가 필요하기 때문에 어떻게 키를 관리할 것인가다.

위와 같은 문제 때문에 등장한 것이 공개키 암호화 비대칭키 암호화 기법이다. 암호화 기법은 비밀키와 공개키라고 불리는 각기 다른 키로 구성된 쌍의 키를 클라이언트와 서버가 나누어 가지고 비밀키로 암호화된 데이터는 그와 같은 쌍이 공개키로만 복호화되는 알고리즘을 제공하고 있다 암호화 기법은 비밀키 암호화 발생하는 교환의 문제를 해결해 주었다. 그러나 각기 나누어 갖은 쌍의 키를 가진 상대방에 대한 인증에 대한 문제와 암호화/복호화 과정에 많은 부하가 걸리는 단점을 가지고 있다. 따라서 실제로 SSL 수행의 경우 가지 암호화 기법과 PKI 기반의 디지털 인증서를 사용한 인증을 혼합하여 사용할 있다.

먼저 디지털 인증서(Digital Certificate) 교환을 통해 상대방을 인증, 공개키 암호화의 약점을 줄였으며 서버에서 클라이언트로 전달된 서버의 공개키로 클라이언트에서 생성된 세션키를 암호화하여 서버로 전달하는 공개키 암호화 기법을 사용하여 비밀키 암호화의 교환 문제를 없앴고 세션키가 전달된 세션키를 통해 실제 데이터를 암호화/복호화함으로 공개키 암호화 발생하는 부하로 인한 서비스 지연현상을 방지할 있다.

참고로 SSL에서 세션키를 통해 암호화하는 암호화 기법으로는 DES, 3DES, RC2, RC4등이 있고 40비트부터 168비트까지 사용된다. 메시지 무결성(Message Integrity) 보장을 위해 사용되는 Hash 알고리즘으로는 MD5 SHA1.등이 주로 사용 된다.

 출처: http://yagi815.tistory.com/168 그리고 약간의 편집



3. SSL/TLS, HTTP 통신과정

1. 항상 키쌍으로 작성된다.(Public, Private)


2. Public로 암호화된 정보는 Private키로 해독가능하며, Private키로 암호화된 정보는 Public키로 해독가능한 기본적인 특징이있다.

3. 보통 Public키는 모두가 가지고 있을수있는 공개된 키이며, Private키는 보안을 지키려고하는 주체(서버)에만 있는 공개되어서는 안되는 키다.

4. 서버는(Server A)는 Public, Private쌍을 만든다.

5. 신뢰할 수 있는 CA 기업을 선택하고 그 기업에 내 공개키를 관리해달라고 계약하고 돈을 지불한다.

6. 계약을 완료한 CA 기업은 CA 기업만의 공개키와 개인키가 있다.
   CA기업은 기업의 이름과 Server A의 공개키, 공개키의 암호화 방법 등의 정보를 담은 인증서를 만들고,
   해당 인증서를 CA기업의 개인키로 암호화해서 Server A에 제공한다.(이 요청을 위해서 사용하는것이 CSR이다.)
   - CA기업에서 암호환것은 Public키로로 암호화 했기때문에 모두가(브라우저가)알고있는 Public키로 해독가능하기 때문에 암호화의 의미는 없지만, 대신  CA 기업에서 생성했다는 증명이되기때문에
     서명의 효과로서 사용하게되는것이다.


7. Server A는 암호화된 인증서를 가지게 되고, Server A에 Request가 오면 클라이언트에게 인증서를 줍니다.
   (이 인증서에는 CA에서 만들때 기업 Server A의 Public키가 포함되어있다는것을 상기하자 )

8. 클라이언트 입장에서는 Server A의 정보를 CA 기업의 개인키로 암호화한 인증서를 받게 됩니다.

9. CA 기업의 공개키는 브라우저는 알고 있다.

10. 브라우저는 CA 기업 리스트를 쭉 탐색해서 공개키로 인증서를 해독하여 Server A의 공개키를 얻습니다.

11. Server A의 공개키로 암호화해서 Request를 날리게 된다.(이때 다음부터의 통신을 간소화하기위해 대칭키를 사용하는데 이를 위해서 pre master secret키를 생성해서 같이 보낸다.)

12. 이때부터 양 종단에는 pre master secret를 이용해서 암/복호 화가 이루러진다.




 

'Java > Java for the Web' 카테고리의 다른 글

A Simple Filter  (0) 2013.02.12
Cookie정보 보기  (0) 2013.02.12
Session정보 보기  (0) 2013.02.12
Hello Servlet  (0) 2013.02.12
Servlet Filter 개요  (2) 2013.02.03
Posted by Steven J.S Min
,

서창근 <chang@webdox.co.kr>

2001년 3월 7일

 

개요
객체지향 소프트웨어 개발
현대 소프트웨어 개발 개념 중 근래에 가장 많은 주목을 받고 있는 것이 바로 객체지향 개발이다. C와 같은 순차적인 개발 언어는 프로그램이 시작에서 끝까지 순서대로 실행되지만 객체지향 개발 개념은 각 프로그램이 객체로 구성되어 있고 그 객체 간의 메세지 전송으로 실행된다. 현재 자바로 대변되는 객체지향 개발 개념은 소프트웨어 공학분야에 많은 발전을 가져왔다. 물론 자바가 객체지향 개밸 개념을 구현하고 있는 유일한 언어는 아니다. 실제 객체지향 개발 개념은 꽤 오래전부터 Smalltalk 등에서 구현되었으며 얼마 전까지만 해도 C++가 대표적인 존재였다. 그러나, 현재 가장 인기가 많고 주목을 받고 있는 언어가 자바인 것만은 사실이며 사실상 C++는 개발의 어려움 등으로 인해 자바만한 대중성은 얻지 못하고 있다.

 


분산시스템
소프트웨어 개발에서 가장 주목받고 있는 것이 객체지향 개발이라면 시스템분야에서 가장 주목받는 것은 분산시스템의 개념이다. 어쩌면 이 분산시스템의 개념이 객체지향 개발을 절대적으로 필요하게 했을지도 모른다. 


분산시스템이란 기본적으로 다수의 컴퓨터가 하나의 시스템처럼 작동하며 사용자에게 그 차이를 느끼지 못하게 하는 시스템을 말한다. 분산시스템의 중요한 개념은 명령실행의 투명성 (transparency), 효율성 (efficiency), 일관성 (consistency), 유연성 (flexibility), 그리고 robustness 등을 들 수 있는데 그 중 가장 많이 강조하는 것이 투명성이다. 

즉, 사용자가 어떤 명령을 실행시켰을 경우 그 명령이 지금 사용자가 사용하고 있는 컴퓨터에서 실행되고 있는지, 아니면 네트워크에 연결된 다른 컴퓨터에서 실행되고 있는지 분간할 수 없음을 말한다. 대표적인 분산시스템으로 UNIX의 NFS (Network File System), AFS (Andrew File System), CODA 등이 있다.

 

이러한 시스템에서 중요한 것은 바로 시스템에 소속되어 있는 컴퓨터 간의 통신이다. 더욱 자세하게 말하면 각 컴퓨터에서 실행되고 있는 프로그램, 또는 프로세스들이 서로 통신이 가능해야 한다는 것이다. 이 프로그램 사이의 통신을 가능하게 하는 것 중 현재 UNIX에서 가장 많이 사용되고 있는 통신 프로토콜이 바로 RPC (Remote Procedure Call)이다. 

RPC는 네트워크에 연결되어 있는 다른 컴퓨터에 존재하는 함수를 사용자가 눈치채지 못하도록 실행해 줌으로써 분산시스템의 구현을 가능하게 했다. 그러나, RPC는 객체지향 개발개념을 구현하지는 않으며 단지 다른 컴퓨터에 저장되어 있는 순처적인 언어로 개발된 애플리케이션의 함수를 부를 수 있도록 해주는 역할을 할 따름이다.

 

RMI (Remote Method Invocation) 란?
위에서 객체지향 개발 개념을 설명할 때 프로그램이 객체 간의 메세지 전송으로 실행된다고 했다. 그렇다면 만약 이 객체들이 서로 다른 컴퓨터에 존재한다면 어떻게 해야할까? 즉, 예전의 분산시스템의 개념은 서로 다른 프로그램들이 다른 컴퓨터에 존재하고 실행되는 것이였다면 근래엔 각 객체가 분산되어 존재하는 개념인 것이다. 특히 현대 시스템 개발은 쓰리티어 시스템 (three-tier system)이 주류를 이루므로 데이터베이스, 애플리케이션, 그리고 클라이언트가 모두 객체를 통하여 구현되고 따로 저장되는 일이 많아졌다. 이렇게 분산되어 존재하는 객체간의 메세지 전송(메쏘드를 부르는 것과 동일함)을 가능케 해주는 것이 바로 RMI 프로토콜이다. 

그리고, 객체지향 개발 개념을 기본으로 RMI의 기능과 transaction, security등 엔터프라이즈급 기능을 포괄적으로 콤포넌트 모델화한 것이 바로 EJB (Enterprise JavaBeans)가 되겠다. 일반적으로 자바 RMI라고 하면 자바 애플리케이션 간의 통신을 가능케 해주는 프로토콜로 생각하나 실제로 RMI 자체는 자바 RMI 이전부터 쓰여지던 단어이며 더욱 포괄적인 의미를 지닌다. RMI는 분산객체 개념을 구현하는 CORBA 또는 DCOM 등에서 분산객체 간의 통신을 구현하는 모든 프로토콜을 의미한다. CORBA나 DCOM은 이 글의 범위 밖이므로 접어두고 이번 글에선 특히 자바 애플리케이션 간의 RMI에 대해 집중적으로 살펴보도록 하자.

 

왜 RMI를 사용하는가?
네트워크 프로그래밍을 해보신 독자라면 곧 이런 의문을 가지게 될 것이다. 왜냐하면 많은 독자들이 지금까지 데이터 전송을 위해서 소켓(socket)을 사용해 왔기 때문이다. 그러나, RMI를 배우고 나면 아마도 최소한 자바 애플리케이션에서 소켓을 사용하고자 하는 개발자는 별로 없을 것이다. 


프로그래밍의 기본은 데이터의 전송이다. RMI는 분산객체 간의 데이터 전송을 메쏘드를 부른는 것과 같은 방법으로 실현시킨다. 소켓을 다루어 본 독자라면 벌써 알겠지만 메쏘드를 부르는 것과 소켓으로 데이터를 보내는 것 중 어느 것이 더 편한가? 당연히 메쏘드를 부르는 것이 훨씬 더 편하다. 똑같은 데이터 전송 방법의 일환인데 더 어렵고 불편한 방법을 쓸 필요가 없는 것이다.


지금부터 자바 RMI의 기본적인 사용 방법을 예제를 통하여 살펴보고 더 이상 소켓 프로그래밍에 구애받지 않는 더욱 강력하고 세련된 개발자가 되어보도록 하자. 특히 자바 RMI는 EJB의 통신 중추이므로 RMI를 이해하고 있으면 EJB를 공부하는데도 더욱 수월하다. 현재 EJB를 공부하고 있거나 나중에 배우기를 원하는 독자들에겐 RMI의 기본 개념의 이해는 필수적이라 하겠다.

 


RMI에 관해서

RMI의 기본개념
RMI가 자바에서 어떻게 구현되는지 살펴보기 전에 먼저 RMI가 어떻게 가능한지 살펴보기로 하자. RMI의 기본적인 이론을 먼저 공부하는 것이 나중에 자바 RMI를 공부할 때 이해하기 쉽다. 그리고 뭐든지 그렇지만 기반 이론을 이해하는 것이 나중에 문제가 발생하였을 경우 문제를 알아내고 해결하기 쉽기 때문이다. RMI의 개념을 자바 중심으로 서술하였지만 다른 RMI 기법들도 이와 비슷하다고 보면 된다.

 

그렇다면 도대체 RMI에서 어떻게 한 객체가 다른 컴퓨터에 존재하는 객체에게 메세지를 전송할 수 있을까? 가장 기본적인 문제는 그 객체가 다른 객체(다른 컴퓨터에 존재하는)의 존재를 알아야 하며, 그 객체에게 메세지를 전송하는 방법을 알아야 하고, 그리고 마지막으로 그 객체로부터 메세지의 답변을 받아야 한다는 것이다. 이 과정에서 수반되는 문제는 메세지를 전송하고 답변을 받을때 어떠한 종류의 데이터를 주고 받아야 하는가이다. RMI는 위에 나열된 문제를 두 가지의 방법으로 해결한다. 첫째가 스텁(stub)이며 둘째가 parameter marshalling 이다.

 

스텁(stub) 과 Parameter Marshalling
설명을 위해서 메쏘드를 부르려고 하는 객체가 존재하는 컴퓨터를 클라이언트라고 하고 그 메쏘드를 실행하는 객체가 존재하는 컴퓨터를 서버라고 하자. 일반적인 의미의 클라이언트와 서버와는 약간 다르므로 혼동하지 말기를 바란다. 여기선 누가 누구의 메쏘드를 부르냐에 따라 서버와 클라이언트의 역할이 바뀔수도 있다. 

클라이언트의 객체가 서버에 존재하는 객체의 메쏘드를 부르려고 할 경우 그 메쏘드의 존재를 알려주고 추상적으로 나타내 주는 것이 바로 스텁이다. 이 스텁은 실제로 인터페이스의 형태로 서버에 있는 객체가 원격적으로 부를 수 있는 메쏘드를 공개하는 것으로 서버에 저장되어 있으며 클라이언트의 객체가 서버에 있는 메쏘드를 부르려고 할 때 클라이언트로 전달이 된다. 스텁 클래스는 원래 서버의 객체로부터 생성이 되지만 클라이언트에 머무르면서 클라이언트가 서버의 메쏘드를 부를 수 있도록 도와준다. EJB에서는 이것을 원격 인터페이스 (remote interface)라고도 한다.

 

스텁이 하는 중요한 일은 두 가지가 있다. 첫째는 당연히 서버에 존재하는 메쏘드를 불러줘야 하고 둘째는 그 메쏘드의 인자를 일정한 포맷으로 바꿔주는 것이다. 이렇게 인자를 일정한 포맷으로 바꿔주는 일을 parameter marshalling 이라고 한다. 즉, 인자를 조율하고 정리하는 역할을 하는 것이다. 왜 이런 parameter marshalling 이 필요한 것일까? 컴퓨터개론을 공부해 본 독자들은 알겠지만 모든 컴퓨터시스템에서 데이터 표현방법이 약간씩 다르다는 것을 알 것이다. 예를 들어 어떤 시스템은 big-endian으로 표현하고 어떤 시스템은 little-endian으로 표시한다. 정수가 차지하는 메모리의 양도 다를 수 있다. 자바는 플랫폼과는 독립적으로 작동하는 언어이다. 만약 한 객체가 윈도우 시스템에서 작동하고 다른 객체는 유닉스 환경에서 작동하고 있다면 이러한 데이터의 구현 방법을 통일해 줄 필요가 있다. 특히 쓰리티어시스템(three-tier system)의 경우 데이터베이스 서버, 애플리케이션 서버, 그리고 클라이언트가 일일히 다른 플랫폼으로 구현될 수도 있으므로 데이터의 구현방법을 일관적으로 표현하는 일은 아주 중요하다. 그 일이 바로 paramter marshalling 이다.

 

Parameter marshalling 이 끝났으면 스텁은 서버에 존재하는 객체의 메쏘드를 부르게 된다. 이때 스텁이 서버에 보내는 정보는 서버 객체의 아이디, 실행해야할 메쏘드, 그리고 변환된 인자이다. 클라이언트 쪽에서 스텁이 대리인의 역할을 하듯 서버쪽에서도 이러한 대리인의 역할을 하는 존재가 있는데 수신자(receiver) 또는 스켈레톤(skeleton, 주: 해골 또는 골격이라고 부르기엔 좀 너무하다는 생각이 들었다.) 이라고도 한다. 

수신자가 하는 역할은 스텁의 반대라고 생각하면 된다. 조율된 인자들을 다시 그 서버에 맞는 형태로 바꿔준 뒤 스텁이 보낸 아이디의 객체가 갖고 있는 알맞는 메쏘드를 실행시킨다. 그리고 실행이 끝났으면 결과를 다시 parameter marshalling 을 통하여 변환한 뒤, 다시 스텁에게 보내준다. 스텁은 이 결과를 받아서 다시 시스템에 맞게 변환시킨 뒤, 원래 메쏘드를 불렀던 객체에게 결과를 넘겨준다. 물론 결과가 예외일 수도 있다.

 

동적 클래스 로딩 (Dynamic Class Loading)
그렇다면 여기서 또 하나의 의문점이 생기게 된다. 만약 인자가 일반적인 데이터 타입, 즉 숫자나 문자등일 경우엔 parameter mashalling이 간단하지만 만약 어떠한 객체라면 어떻게 할까? 특히 결과값이 서버에 존재하고 있는 또 다른 원격객체라면 클라이언트엔 그 객체의 코드가 없으므로 문제는 심각하다. 예를 들어 서버에 Computer라는 객체가 실행되고 있다고 하자. 이 객체의 메쏘드 중에 getType()이라는 메쏘드가 있는데 컴퓨터의 종류를 추상화하는 ComputerType이라는 객체를 돌려준다고 하자. 즉, 클라이언트에서 다음과 같이 실행시킬 수 있다는 얘기가 되겠다.

 

Computer remote_comp = new Computer();
ComputerType mycomp = (ComputerType)remote_comp.getType();
mycomp.someMethod();

 

문제는 여기서 getType() 메쏘드를 불렀을 때 돌아오는 값이 어떤 숫자나 문자가 아니라 서버에 존재하는 객체라는 것이다. 그리고 클라이언트는 이 새로운 원격객체의 someMethod()라는 메쏘드를 불러주고 있다. 물론 클라이언트는 함수값으로 돌아오는 객체가 ComputerType이라는 객체라는 것은 알지만 실제로 돌아오는 객체는 ComputerType의 서브클래스일 수도 있다. 게다가 이 새로운 객체가 서버에 정의돼 있는 새로운 예외객체를 필요로 할 수도 있다. 물론 이 새로운 객체의 someMethod()라는 메쏘드를 부르려면 클라이언트가 이 새로운 객체에 대해서 알고 있어야 한다. 이 문제를 해결하기 위해 클라이언트에 이 객체를 정의해주고 저장하는 방법도 있고 서버와 동시에 일일히 클라이언트를 업데이트 해주는 것도 방법이겠지만 새로운 종류의 ComputerType을 서버에 정의할 때마다 일일이 클라이언트를 업데이트 할 수는 없다. 그래서 필요한 것이 바로 동적 클래스 로딩이다.

 

동적 클래스 로딩은 이렇게 클라이언트가 알지 못하는 객체가 필요할 경우 서버에서 그때 그때 필요할 때마다 클라이언트로 그 필요한 객체의 스텁클래스를 불러오는 것을 말한다. 자바의 애플릿과 비슷한 개념이라고 생각하면 되겠다. 위의 예제의 경우 자동으로 ComputerType의 서브클래스의 스텁클래스가 클라이언트로 로딩되어 someMethod()를 부르는것을 가능케 한다. 하지만 이렇게 다른 컴퓨터에서 네트워크를 통하여 스텁클래스를 불러올 경우 보안의 문제가 발생할 수 있으므로 실제로 이러한 시스템을 가동시킬 땐 꼭 보안에 신경을 써서 자바 보안 매니저 (Java Security Manager)등을 사용하는 것이 좋다.

 



자바 RMI 프로그래밍

자바에서의 RMI 구현
이제 RMI의 기본적인 개념과 수반되는 이슈들에 대해 알아 봤으므로 본격적으로 자바 RMI에 대해 공부해 보기로 하자. 자바 RMI를 구현하기 위해선 일단 클라이언트와 서버에 적절한 코딩을 해야 하는데 자바 RMI는 개발자에게 편리하게 개발되었으므로 실제로 코딩은 중요한 문제가 아니다. 하지만, RMI라는 것이 근본적으로 간단한 문제가 아니므로 실제로 RMI를 실행시킬 수 있는 환경을 만드는 것이 실제 코딩보다 더 복잡하다. 일단 클라이언트와 서버에서 코딩하는 방법을 알아보고 RMI를 실행시킬 때 필요환 환경과 프로그램, 그리고 주의사항 등을 알아보도록 하자.

 

서버 쪽 프로그래밍
일단 클라이언트 쪽에서는 서버에 존재하는 객체와 통신을 하기 위해서 인터페이스를 정의해 주어야 한다. 클라이언트의 객체가 서버의 객체가 가지고 있는 메쏘드를 부를 때 이 인터페이스를 사용하여 부르게 된다. 이 인터페이스는 서버와 클라이언트 양쪽에 존재하게 된다. 예를 들어 비행기 좌석을 예약하는 프로그램을 만든다고 하자. 만약 이 좌석을 서버에 존재하는 객체로 표현하고 클라이언트가 이 좌석이 현재 빈 좌석인지 알아보려고 한다고 하자. 그렇다면 일단 다음과 같이 인터페이스를 정의해주어야 한다.


import java.rmi.Remote;

import java.rmi.RemoteException;

 

/**

 * 서버와 클라이언트에 존재하는 인테페이스

 *

 * @author Steven J.S Min

 *

 */

public interface Seat extends Remote {

       public boolean isReserved(int seat_number) throws RemoteException;

}

 

 

여기서 주의해야 할 것은 모든 원격 인터페이스는 Remote 클래스를 상속하여야 하고 원격적으로 공개되는 메쏘드는 모두 RemoteException 예외클래스를 던져야 한다는 것이다. 이 인터페이스를 하나의 자바파일로 만들어서 서버와 클라이언트 양쪽에 저장하면 되겠다.

이렇게 인터페이스를 정의했으면 다음엔 실제 객체를 구현해야 한다. 여기선 다른 메쏘드는 배제하고 isReserved() 메쏘드만 구현해 보기로 하겠다.

 

import java.rmi.Naming;

import java.rmi.RemoteException;

import java.rmi.server.UnicastRemoteObject;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

 

/**

 * 서버측 코드

 *

 * @author Steven J.S Min

 *

 */

public class SeatImpl extends UnicastRemoteObject implements Seat {

 

       @Override

       public boolean isReserved(int seat_number) throws RemoteException {

 

              // 데이터베이스에 저장되어있는 예약결과를 불러온다.

              // 데이터베이스에 벌써 연결이 되어있다고 하자.

              String query = "SELECT reserved FROM seats WHERE plane = ? AND seat_no = ?";

              PreparedStatement ps = connection.prepareStatement(query);

              ps.setString(1, String.valueOf(plane_id));

              ps.setString(2, String.valueOf(seat_number));

 

              ResultSet rs = ps.executeQuery();

 

              boolean reserved = rs.getBoolean("reserved");

              return (reserved);

       }

 

       /**

        * @param args

        */

       public static void main(String[] args) {

 

              // 이름을 등록한다.

              try {

                     Seat seat = new SeatImpl();

                     Naming.rebind("seat", seat);

 

              } catch (Exception e) {

                     System.out.println("SeatImpl err: " + e.getMessage());

                     e.printStackTrace();

              }

 

       }

 

}

 

 

여기서 주의해야 할 것은 바로 서버객체가 UnicastRemoteObject 클래스를 상속한다는 것이다. 이 클래스가 바로 서버객체를 원격객체로 만들어주는 클래스가 되겠다. UnicastRemoteObject 를 사용하면 꼭 서버객체가 TCP/IP로 접근이 가능하여야 한다. 그 위에 정의했던 Seat 이라는 인터페이스를 구현하는 것도 잊지 말자.

 

다음으로 해야 할 것이 객체의 이름을 RMI Registry에 등록하는 것이다. JNDI (Java Naming and Directory Interface)의 일환이기도 한데 클라이언트에 있는 객체에게 서버객체의 존재를 알려주는 역할을 한다. 즉, 서버객체를 어떠한 이름하에 등록하여 클라이언트의 객체가 그 이름으로 원격객체를 찾을 수 있도록 해주는 것을 말한다. 클라이언트는 이 이름으로 서버객체를 찾으며 찾았을 경우 그 이름에 해당하는 서버객체의 스텁클래스가 클라이언트로 전송되어 서버객체의 메쏘드를 부르는 것을 가능케 한다. 특히 JNDI는 EJB에서도 사용되므로 개념을 이해해두면 EJB를 공부하기도 편하다.

 

위의 예제를 보면 seat이라는 이름 하나만 등록한 것을 알 수 있다. 실제로 비행기 좌석 예약 시스템을 만든다고 하자. SeatImpl 이라는 서버객체가 만약에 좌석 하나하나를 구현한다고 하면 그 좌석 하나하나를 다 등록할 수는 없는 일이다. 왜냐하면 이 이름은 클라이언트에게 서버객체의 존재를 알게 해주는 것이므로 클라이언트가 어떠한 메쏘드를 부르기 전에 벌써 등록이 되어 있어야 한다. 하지만 여기서 Naming.bind() 메쏘드의 인자 중 하나가 바로 SeatImpl 객체이다. 그렇다면 좌석 하나하나의 객체를 다 생성해서 그 이름들을 따로 등록해야 한다는 예기가 된다. 특히 좌석의 데이터가 데이터베이스에 저장되어 있을 경우 그것을 일일히 동적으로 불러와야 하는데 그렇다면 클라이언트가 메쏘드를 부르기 전에 그 이름을 등록한다는 것은 불가능하다. 이것은 상당히 비효율적인 개발방법이다. 여기선 SeatImpl 객체가 한 비행기의 모든 좌석을 구현하게 개발하는 것이 더욱 효율적이며 RMI Registry도 더 간단해 진다. seat 이라는 이름을 통하여 클라이언트에게 서버객체의 존재를 알려주고 SeatImpl 이라는 서버객체가 따로 각 좌석의 데이터를 데이터베이스로부터 불러오는 것이 더 효율적인 것이다. 게다가 RMI Registry는 모든 객체들이 공유하는 서비스이므로 한 프로그램이 다 독차지해서도 안된다.

 

클라이언트쪽 프로그래밍
위와 같이 서버쪽의 프로그래밍이 되어있으면 이젠 클라이언트쪽을 구현하면 된다. 일단 위에서 코딩한 인터페이스 파일이 필요하고 그 다음엔 그 인터페이스를 이용하여 서버객체의 메쏘드를 불러주면 되겠다.

 

import java.rmi.Naming;

import java.rmi.RemoteException;

import java.rmi.RMISecurityManager;

 

/**

 * @author Steven J.S Min

 *

 */

public class Reservation {

 

       // 자바 보안매니저는 벌써 다른곳에서 실행 시켰다고 생각하자.

       // 실제 실행은 다음과 같이 하면 된다.

       // 보통은 클라이언트의 main() 에서 실행시킨다.

       // if (System.getSecurityManager() == null) {

       // System.setSecurityManager(new RMISecurityManager());

       // }

       public boolean isReserved(int seat_numer) throws Exception {

              boolean result;

              try {

                     // 먼저 원격객체를 RMI Registry에서 찾아본다.

                     Seat seat = (Seat) Naming.lookup("rmi://server.com/seat");

                     // 원격객체를 찾는데 성공했으면 메쏘드를 부르자

                     result = seat.isReserved(seat_number);

 

              } catch (Exception e) {

                     throw new Exception(e.getMessage());

              }

              return result;

       }

 

}

 


먼저 클라이언트 프로그램이 실행되기 시작될 때 자바 보안매니저를 꼭 실행시키도록 하자. 만약 클라이언트가 자바 애플릿일 경우엔 자동으로 보안이 실행되므로 상관없지만 자바 애플리케이션이라면 꼭 실행시켜주도록 한다. 그리고 서버객체의 이름을 찾을때 URL을 사용하는것을 기억하도록 하자. "rmi://서버이름/등록된 객체이름" 의 형식이면 되겠다. 그리고 예외클래스는 꼭 처리하도록 해야한다. RMI가 네트워크를 통해서 이루어지므로 어떠한 에러가 발생할지 모르기 때문이다.

 


RMI의 실행
위와 같이 코딩이 끝났으면 이젠 프로그램을 실행하는 일만 남았다. 그러나 자바 RMI를 실행하기 위해선 약간의 복잡한 준비가 필요하다. 이렇게 서버와 클라이언트 사이를 연결해주는 고리가 필요한데 그것이 바로 rmiregistry 이다. rmiregistry 는 RMI를 사용하는 객체의 등록을 관장하는 역할을 한다. 이제부터 rmiregistry 의 사용방법과 함께 서버와 클라이언트가 RMI를 실행하려면 어떠한 과정을 거쳐야 하는지 알아보도록 하자.

 

서버
    1. 서버객체와 인터페이스 파일 컴파일하기

    2. rmic 를 이용하여 스텁클래스 생성

    3. rmiregistry 실행

    4. 서버 프로그램 실행


위에 나열한 것과 같이 실행하면 RMI를 사용할 수 있게 된다. 일단 인터페이스와 서버 프로그램을 자바 컴파일러로 컴파일 다. 처음에 서술했듯이 클라이언트객체가 서버객체를 사용하려면 스텁클래스가 있어야 한다. 이 스텁클래스를 생성하는 것이 rmic 프로그램이 되겠다. rmic 로 생성된 스텁클래스가 동적 클래스로딩을 통해서 클라이언트가 필요로할 때 클라이언트로 옮겨 가게 되는 것이다. rmic 는 다음과 같이 실행시키면 된다.

 

rmic SeatImpl

 

만약 자바 1.1 객체를 지원해야 한다면 -v1.2 옵션을 사용하면 된다. 클래스 이름은 패키지 이름까지 다 사용하여야 한다. 만약에 SeatImpl 클래스가 com.myairline 이라는 패키지의 일부라면

 

rmic com.myairline.SeatImpl

 

이라고 해야 한다. rmic 를 실행시키면 앞에서 말한 바와 같이 스텁 클래스와 스켈레톤 클래스가 SeatImpl_Stub.class, SeatImpl_Skel.class 라는 이름으로 생성된다. 하지만 스켈레톤 클래스는 자바 2에선 더이상 사용하지 않으므로 무시해도 좋다.

이렇게 스텁을 만들었으면 다음은 rmiregistry 를 실행시키는 일만이 남았다. 윈도우즈 서버에서는

 

다음은 윈도우와 유닉스 계열에서 실행하는 커맨드이다.

윈도우: start rmiregistry

유닉스: rmiregistry &


과 같이 실행시켜주면 된다. 여기서 rmiregistry 의 역할은 명확할 것이다. 클라이언트가 서버의 이름과 객체의 이름으로 서버객체를 찾으면 그 요구가 rmiregistry 로 오게 된다. 벌써 서버쪽에선 rmiregistry 에 서버객체가 등록되어 있으므로 그 서버객체의 스텁클래스가 클라이언트로 로딩된다. 그러면 클라이언트 객체는 그 스텁클래스를 이용하여 서버객체의 메쏘드를 부를 수 있게 된다.

이렇게 rmiregistry 가 실행 됐으면 서버 프로그램을 실행시켜주면 되겠다. 여기서 주의해야 할것은 서버프로그램을 서비스 프로그램으로 실행시켜야 한다는 것이다. 즉, rmiregistry 와 같은 방법으로 말이다. 위의 예제에선

 

start java SeatImpl

또는

java SeatImpl &


와 같이 실행시켜야 한다.

 

클라이언트
    1. 클라이언트와 인터페이스 파일 컴파일 하기

    2. 보안정책을 정의하는 파일 만들기

    3. 클라이언트 프로그램의 실행


서버와 마찬가지로 클라이언트 객체의 자바 파일과 인터페이스 파일을 자바 컴파일러로 컴파일 해준다. 여기서 인터페이스 파일은 서버에서 컴파일한 것과 같은 파일이어야 한다.


컴파일이 성공했으면 다음은 보안정책을 설정해주는 파일을 만들어야 한다. 파일이름은 아무래도 상관없다. 여기선 client_security.policy 라고 하겠다. 위에서 자바 보안매니저를 실행시킨 것을 기억할 것이다. 자바 보안매니저는 원래 어떠한 네트웍 연결도 허용하지 않는다. 그러므로 이러한 보안정책을 설정해 주어야 하는 것이다. 보안정책 파일엔 다음과 같이 해주면 되겠다.

grant

{

permission java.net.SocketPermission

"*:1024-65535", "connect";

};

  

여기선 RMI가 포트번호 1024에서 65535 사이의 아무 포트나 사용할 수 있도록 하였다. RMI의 기본설정은 1099이다. 보안정책 파일엔 이외에 다른 많은 정보가 들어갈 수 있는데 정책파일과 보안 매니저에 관한 자세한 정보는 다른 글들을 참조하길 바란다.

이렇게 보안정책이 설정되었으면 프로그램을 실행시키는 일만이 남았다. 프로그램은 다음과 같이 실행시키면 된다. 보안정책 파일을 지정해 주는 것을 잊지 않도록 하자.

 

java Reservation -Djava.security.policy=client_security.policy

 

동적 클래스로딩에 관해 주의할점
위에서 자바 RMI의 개념 중에 동적 클래스로딩이라는 것이 있었다. 클라이언트가 필요로 하는 스텁클래스를 그때그때마다 서버에서부터 받아오는 것을 말한다. 이 동적 클래스로딩 때문에 실제로 프로그램을 개발할 때 주의해야할 사항이 몇가지 있으므로 여기서 짚고 넘어가려고 한다.

위에서 언급한대로 클라이언트 객체는 원격 인터페이스를 통하여, 즉 스텁클래스를 통하여 서버객체의 메쏘드를 부르게 된다. 다시 말하면 클라이언트는 원격 인터페이스에 공개되어 있는 메쏘드밖에 부를 수 없다는 말이다. 서버객체 내에 다른 여러가지 메쏘드가 있을 수 있다. 하지만 원격 인터페이스에 공개되어 있지 않으면 클라이언트 객체는 그 메쏘드를 부를 수 없다.

 

예를 들어 클라이언트 객체가 서버객체의 어떠한 메쏘드를 불렀는데 그 값으로 원격객체를 상속한 어떠한 객체가 돌아왔다고 하자. 그리고 원격객체는 원격 인터페이스를 구현하지만 그 원격객체를 상속한 객체는 원격 인터페이스를 구현하지 않는다고 하자. 그렇다면 클라이언트 객체가 받은 이 객체는 어떠한 메쏘드의 실현이 가능할까? 답은 수퍼클래스가 구현한 원격 인터페이스에 공개되어 있는 메쏘드 뿐이다. 상속한 클래스가 다른 어떤 메쏘드를 정의했을지라도 그것은 클라이언트가 실행시킬 수 없다. 이것은 그 객체가 메쏘드의 인자로 사용되던 돌아오는 값으로 사용되던 마찬가지다.

 

그렇다면 원격 인터페이스를 구현하지 않는 다른 클래스들은 어떻게 될까? 예를 들어 서버객체의 메쏘드를 부를때 String 클래스를 인자로 사용했다고 하자. 이럴 경우 String 클래스는 Remote 인터페이스를 구현하지 않으므로 실제론 객체가 복사가 되어 서버로 보내지게 된다. 만약 서버가 String 객체의 어떠한 메쏘드를 사용한다고 해도 객체 자체가 복사가 되어 보내졌으므로 문제가 없다.

간단히 말하면 원격 인터페이스를 구현하는 객체가 인자나 메쏘드 값으로 보내질 경우 스텁클래스만이 보내지고 원격 인터페이스를 구현하지 않는 객체를 보낼 경우 객체 자체가 복사되어 보내진다는 얘기가 되겠다.

 

마치며
객체지향 개발개념과 분산시스템의 개념이 맞물려 분산객체 시스템이라는 것이 탄생하게 되었고 이렇게 분산되어 있는 객체 간의 통신과 데이터 교환을 가능하게 해주는 것이 RMI라는 것을 보았다. 특히 자바에서 구현하는 자바 RMI의 개념, 개발방법, 그리고 설치, 실행방법까지 알아 보았다. 지금까지 소켓만을 사용하여 데이터 교환을 한 독자라면 자바 RMI의 편리함을 알게 됐으리라 믿는다. 게다가 현재 서서히 주목을 받고 있는 EJB의 경우 RMI는 물론 위에서 보았던 Naming이 객체간 통신의 중추가 된다. 그러므로 앞으로 EJB를 공부하려고 생각하는 독자들에겐 자바 RMI의 개념을 이해하는 것은 필수적이며 EJB의 분산객체 개념과 그 구현방법이 더욱 확연하게 이해되리라 믿는다. 앞으로 자바 애플리케이션간의 데이터 교환이 필요할 경우 더욱 간편하고 개발이 수월한 자바 RMI를 사용하여 더욱 강력하고 세련된 개발자가 됐으면 한다. 그리고 자바 RMI를 이해함으로써 앞으로의 EJB 공부에도 더욱 도움이 됐으면 한다.

Posted by Steven J.S Min
,