android. alljoyn 라이브러리를 이용한 P2P 통신 기능 개발

android. alljoyn 라이브러리를 이용한 P2P 통신 기능 개발 [현재글]
ios. alljoyn 라이브러리를 이용한 P2P 통신 기능 개발

오픈소스 라이브러리 alljoyn 은 (별도의 서버를 구성하지 않고도) 같은 WIFI 또는 WIFI Direct 망 안의 다른 사용자들을 발견하고 통신할 수 있게 해주는 네트워크 전송 계층을 제공하며 추상화된 RPC를 지원합니다.



자세한 내용 및 소개는 아래 링크에서 확인 할 수 있습니다.

https://www.alljoyn.org/


Windows7,8,RT / Linux / IOS / Android 등 여러 플랫폼별로 사용 가능한 SDK를 제공한다고 하는데, 이 글에서는 Andorid에서의 Alljoyn 라이브러리 사용 플로우를 대략 설명하도록 하겠습니다.
이 글에서의 설명은 SDK 버전 3.4.1을 기준하고 있습니다.

일단 SDK를 다운받고, 샘플을 따라 라이브러리를 배치해 alljoyn 라이브러리를 사용할 수 있는 환경을 구성합니다.

https://www.alljoyn.org/docs-and-downloads/android

liballjoyn_java.so 를 /libs/armeabi/ 하위에 두고
alljoyn.jar 를 /libs/ 하위에 배치하면 됩니다.

그리고 이제부터는 코드 설명입니다. 일단 네이티브 라이브러리를 로드해야 합니다. 그러면서 몇가지 상수값을 정의해놓도록 하죠.
 
static{
 System.loadLibrary( "alljoyn_java" );
}//static

public static final String APP_NAME = "com.acidraincity";
public static final String SERVICE_NAME = "com.acidraincity.ResponseService";
public static final String SERVICE_PATH = "/ResponseService"
public static final short CONTACT_PORT = 42;

통신인터페이스및 서비스 구현 - 1.

(서버로서)다른 사용자에게 노출할 서비스 인터페이스를 작성합니다.
인터페이스 형태나 이름은 자유롭습니다만, @BusInterface 와 @BusMethod 주석을 등록하고, BusException을 throws 해야 합니다.
서버가 바인드한 세션에 해당 서비스를 노출하면, 클라이언트는 해당 서비스 메소드를 원격 호출할 수 있습니다.

import org.alljoyn.bus.BusException;
import org.alljoyn.bus.annotation.BusInterface;
import org.alljoyn.bus.annotation.BusMethod;

@BusInterface( name = "com.acidraincity.ResponseInterface" )
public interface ResponseInterface{

 @BusMethod
 byte[] request( byte[] req )throws BusException;
 
}//interface

통신인터페이스및 서비스 구현 - 2.

(서버로서)위에서 작성한 인터페이스를 명세를 따르는 서비스를 구현합니다.
class ResponseService implements ResponseInterface, BusObject{

 @Override
 @BusMethod
 public byte[] request( byte[] req ) throws BusException{
  //원격호출되는 메소드입니다. 실제 처리될 내용을 구현하면 됩니다.
  
  byte[] rtn = null;
  
  //TODO rtn을 구성하는 로직이 이곳에 들어가면 됩니다.
  
  return rtn;
 }//method
 
}//class

통신인터페이스및 서비스 구현 - 3.

ResponseService 객체를 생성해 둡니다.
ResponseService responseService = new ResponseService();


연결 - 1.

alljoyn 데몬을 준비하고 BusAttachment를 생성합니다.
boolean ok = org.alljoyn.bus.alljoyn.DaemonInit.PrepareDaemon( 
   context.getApplicationContext() );
if( !ok ){
 return false;
}//if
bus = new BusAttachment( APP_NAME, BusAttachment.RemoteMessage.Receive );


연결 - 2.

