IBM Korea Skip to main content
       IBM 홈    |  제품 & 서비스  |  고객지원 & 다운로드  |  회원가입  

IBM developerWorks > 자바
developerWorks

모바일 P2P 메시징, Part 2 : 일반적인 P2P 네트워크로 모바일 확장 개발하기
모바일 디바이스를 JXTA와 Jabber 클라이언트로 전환하기

Level: Intermediate

Michael Juntao Yuan
University of Texas at Austin, 전자상거래 센터, 연구원
2003년 1월 1일

JXTA와 Jabber 같은 일반적인 P2P 컴퓨팅 네트워크는 모바일 디바이스에 적용하기에는 너무 복잡하다. 따라서 경량 모바일 클라이언트나 릴레이(relay)를 통해 작동하는 특별한 아키텍쳐는 P2P 커뮤니티를 모바일 사용자로 확대되어야 한다. Michael Yuan은 J2ME JXTA 클라이언트 프로젝트인 JXME를 설명한다.

SMS 기반 메시징은 무선 전화 사용자들에게는 매우 편리하다. 하지만, PDA와 WAN 연결 핸드헬드 같은 모바일 장치에는 합당한 메시징 플랫폼이 아니다. 다양한 셀 네트워크(국제 전화)를 통한 SMS 메시징 역시 값이 비싸거나 어떤 경우는 불가능하다. 이 글에서는 두 개의 범용 P2P 네트워크인 JXTA P2P와 Jabber 인스턴트 메시징 네트워크를 소개하겠다.

JXTA 프레임웍
JXTA는 P2P 네트워크용 오픈 프로토콜을 정의한다. 이 XML 기반의 프로토콜은 피어 디스커버리, 엔드포인트 라우팅, 커넥션 바인딩, 기본 쿼리/응답 메시지 교환, 랑데부 피어를 통한 네트워크 파급 같은 복잡한 작동을 정의한다. JXTA 네트워크는 다음과 같은 컴포넌트로 구성되어 있다:

  • 피어(peer)는 JXTA 네트워크의 기본적인 엘리먼트이다. 피어는 핵심 JXTA 프로토콜을 구현하고 모든 다른 피어들과 독립적이고 비동기식으로 작동한다. 피어는 애플리케이션과 네트워크 서비스를 제공하고 피어 자체를 광고할 수 있다.
  • 피어 그룹은 피어들의 모음이다. JXTA는 그룹을 만들고 참여하고 감시하기 위해 피어들이 사용하는 프로토콜을 정의한다. 그룹은 피어 발견, 멤버쉽, 액세스 제어, 파이프, 클라이언트(resolver), 모니터링 같은 서비스를 제공한다.
  • 파이프(Pipe)는 피어들 간 가상 연결을 나타낸다. point-to-point 파이프는 두 개의 피어를 연결한다. propagate 파이프는 방송 피어를 다중 리스너에 연결한다. 파이프가 연결된 피어들은 직접 물리적 링크가 될 필요는 없다. 여러 개의 중간 파이프를 통해 연결될 수 있기 때문이다.
  • 메시지(Message)는 피어들과 엔드포인트 간, 파이프를 통해 교환되는 데이터이다. JXTA는 모든 메시지용 envelope 포맷을 정의한다. 각 피어는 자신의 메시지 컨텐트 포맷을 정의할 수 있다. 단 포맷은 XML 스팩에 호환되어야 한다. 하지만 두 개의 피어들이 정보를 교환하기 위해서는 상대방의 메시지 포맷을 이해해야 한다.
  • 광고(Advertisement)는 JXTA 네트워크 리소스를 기술하기 위한 메타데이터 구조이다. 모든 피어들과 서비스들은 광고를 이해한다.

JXTA는 단순한 메시지교환 이상의 P2P 프레임웍이다. P2P 파일 공유, P2P 애플리케이션 서비스, 협업 분산 컴퓨팅 같은 문제를 다루고있다. JXTA 프로토콜은 모든 구현 기술에 독립적으로 설계된 반면 JXTA의 레퍼런스 구현은 자바 플랫폼에 구현된다. 이 레퍼런스 구현은 자바 API를 사용하여 JXTA 프로토콜 메시지를 래핑하고 프로그램 방법으로 자바 애플리케이션에서 JXTA 네트워크에 액세스한다.

