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

The practice of peer-to-peer computing : IP 멀티캐스트 기반의 검색
목 차:
검색 방식에 대한 복습
IP 멀티캐스트를 이용한 검색
간단한 클라이언트와 서버
검색 컴포넌트
멤버 클래스
P2P 애플리케이션
결론
참고 자료
필자 소개
기사에 대한 평가
관련 dW 링크:
The practice of peer-to-peer computing : 검색
P2P를 양방향으로 만들기 : The Jxta story
P2P를 양방향으로 만들기 : Jxta 명령쉘
P2P를 양방향으로 만들기 : Jxta 시스템 구축하기
US 원문 읽기
peer 검색을 위한 간단한 프레임워크 구축하기


Todd E. Sundsted
부사장, Facilitation, Etcee LLC
2002년 2월

P2P 애플리케이션의 피어(peer)들은 유용한 작업을 수행하기 위해 서로를 발견하고 상호작용할 수 있어야 한다. Todd는 이 글에서 피어들이 서로를 검색하는데 사용할 수 있는 몇 가지 다른 메커니즘을 기술하고 각각의 장단점을 설명하였다. 이번 달에는 IP 멀티캐스트에 기반하여 검색을 구현하는 방법을 설명하도록 하겠다.

한 소프트웨어 엔터티가 P2P 애플리케이션을 특징짓는 직접적인 피어간 상호작용에 참가하려면 그 전에 상호작용할 적합한 피어를 검색해야 한다. 모든 실행 가능한 P2P 아키텍처는 검색 문제에 대한 솔루션을 제공한다. 지난 회에서 우리는 검색을 구현하는 몇 가지 다른 방식들을 알아 보았다. 이번 달에는 그 메커니즘 중 하나를 구현하는 것에 관해 설명하겠다. 복습부터 해보자

검색 방식에 대한 복습

피어 검색은 P2P 애플리케이션의 피어들이 상호작용하기 위해 서로를 찾게 해준다. 피어 검색 서비스를 구현하는데는 많은 방식이 있다. 가장 간단한 메커니즘은 분명한 포인트 대 포인트 구성이다. 이 메커니즘에서는 모든 피어가 상호 작용할 다른 모든 피어에 대해서 알고 있고 연결되어 있어야 한다. 포인트 대 포인트 구성의 주된 장점은 간편성이다. 가장 큰 단점은 유연성이 부족하다는 것과 대단위 피어 네트워크로 확장될 수 없다는 것이다.

또다른 일반적인 검색 모델은 중앙 디렉토리가 중개하는 것이다. 이 모델은 기존의 non-P2P 분산 애플리케이션의 많은 유형들에서 매우 인기 있고, 그 장점이 잘 이해되고 있다. 피어들은 중앙 디렉토리에 자신의 존재를 등록하고, 중앙 디렉토리를 이용하여 다른 피어의 위치를 찾는다. 이 모델의 주된 장점은 관리의 용이성과 확장성이다. 그러나 중앙 집중형 설계는 단일 장애 지점이라는 위험이 도사리고 있어 자연 재해나 인터넷을 서핑하고 다니는 많은 얼치기들로부터 피해를 받기 쉽다.

중앙 디렉토리를 사용하는 대신, 많은 인기 있는 P2P 애플리케이션들은 한 피어가 네트워크의 로컬 지역에 있는 피어들의 신원만 아는 네트워크 모델을 사용하고 있다. 각 피어는 자신이 연결되어 있는 피어들에 대한 하나의 디렉토리로써 작동한다. 피어들은 인접한 피어들에게 디렉토리 조회 요청을 전달하고 관련된 응답을 반환함으로써 서로 협력한다. 이 모델의 주된 장점은 덜 중앙집중화되어 있다는 것이고, 주요 단점으로는 조회들을 전달함으로써 많은 양의 네트워크 자원과 처리 능력이 소모된다는 점이다.

위 세 메커니즘에 대해 수 많은 변형들이 있다. 이 변형들을 검토하기보다는 앞으로 더 나아가 다른 검색 메커니즘을 살펴 보도록 하자.

IP 멀티캐스트를 이용한 검색

멀티캐스트 모델은 각 피어가 자체적인 디렉토리를 유지한다는 점에서 네트워크 모델과 유사하다. 그러나 피어들은 네트워크 전체적인 대규모 검색을 수행하기 위해 협력하지 않는다. 또한 피어들은 다른 피어들을 찾고 확인하기 위해 네트워크 자체에서 제공한 기능을 이용한다. (IP 멀티캐스트)

IP 멀티캐스트는 비접속적이고 신뢰성이 없다 (둘 다를 지원하는 TCP/IP와 다르다). IP 멀티캐스트는 IP 데이터그램을 사용한다. 그러나 한 호스트에서 다른 하나의 호스트로 전해지는 유니캐스트 IP 데이터그램과는 달리 멀티캐스트 IP 데이터그램은 여러 개의 호스트에 동시에 전송될 수 있다.