(클라이언트로서)BusAttachment리스너를 등록합니다. Bus에 새로운 노드가 등록(advertise)되었을때 통지받게 됩니다.
foundAdvertisedName 메소드 안에서 (클라이언트로서)세션 연결 처리를 하면 되는데... 코드가 길어지게 되니까 나중에 다시 설명하겠습니다.
bus.registerBusListener( new BusListener(){
bus.registerBusListener( new BusListener(){
 @Override
 public void foundAdvertisedName( String name, short transport, String namePrefix ){
  //TODO (클라이언트로서)발견된 노드 세션에 연결
 }//method
 
 @Override
 public void lostAdvertisedName( String name, short transport, String namePrefix ){
  //TODO 노드의 연결이 끊어졌을 때의 처리
 }//method
} );

연결 - 3.

(서버로서)서비스를 BusAttachment에 등록하고, (서버와 클라이언트로서)BusAttachment를 Bus에 연결합니다.
Status status = bus.registerBusObject( responseService, SERVICE_PATH );
if( status != Status.OK ){
 return false;
}//if

status = bus.connect();
if( status != Status.OK ){
 return false;
}//if

연결 - 4.

(서버로서)세션을 바인드합니다.
Mutable.ShortValue contactPort = new Mutable.ShortValue( CONTACT_PORT );
SessionOpts so = new SessionOpts();
so.traffic = SessionOpts.TRAFFIC_MESSAGES;
so.isMultipoint = false;
so.proximity = SessionOpts.PROXIMITY_ANY;
so.transports = SessionOpts.TRANSPORT_ANY;

status = bus.bindSessionPort( contactPort, so, new SessionPortListener(){
 public boolean acceptSessionJoiner( short sessionPort, String joiner, SessionOpts sessonOpts ){
  return sessionPort == CONTACT_PORT;
 }//method
} );
if( status != Status.OK ){
 return false;
}//if

연결 - 5.

(서버로서)어플리케이션의 서비스네임을 등록합니다. 등록하는 서비스네임은 여러 피어에서 공통적으로 사용하는 어플리케이션 서비스 네임과, 각 피어를 구분할 수 있는 유니크네임을 붙여 구성하세요.
myNodeName = "node" + UUID.randomUUID().toString().hashCode();
String serviceFullName = SERVICE_NAME + "." + myNodeName;

status = bus.requestName( 
  serviceFullName, 
  BusAttachment.ALLJOYN_REQUESTNAME_FLAG_REPLACE_EXISTING | BusAttachment.ALLJOYN_REQUESTNAME_FLAG_DO_NOT_QUEUE );
if( status == Status.OK ){
 status = bus.advertiseName( serviceFullName, SessionOpts.TRANSPORT_ANY );
 if( status != Status.OK ){
  status = bus.releaseName( serviceFullName );
  return false;
 }//if
}else{
 return false;
}//if

연결 - 6.

(클라이언트로서)버스에 등록된 노드를 탐색합니다.
서비스네임을 탐색하면, 해당 서비스네임을 프리픽스로 가진 모든 노드들이 탐색되게 됩니다.
그리고, 탐색된 노드가 있을 경우에, 위에서 등록한 BusAttachment리스너의 foundAdvertisedName 메소드가 호출되게 되는 것이죠.
status = bus.findAdvertisedName( SERVICE_NAME );
if( status != Status.OK ){
 return false;
}//if

연결 - 7.

(클라이언트로서)다른 노드가 탐색됐을때 해당 세션에 접속하는 코드를 구현합니다.
해당 코드는 foundAdvertisedName 메소드 안에서 구현하시면 됩니다. 위에서 TODO로 남겨두었던 부분이죠.
status = bus.findAdvertisedName( SERVICE_NAME );
@Override
public void foundAdvertisedName( final String name, short transport, String namePrefix ){

 //버스에 새로 연결된 노드를 발견
 
 //서비스명이 다를 경우 무시
 
 if( !name.startsWith( SERVICE_NAME ) ){
  return;
 }//if
 final String node = name.replace( SERVICE_NAME + ".", "" );
 
 if( node.equals( myNodeName ) ){
  
  //발견된 것이 자기 자신인 경우
  
 }else{
  
  //발견된 노드가 자신이 아닌 경우
  //(클라이언트로서) 발견 노드 세션에 연결합니다
  
  SessionOpts sessionOpts = new SessionOpts();
  sessionOpts.transports = transport;
  
  bus.joinSession( 
    name, 
    CONTACT_PORT, 
    sessionOpts, 
    new SessionListener(){
     @Override
     public void sessionLost( int sessionId, int reason ){
     }//method
    },
    new OnJoinSessionListener(){
     @Override
     public void onJoinSession( Status status, int sessionId, SessionOpts opts, Object context ){

      if( status == Status.OK ){
       
       //(클라이언트로서)발견된 노드 세션에 연결 성공
       //(클라이언트로서)연결된 노드의 원격 메소드 콜을 할 수 있는 인터페이스를 얻어옵니다.
       
       ProxyBusObject proxyObj = bus.getProxyBusObject( 
            name,
            SERVICE_PATH,
            sessionId,
            new Class[]{ ResponseInterface.class } );
       remoteResponseInterface = proxyObj.getInterface( ResponseInterface.class );

      }//if
      
     }//method
    }, 
    new Object() );

  
 }//if
 
}//method

RPC(원격메소드콜) - 1.

(클라이언트로서)세션 연결시 룩업해 얻어온 프록시 오브젝트로부터 다른 피어가 제공하는 서비스 메소드를 호출할 수 있습니다.
try{
 byte[] param = new byte[]{}; //별 의미는 없습니다.
 byte[] rtn = remoteResponseInterface.request( param );
}catch( BusException bex ){
 //TODO 예외처리
}//try

연결해제 - 1.

(클라이언트로서)연결된 세션 접속을 끊고 버스 연결을 종료하기 위해 다음 코드를 사용합니다.
for( String sessionId : sessionIds ){
 bus.leaveSession( sessionId );
}//for
bus.unregisterBusObject( responseService );
bus.disconnect();

이상으로 alljoyn 라이브러리를 사용해서 P2P 접속 및 통신을 구현하는 대략의 흐름을 개괄해 보았습니다.
연결되는 세션은 하나가 아니라 여럿일 수 있음을 유의하셔야 하고, 각각의 피어는 서비스를 제공하는 서버이면서 동시에 클라이언트가 되어야 한다는 개념을 잘 이해하시기 바랍니다.
연결된 여러개의 세션 연결을 pool로 관리하는 코드가 실제 구현시에 추가되어야 할 것입니다.