모바일 장치용 JXTA
JXTA의 힘과 유연성은 복잡성이라는 상당한 대가를 치르고 얻어졌다. JXTA 피어는 많은 태스크를 관리하고 XML-소켓 레벨에서 메시지를 처리한다. 그와 같은 피어는 너무나 복잡해서 대부분의 모바일 디바이스 상에서는 실행할 수 없다. 게다가, XML이나 원래의 소켓 지원은 표준 J2ME/MIDP 스팩의 일부이다. 모바일 P2P 사용자들에게 JXTA 네트워크를 가능하게 하려면 모바일 디바이스용 JXTA API가 필요하다. JXME 프로젝트는 JXTA API를 CLDC와 MIDP 플랫폼에 제공하는 것을 목표로하고 있다. 이것은 Personal Profile 같은 J2ME 프로파일에도 사용된다.

JXME은 릴레이(relay)를 사용하여 경량의 모바일 피어들을 나머지 JXTA 네트워크에 연결한다. 릴레이 자체로는 랑데부 JXTA 피어로서 파이프, 광고, 피어 그룹 서비스를 핸들할 수 있다. 모바일 피어는 HTTP를 통한 바이너리 연결을 통해 릴레이들과 통신한다. 이때 JXTA Binary Message 포맷에 순응하는 메시지를 사용한다. 릴레이는 모바일 피어들에게 많은 피어 서비스를 제공한다:

  • 릴레이는 불필요한 광고를 걸러내고 그런 광고들을 없애 대역폭을 저장한다.
  • 릴레이는 메시지들을 모바일 피어들에 라우팅한다.
  • 릴레이는 JXTA XML 포맷에서 JXTA Binary Message 포맷으로 메시지들을 변환한다. 그 반대도 수행한다. 모바일 피어와 일상 피어들 간 상호운용성을 위해서이다.
  • 릴레이는 모바일 피어를 대신하여 프록시로서 작동한다. 릴레이는 다른 피어와 파이프와 인터랙팅하고 그룹 서비스를 사용한다.

그림 1. JXME 아키텍쳐
JXME architecture

JXME API

JXME 패키지를 다운로드하면 데모를 실행할 수 있다. 다운로드 패키지에 포함된 Ant 구현 스크립트를 사용하여 단계별로 데모를 실행한다. 자세한 튜토리얼은 JXME 웹 사이트를 참조한다.

우선, 모바일 디바이스와 목표 JXTA 커뮤니티에서 접근 가능한 컴퓨터에 릴레이 피어를 실행해야 한다. proxy 디렉토리의 build.xml 파일에는 runproxy 태스크가 포함되어 있어 JXME을 릴레이를 실행한다. 적당한 박스를 점검하여 Rendevous, relay, JxtaProxy 로서 실행한다. 이제 JXME 피어를 실행할 수 있다. 다운로드 패키지에는 두개의 샘플 JXME 애플리케이션(chat & tictactoe)이 포함되어 있다.

JXME 패키지를 보자. JXME API는 세 개의 클래스를 가지고 있으며 모두 net.jxta.j2me 패키지에 있다:

  • Element는 JXTA 메시지 내의 엘리먼트이다. 엘리먼트에는 네임, 네임스페이스, MIME 타입, 데이터의 바이너리 어레이가 포함되어 있다.
  • Message는 여러 개의 엘리먼트로 구성된 JXTA 메시지이다. Message는 그러한 엘리먼트에 액세스하기 위해 메소드를 제공한다.
  • PeerNetwork는 가장 유용한 클래스이다. JXTA 태스크를 설정하여 모바일 피어가 릴레이를 통해 수행될 수 있게 한다. 다음은 PeerNetwork 클래스의 유용한 메소드들이다:

    • createInstance()는 지정된 피어 이름을 가진 PeerNetwork의 인스턴스를 리턴하는 팩토리 메소드이다.
    • connect() 메소드는 지정된 HTTP URL에서 릴레이로 연결한다. 영속 상태 정보의 바이트 어레이를 리턴한다.
    • create() 메소드는 릴레이 프록시를 통해 JXTA 네트워크 상에 피어, 그룹, 파이프를 만든다.
    • search() 메소드는 피어, 그룹, 파이프를 검색한다.
    • poll() 메소드는 이 모바일 피어에 접근하는 메시지용 모든 릴레이들을 등록한다. 서버 쓰레드에서 반복적으로 호출 될 수 있다.
    • listen()close() 메소드는 인풋 파이프를 열고 닫는다.
    • send() 메소드는 메시지를 지정된 파이프에 보낸다.

그림 2. net.jxta.j2me 패키지의 클래스
Package net.jxta.j2me classes

예제
JXME 웹 사이트의 JXME 소스 코드 패키지에 포함된 mySend.java 튜토리얼 예제를 사용하여 앞서 설명한 API 사용법을 설명하겠다. JXME 패키지는 Project JXTA에 대한 개별적 기여들로 이루어져 있다. 소스 코드는 Sun Project JXTA Software License의 허가를 받았다.