피어는 IP 멀티캐스트를 사용해 정기적으로 자신의 존재를 알린다. 여기에는 호스트명과 정상적인 통신에 사용되는 포트가 들어 있다. 흥미를 가진 피어는 이 메시지를 추적하여 호스트명과 포트를 추출한 후 이 정보를 사용하여 통신 채널을 구축한다.

충분히 복습했으므로 이제 코드를 살펴보자.

간단한 클라이언트와 서버

IP 멀티캐스트를 사용해 두 프로세스가 어떻게 통신하는지를 보여주는 간단한 예제로 출발해 보자. 설명을 간단하게 하기 위해 나는 이 예제를 클라이언트 프로세스와 서버 프로세스의 측면에서 설명하겠다. 하나의 P2P 애플리케이션은 보통 양 프로세스를 다 구현하지만, 클라이언트, 혹은 서버로 쉽게 분류되지는 않는다.

이 예제에서 서버 프로세스는 루프 내에 있고 데이터그램 패킷이 도착하기를 기다린다. 각 패킷을 받으면 서버는 짧은 진단 메시지를 콘솔에 출력한다. 클라이언트의 역할은 더 간단하다. 클라이언트는 하나의 데이터그램 패킷을 멀티캐스트한 후 빠져 나간다.

Listing 1과 2는 이 두 부분이 어떻게 맞추어지는지를 보여준다. 코드 내의 코멘트는 무슨 일이 발생하고 있는지를 설명한다.

Listing 1. 간단한 서버

public
class Server
{
  public
  static
  void
  main(String [] arstring)
  {
    try
    {
      // Create a multicast datagram socket for receiving IP
      //  multicast packets.  Join the multicast group at
      //  230.0.0.1, port 7777.
      MulticastSocket multicastSocket = new MulticastSocket(7777);
      InetAddress inetAddress = InetAddress.getByName("230.0.0.1");
      multicastSocket.joinGroup(inetAddress);
      // Loop forever and receive messages from clients.  Print
      //  the received messages.
      while (true)
      {
        byte [] arb = new byte [100];
        DatagramPacket datagramPacket = new DatagramPacket(arb, arb.length);
        multicastSocket.receive(datagramPacket);
        System.out.println(new String(arb));
      }
    }
    catch (Exception exception)
    {
      exception.printStackTrace();
    }
  }
}

Listing 2. 간단한 클라이언트

public
class Client
{
  public
  static
  void
  main(String [] arstring)
  {
    try
    {
      // Create a datagram package and send it to the multicast
      //  group at 230.0.0.1, port 7777.
      byte [] arb = new byte [] {'h','e','l','l','o'};
      InetAddress inetAddress = InetAddress.getByName("230.0.0.1");
      DatagramPacket datagramPacket = 
          new DatagramPacket(arb, arb.length, inetAddress, 7777);
      MulticastSocket multicastSocket = new MulticastSocket();
      multicastSocket.send(datagramPacket);
    }
    catch (Exception exception)
    {
      exception.printStackTrace();
    }
  }
}

java.net 패키지에 들어 있는 두 클래스가 이를 수행한다. java.net.DatagramPacket 클래스는 IP 데이터그램 패킷에 들어 있는 데이터를 가지고 있다. java.net.MulticastSocket 클래스는 특정 멀티캐스트 그룹에 맞추어진 멀티캐스트 소켓을 생성한다.

검색 컴포넌트

위의 예제가 IP 멀티캐스트를 잘 보여주긴 하지만, IP 멀티캐스트 기반의 피어 검색을 구현하기 위해 필요한 것이 무엇인지를 보여주는데는 부족하다. 유용하게 되려면 간단히 패킷을 전송하고 받는 것 이상을 수행하는 소프트웨어 컴포넌트가 필요하다. 이상적으로 이 컴포넌트는 자신에게 패킷을 보낸 피어를 추적하고, 사라지거나 없어졌다고 생각되는 피어에 관한 정보를 점진적으로 폐기한다.

이 새로운 설계에서 피어는 멀티캐스트 그룹의 한 멤버이다. 멀티캐스트 그룹에 전송된 메시지는 멀티캐스트 그룹의 모든 멤버들에게 투명하게 보내진다.

설계는 두 개의 핵심 클래스와 세 개의 인터페이스 (나는 대충 "인터페이스"라는 용어를 사용했는데, 기술적으로 하나의 인터페이스와 두 개의 추상 클래스가 있다.) 중심으로 되어 있다. Member 클래스의 인스턴스는 멀티캐스트 그룹의 한 멤버이다. 이 클래스는 통신과 관련된 모든 상세사항을 다룬다. 멀티캐스트 그룹에 참가하고 있는 다른 멤버를 추적하는데 필요한 작업은 MemberManager 클래스의 인스턴스의 책임이다.

피어는 피어들이 속해 있는 멀티캐스트 그룹에게 메시지를 보내어 자신을 그 피어 그룹에게 알린다. 각 메시지에는 그것을 보내는 피어에 대한 정보 --보통 호스트명과 정상적인(발견과 관련되지 않은) 통신에 사용되는 포트-가 들어 있다. Member 클래스와 MemberManager 클래스는 이 메시지들의 내용에 대해서는 거의 알지 못한다. 그 정보로의 접근은 이 두 클래스를 사용하는 애플리케이션의 몫이다.

세 개의 인터페이스는 메시징/검색 층과 이를 이용하는 애플리케이션 층간의 경계에 걸쳐 있다. 이들은 Reference 추상 클래스, Message 인터페이스 및 MessageFactory 추상 클래스이다. 애플리케이션은 이 세 인터페이스의 구현을 제공해야 한다.

Reference 추상 클래스는 멀티캐스트 그룹의 멤버에 대한 참조를 정의한다. MemberManager 클래스는 참조 집합을 관리한다. 애플리케이션은 애플리케이션이 요구하는 참조 로직을 포함하고 있는 (로직이 무엇이든이든간에) 이 클래스의 구체적인 버전을 구현할 것이다. 클래스는 equalsInternal()hashCodeInternal()라는 두 메소드를 정의하고 이들을 호출하기 위해 equals()hashCode() 메소드를 재정의한다. 이것은 구현자가 이 두 중요한 기능(MemberManager가 이 메소드들에 의존한다)을 구현하도록 하기 위해서이다.

Message 인터페이스는 네트워크 코드로 교환되는 메시지 데이터가 애플리케이션에서 보여지는 형태를 정의한다. 애플리케이션은 메시지를 애플리케이션이 실행되는 영역과 관련된 상위 개념(호스트명과 포트 같은 개념)으로 생각한다. 네트워크 코드는 바이트로 된 패킷을 전송하기를 원한다. Message 인터페이스의 구현은 이 상위 정보를 바이트로, 그리고 바이트를 상위 정보로 변환하는 방법을 정의한다. 참조는 모든 메시지가 가지고 있어야 하는 중요한 부분이기 때문에, 인터페이스는 구현이 reference를 읽고 쓰기 위한 메소드를 제공할 것을 요구한다.

퍼즐의 마지막 조각은 MessageFactory 추상 클래스이다. 이 클래스는 새로운 Message 인스턴스가 생성되는 메커니즘을 정의한다. Member 클래스 내에 깊숙히 숨겨져 있는 네트워크 코드는 멀티캐스트 데이터그램으로부터 추출한 데이터에서 Message 인스턴스를 생성하기 위해 factory를 사용한다. 모든 MessageFactory 인스턴스는 무작위로 생성된 identity를 가지고 있는데, 수신한 메시지들에서 자신이 전송한 메시지를 가려내기 위해 이를 사용한다.

이 다섯 개의 클래스와 인터페이스 (Member, MemberManager, Reference, MessageMessageFactory)는 함께 피어 검색을 위한 간단한 프레임워크를 형성한다. 물론 개선의 여지가 있다. 멤버가 등장했거나 사라졌음을 관심 있는 listener에게 통지하기 위한 이벤트 기반 메커니즘을 쉽게 추가할 수 있을 것이다. 수신한 메시지들을 가려내기 위한 유연한 메커니즘이 있으면 좋겠지만, 구현하기가 더 어려울 것이다. 이 제안은 여러분이 연습삼아 구현해보기 바란다.

Member 클래스

위에서 설명한 프레임워크의 전체 소스는 너무 길어서 상세하게 나타낼 수 없기 때문에, 대부분의 action이 들어 있는 Member 클래스에 해당되는 부분만 살펴보자. 좀 더 정확하게 말하면, action은 MemberClient 클래스와 MemberServer 클래스라는 두 개의 내부 클래스에서 발생한다.

첫번째 예제를 다시 생각해보자. 이 예제는 IP 멀티캐스트 데이터그램을 전송하는 클라이언트와 이를 받는 서버로 구성되어 있다. 이 예(Listings 3과 4)에서는 두개의 개별적인 애플리케이션이 이 두 기능을 수행한다. P2P 애플리케이션에서 피어들은 클라이언트와 서버 양쪽과 유사하게 행동한다. 따라서 우리의 P2P 애플리케이션이 양쪽을 포함해야 하는 것이 맞다.

Listing 3. MemberClient 클래스