J2ME 에뮬레이터에서 실행되는 예제 모바일 피어는 9700 포트의 호스트 PC(localhost) 상에서 실행되는 JXME 릴레이를 통해 많은 액션들을 수행한다. 물론 실제 전개시에는 모바일 피어는 모바일 디바이스상에서 실행될 수 있으며 localhost를 자신의 릴레이 컴퓨터의 IP 어드레스로 바꿀 수 있다. JXME 릴레이는 다운로드 패키지에 포함되어 있다.

예제 피어는 다음 단계를 거친다:

  1. 새로운 파이프를 만든다.
  2. 설치된 파이프를 찾는다.
  3. 그 파이프를 통해 메시지를 보낸다.
  4. 메시지를 받는다.

Listing 1은 peer.listen() 메소드가 릴레이에게 새로운 파이프를 만들것을 명령하고 있다.

Listing 1. 새로운 파이프 구현하기


// Create a peer and have it connect to the relay.
// A connection to relay is required before any other
// operations can be invoked.
String relayUrl = "http://localhost:9700/";
PeerNetwork peer = PeerNetwork.createInstance("mySendPeer");
byte [] persistentState = peer.connect(relayUrl, null);
 
// Have the peer create and open a Unicast pipe; PipeID will
// be returned asynchronously in a response message
int listenQueryId = peer.listen("myPipe", null, PeerNetwork.UNICAST_PIPE);
 
// Have peer search for this Pipe and then send it a Message
String pipeID = findMyPipe();
sendMyMessage(pipeID);
 
// Finally, have the peer poll for Messages sent to it
recvMessage();
}

파이프 만들기가 성공하면 릴레이는 바이너리 메시지를 모바일 피어에 만든다. findMyPipe() 메소드는 성공을 나타내는 메시지를 받을 때 까지 루프 안에서 릴레이를 등록한다. 그런 다음 메시지를 파싱하고 파이프 ID를 얻는다. 바이너리 메시지는 XML 같은 구조를 갖고있다. (Listing 2).

Listing 2. Getting the pipe we just created


public String findMyPipe() throws IOException {
  // Now poll for all messages addressed to us. Stop when we
  // find the response to our listen command
  int rid = -1;
  String id = null;
  String type = null;
  String name = null;
  String arg = null;
  String response = null;
 
  Message msg = null;
  while (true) {
    // do not use a timeout of 0, 0 means block forever
    msg = peer.poll(1);
    if (msg == null) {
      continue;
    }
    // look for a response to our search query
    for (int i = 0; i < msg.getElementCount(); i++ ) {
      Element e = msg.getElement(i);
      if (Message.PROXY_NAME_SPACE.equals(e.getNameSpace())) {
        String elementName = e.getName();
        if (Message.REQUESTID_TAG.equals(elementName)) {
          String rids = new String(e.getData());
          try {
            rid = Integer.parseInt(rids);
          } catch (NumberFormatException nfx) {
            System.err.println("Recvd invalid " +
                               Message.REQUESTID_TAG +
                               ": " + rids);
            continue;
          }
        } else if (Message.TYPE_TAG.equals(elementName)) {
          type = new String(e.getData());
        } else if (Message.NAME_TAG.equals(elementName)) {
          name = new String(e.getData());
        } else if (Message.ARG_TAG.equals(elementName)) {
          arg = new String(e.getData());
        } else if (Message.ID_TAG.equals(elementName)) {
          id = new String(e.getData());
        } else if (Message.RESPONSE_TAG.equals(elementName)) {
          response = new String(e.getData());
        }
      }
    }
    // PIPE_NAME: myPipe
    // PIPE_TYPE: PeerNetwork.UNICAST_PIPE
    if (rid == listenQueryId &&
        response.equals("success") &&
        type.equals("PIPE") &&
        name.equals(PIPE_NAME) &&
        arg.equals(PIPE_TYPE)) {
      return id;
    }
  }
}

sendMyMessage() 메소드는 findMyPipe() 메소드에서 리턴된 파이프 ID를 갖고 파이프를 통해 메시지를 보낸다. (Listing 3).

Listing 3. 파이프를 통해 메시지 보내기