private
class MemberClient
extends Thread
{
  public
  void
  run()
  {
    try
    {
      while (true)
      {
        try
        {
          Message message = m_messagefactory.createSendMessage();
          Reference reference = message.createReference();
          message.writeReference(reference);
          message.sync();
          byte [] arb = message.getByteArray();
          DatagramPacket datagrampacket = new DatagramPacket(arb, arb.length);
          datagrampacket.setAddress(m_inetAddress);
          datagrampacket.setPort(m_nPort);
          MulticastSocket multicastsocket = new MulticastSocket();
          multicastsocket.send(datagrampacket);
        }
        catch (IOException ioException)
        {
          ioException.printStackTrace();
        }
        try
        {
          synchronized (this)
          {
            wait(m_nPeriod);
          }
        }
        catch (InterruptedException interruptedException)
        {
          break;
        }
      }
    }
    catch (Throwable throwable)
    {
      throwable.printStackTrace();
    }
  }
}

Listing 3은 MemberClient 클래스의 소스를 포함하고 있다. Listing 1의 클라이언트와 마찬가지로 이 클라이언트는 MulticastSocket 인스턴스를 생성하고 이를 이용하여 DatagramPacket 인스턴스를 전송한다. DatagramPacket 인스턴스는 바이트 배열로 인코딩된 발신 피어에 대한 참조를 포함하고 있다. 피어가 살아 있고 실행되는 한 이 클라이언트는 이 정보를 정기적으로 방송(braodcast)할 것이다.

Listing 4. MemberServer 클래스

private
class MemberServer
extends Thread
{
  public
  void
  run()
  {
    try
    {
      MulticastSocket multicastsocket = new MulticastSocket(m_nPort);
      multicastsocket.joinGroup(m_inetAddress);
      while (true)
      {
        Message message = m_messagefactory.createReceiveMessage();
        byte [] arb = message.getByteArray();
        DatagramPacket datagrampacket = new DatagramPacket(arb, arb.length);
        multicastsocket.receive(datagrampacket);
        message.sync();
        if (m_messagefactory.isMine(message) == false)
        {
          Reference reference = message.createReference();
          message.readReference(reference);
          m_membermanager.addReference(reference);
        }
      }
    }
    catch (Throwable throwable)
    {
      throwable.printStackTrace();
    }
  }
}

MemberServer 클래스는 Listing 2의 서버와 많은 면에서 유사하다. 이 서버는 필요한 네트워크 연결을 생성하고 이를 이용해 네트워크에서 적절한 데이터그램을 가져올 뿐 아니라, 인코딩된 참조를 해독하고 메시지를 생성하여 이를 MemberManager 인스턴스에 보내어 안전하게 보관한다. 하루 일로는 나쁘지 않다.

클래스의 나머지 부분은 다양한 특성에 대한 getters와 setters 및 클래스의 수명 주기를 제어하기 위한 start()stop() 메소드로 구성된다.

P2P 애플리케이션

피어 검색 프레임워크가 완료되면 남은 것은 이를 기존 P2P 애플리케이션에 통합하는 일 뿐이다. 우리의 원래 P2P 애플리케이션에 대한 변경은 비교적 적다.

우선 이전의 모습에서 P2P 애플리케이션은 모든 알려진 피어가 자신의 특성 파일에 나열되어 있기를 기대했다. 이는 검증 목적으로는 괜챦고 (위에서 설명한 모든 포인트 대 포인트 구성을 회상해 보라) 약 4개의 피어까지는 잘 작동했지만 궁극적으로 매우 제한적이었다. 따라서 특성 파일에 확인되어 있는 피어들을 읽고 관리하는 코드는 없어졌으며 위의 검색 메커니즘을 사용하는 코드로 교체되었다.

둘째, 피어들은 특성 파일에 나열되어 있었기 때문에 이들에 대한 영속성이 존재했다. 기존 애플리케이션은 이들을 사라지지 않는 척하면서 없앨 수 있다. 그러나 P2P의 실제 세계는 훨씬 더 동적이다. 새 애플리케이션은 피어가 사라졌을 때 좀 더 잘 복구되도록 재설계되었다.

참고 자료에서 업데이트된 소스 코드를 이용할 수 있다.

결론

피어를 검색하는 것은 전쟁의 반일 뿐이다. 다음에는 파이어월, 게이트웨이 및 기타 많은 골치 아픈 것들로 들어찬 현대 인터넷 상에서 두 피어를 통신하도록 만드는 것과 관련된 과제들을 살펴보겠다.

참고자료

필자소개
Todd Sundsted는 컴퓨터를 데스크탑 모델로 사용할 수 있었던 때부터 소프트웨어를 작성해 왔다. 그는 보안, 분산 컴퓨팅 및 매우 정제된 시스템에서 발생하는 역동성과 갑작스러운 행동에 관심을 가지고 있다. Todd는 프로그램 작성뿐 아니라 코딩도 한다.


이 기사에 대하여 어떻게 생각하십니까?

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

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