public void sendMyMessage(String pipeID)
    throws IOException {
  Element[] elm = new Element[2];
  // Our Message will contain two elements. Receiver should look for
  // elements with the same names ("mySend:Name" and "mySend:Message")
  // PEER_NAME: mySendPeer
  elm[0] = new Element("mySend:Name", PEER_NAME.getBytes(),
                       null, null);
  elm[1] = new Element("mySend:Message", "Hello there".getBytes(),
                       null, null);
  Message msg = new Message(elm);
  // PIPE_NAME: myPipe
  // PIPE_TYPE: PeerNetwork.UNICAST_PIPE
  sendRequestId = peer.send(PIPE_NAME, pipeID, PIPE_TYPE, msg);
  System.out.println("send request id: " + sendRequestId);
}

mySendPeer는 파이프를 리스닝하고 있다. 따라서 우리가 보낸 메시지는 릴레이에서 온 같은 모바일에 사용할 수 있다. recvMessage() 메소드는 이 메시지를 받을 때 까지 릴레이를 등록하고 받으면 이 메시지를 디스플레이한다. (Listing 4).

Listing 4. 메시지 받기


public void recvMessage() throws IOException {
  Message msg = null;
  while (true) {
    msg = peer.poll(1);
    if (msg == null) {
      continue;
    }
    for (int i = 0; i < msg.getElementCount(); i++) {
      Element e = msg.getElement(i);
      if ("mySend".equals(e.getNameSpace()) &&
          "Name".equals(e.getName()))
        System.out.println("Message from: " +
                           new String(e.getData()));
      if ("mySend".equals(e.getNameSpace()) &&
          "Message".equals(e.getName()))
        System.out.println("Message: " +
                           new String(e.getData()));
    }
  }
}

Jabber 인스턴트 메신저
P2P 시스템의 성공은 사용자를 끌어들이는 능력에 달려있다. JXTA가 매우 강력하고 기술적으로도 앞선 프레임웍이지만 복잡성 때문에 채택을 망설이고 있다. Jabber는 JXTA 보다 훨씬 단순한 P2P 시스템이다. 주로 인스턴트 메시지 교환용으로 설계되었다. Jabber는 JXTA 보다 넓은 피어 네트워크를 갖고 있다.

Jabber는 원래 대중적인 인터넷 인스턴트 메시징 시스템(AOL, MSN, Yahoo!, ICQ)간 상호 운용성을 제공할 목적으로 설계되었다. 모든 기존의 IM 프로토콜을 래핑할 수 있는 강력하고 유연하면서도 단순한 프로토콜이다. 강력한 기능과 완전히 개방된 XML 프로토콜이라는 점 외에도 Jabber는 가장 진보된 IM 시스템이다. Jabber는 고급 P2P 애플리케이션을 지원한다.

Jabber 피어들은 Jabber 서버들을 통해 서로 서로 통신한다. Jabber 서버는 상호 통신하여 피어들의 큰 도메인을 형성한다. 이것은 같은 서버로는 직접 연결되지 않는다. Jabber 피어와 서버 간 모든 통신은 개방 XML 포맷의 메시지 형태를 취한다. 결과적으로 Jabber 서버와 피어는 플랫폼과는 독립적으로 구현된다. Jabber 개발 라이브러리는 자바, C++, C#, Perl, Python, PHP, Flash에서도 가능하다.

J2ME 디바이스(특히, MIDP 디바이스)용 Jabber 클라이언트 개발은 쉬운일이 아니다. 모든 MIDP Jabber 클라이언트는 XML 파서가 있어야한다. 게다가 MIDP VM과 기저 네트워크가 원시 소켓 연결을 지원해야 한다. MIDP 1.0 호환의 Jabber 클라이언트 또는 라이브러리가 많이 있다:

  • Al Sutton의 KVMJab은 MIDP 플랫폼을 위한 오픈 소스 Jabber 라이브러리이다. MIDP Jabber 애플리케이션 개발에 필요한 간단한 API를 제공한다.
  • uppli의 uMessenger는 완벽한 기능의 Jabber IM 클라이언트 이다. 파일 공유를 지원한다.

참고자료

목 차:
모바일 장치용 JXTA
JXME API
그림 2. net.jxta.j2me 패키지의 클래스
예제
참고 자료
필자 소개
기사에 대한 평가
관련 dW 링크:
Mobile P2P messaging, Part 1
The JXTA story
The JXTA command shell
Creating JXTA systems
Subscribe to the developerWorks newsletter
US 원문 읽기
Also in the Java zone:
Tutorials
Tools and products
Code and components
Articles
필자소개
Michael J. Yuan: 연구원(University of Texas at Austin, 전자상거래 센터).
이 기사에 대하여 어떻게 생각하십니까?

정말 좋다 (5) 좋다 (4) 그저그렇다 (3) 수정보완이 필요하다(2) 형편없다 (1)

  회사소개  |  개인정보 보호정책  |  법률  |  문의