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

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


IOS 플랫폼에서 alljoyn 라이브러리를 이용해 개발하는 방법을 설명합니다.

1.

IOS용 SDK alljoyn-14.02.00-osx_ios-sdk.zip 파일을 https://allseenalliance.org 에서 다운로드할 수 있습니다.
SDK 안의 README.txt 파일을 참고해 라이브러리 빌드 환경을 구성합니다.
alljoyn 라이브러리는 openssl 라이브러리에 의존합니다.

SDK 빌드 환경을 구성한 후에는 포함된 샘플을 실행해 alljoyn 라이브러리의 동작을 테스트해볼 수 있습니다.

https://allseenalliance.org/docs-and-downloads/documentation/alljoyn-objective-c-sample-walkthrough

2.

신규 프로젝트로 개발을 시작하는 경우에는 SDK 샘플에 포함된 AllJoyn iOS Project Template 프로젝트를 그대로 사용하면 됩니다.

이미 진행하던 프로젝트에 alljoyn 라이브러리를 적용해야 할 경우에는 기존 프로젝트에 AllJoynFramework 을 추가하고 AllJoyn iOS Project Template 프로젝트를 참고해 빌드 환경을 동일하게 맞춰줍니다.

다음 항목들에 특히 주의해야 합니다.
- Linking :: Other Linker Flags
- Search Paths :: Header Search Paths
- Search Paths :: Library Search Paths
- Apple LLVM 5.1 - Custom Compiler Flags :: Other C Flags
- Apple LLVM 5.1 - Language - C++ :: Enable C++ Runtime Types

기존 프로젝트가 ARC 를 사용하지 않는다면, Build Phases :: Compile Sources 항목에서 추가된 AllJoynFramework 소스들에 -fobjc-arc 옵션을 추가해줍니다.

AllJoyn iOS Project Template 프로젝트에 포함된 SampleAllJoynObjectModel.xml 파일을 수정해 통신 인터페이스를 정의하고 Generate Code 타겟을 빌드하면 정의한 통신 인터페이스를 구현하는 템플릿 코드를 자동생성할 수 있습니다.

https://allseenalliance.org/docs-and-downloads/documentation/alljoyn-programming-guide-objective-c-language

3.

데이터 포맷에 대해서는 아래의 스팩 문서를 참고할 수 있습니다.

http://dbus.freedesktop.org/doc/dbus-specification.html

다음과 같이 통신 인터페이스를 정의하여 Generate Code로 코드를 자동생성해 봅시다.

<xml>
    <node name="com/acidraincity/ResponseInterface">
        <annotation name="org.alljoyn.lang.objc" value="ARCResponseInterface"/>
        <interface name="com.acidraincity.ResponseInterface">
            <annotation name="org.alljoyn.lang.objc" value="ARCResponseInterfaceDelegate"/>
            <method name="request">
                <arg name="req" type="ay" direction="in">
                    <annotation name="org.alljoyn.lang.objc" value="request:"/>
                </arg>
                <arg name="res" type="ay" direction="out"/>
            </method>
        </interface>
    </node>
</xml>


위의 인터페이스로 만들어진 자동생성코드는 그냥 사용할 수 있어야 하지만, 어째서인지 오류가 있으므로 고쳐줄 부분이 몇군데 있습니다.

void ARCResponseInterfaceImpl::request(const InterfaceDescription::Member *member, Message& msg) 메소드의 outArgs[0].Set("ay", [outArg0 msgArg]); 라고 된 부분을 찾아 다음과 같이 바꿔줍니다.
//outArgs[0].Set("ay", [outArg0 msgArg]); //아래처럼 바꿉니다
outArgs[0] = *[outArg0 msgArg];


그리고 RPC 호출에 사용해야 하는 ARCResponseInterfaceProxy 클래스의 -(AJNMessageArgument*)request:(AJNMessageArgument*)req 메소드 자동생성 코드는 결과값을 리턴하는 부분이 미구현되어있어 그대로 빌드도 되지 않을 뿐더러, 파라미터와 리턴 타입이 AJNMessageArgument* 으로 되어있어 사용하기에 너무 번잡스러운 감이 있습니다.
다음과 같이 바꿔줍니다.

@interface ARCResponseInterfaceProxy(Private)
 
@property (nonatomic, strong) AJNBusAttachment *bus;
 
- (ProxyBusObject*)proxyBusObject;
 
@end
 
@implementation ARCResponseInterfaceProxy
     
- (NSData*)request:(NSData*)req
{

 [self addInterfaceNamed:@"com.acidraincity.ResponseInterface"];
      
 Message reply(*((BusAttachment*)self.bus.handle));
 MsgArg inArgs[1];
      
 ajn::MsgArg msgArgReq;
 msgArgReq.Set( "ay", req.length, req.bytes );
 inArgs[0] = msgArgReq;
      
 QStatus status = self.proxyBusObject->MethodCall("com.acidraincity.ResponseInterface", "request", inArgs, 1, reply, 5000);
 if (ER_OK != status) {
  NSLog(@"ERROR: ProxyBusObject::MethodCall on com.acidraincity.ResponseInterface failed. %@", [AJNStatus descriptionForStatusCode:status]);
  return nil;
 }
  
 const ajn::MsgArg* rtn = reply->GetArg();
 int len = rtn->v_scalarArray.numElements;
 const uint8_t* data = rtn->v_scalarArray.v_byte;
   
 return [NSData dataWithBytes:data length:len];
 
}
 
@end


ARCRResponseInterface 는 원격호출에 응답하는 클래스 구현입니다.
비지니스로직에 맞춰 특정 요청에 특정 응답을 하는 코드를 구현하면 됩니다.

@implementation ARCResponseInterface

- (AJNMessageArgument*)request:(AJNMessageArgument*)req message:(AJNMessage *)methodCallMessage
{

 ajn::MsgArg* reqArg = ( ajn::MsgArg* ) req.handle;
 NSData* reqData = [NSData dataWithBytes:reqArg->v_scalarArray.v_byte length:reqArg->v_scalarArray.numElements];
 
 NSData* resData = nil //TODO 응답 데이터를 비지니스 로직에 따라 구성하세요
 
 ajn::MsgArg* resArg = new ajn::MsgArg();
 resArg->Set( "ay", resData.length, resData.bytes );
  
 AJNMessageArgument* rtn = [[AJNMessageArgument alloc] initWithHandle:resArg shouldDeleteHandleOnDealloc:YES];
 return rtn;
}


@end


4.

위에서 작성한 인터페이스를 사용하여 서비스를 노출(서버)하고, 노출된 서비스에 연결(클라이언트)하는 코드 구현은 다음과 같습니다.
#define APP_NAME @"com.acidraincity"
#define SERVICE_NAME @"com.acidraincity.ResponseService"
#define INTERFACE_NAME @"com.acidraincity.ResponseInterface"
#define SERVICE_PATH @"/ResponseService"
#define COMM_PORT 42

NSString* nodeName = @"someRandomNodeName";
NSString* serviceFullName = [NSString stringWithFormat:@"%@.%@", SERVICE_NAME, nodeName];
AJNSessionOptions *sessionOptions = 
 [[AJNSessionOptions alloc] 
  initWithTrafficType:kAJNTrafficMessages 
  supportsMultipoint:NO 
  proximity:kAJNProximityAny 
  transportMask:kAJNTransportMaskAny];
    
//서버로서
 
AJNBusAttachment* bus = 
 [[AJNBusAttachment alloc] 
  initWithApplicationName:APP_NAME allowRemoteMessages:YES];
[bus start];
ARCResponseInterface* resInterface = 
 [[ARCResponseInterface alloc] initWithBusAttachment:bus onPath:SERVICE_PATH];
[bus registerBusObject:resInterface];
[bus connectWithArguments:@"null:"];
[bus registerBusListener:self];
[bus requestWellKnownName:serviceFullName withFlags:kAJNBusNameFlagReplaceExisting|kAJNBusNameFlagDoNotQueue];
[bus bindSessionOnPort:COMM_PORT withOptions:sessionOptions withDelegate:self];
[bus advertiseName:serviceFullName withTransportMask:sessionOptions.transports];
 
//클라이언트로서
 
[bus findAdvertisedName:SERVICE_NAME];

5.

AJNSessionPortListener 를 구현합니다.
- (BOOL)shouldAcceptSessionJoinerNamed:(NSString *)joiner onSessionPort:(AJNSessionPort)sessionPort withSessionOptions:(AJNSessionOptions *)options
{
    return sessionPort == COMM_PORT;
}


6.

AJNBusListener 를 구현합니다.

- (void)didFindAdvertisedName:(NSString *)name withTransportMask:(AJNTransportMask)transport namePrefix:(NSString *)namePrefix
{
    
 //버스에 새로 연결된 노드를 발견
  
 //서비스명이 다를 경우 무시    
    
 //문자열 비교하는 equals 메소드가 확장구현되어있다고 가정합니다.
 if(![namePrefix equals:SERVICE_NAME]){
  return;
 }
 
 //문자열 치환하는 replace 메소드가 확장구현되어있다고 가정합니다.
 NSString* nodeName = [name replace:[SERVICE_NAME stringByAppendingString:@"."] replacement:@""];
 
 if( [nodeName equals:myNodeName] ){
  
  //발견된 것이 자신일 경우
  
 }else{
  
  //발견된 노드가 자신이 아닌 경우
  //(클라이언트로서) 발견 노드 세션에 연결합니다
  
  [bus enableConcurrentCallbacks];
    
  AJNSessionOptions *sessionOptions = 
   [[AJNSessionOptions alloc] 
    initWithTrafficType:kAJNTrafficMessages 
    supportsMultipoint:NO 
    proximity:kAJNProximityAny 
    transportMask:kAJNTransportMaskAny];
    
  AJNSessionId sessionId = 
   [bus joinSessionWithName:name onPort:COMM_PORT withDelegate:self options:sessionOptions];
    
  if (sessionId != -1) {
   
   //(클라이언트로서)발견된 노드 세션에 연결 성공
   //(클라이언트로서)연결된 노드의 원격 메소드 콜을 할 수 있는 인터페이스를 얻어옵니다.
     
   ARCResponseInterfaceProxy* rip = 
    [[ARCResponseInterfaceProxy alloc] 
     initWithBusAttachment:bus 
     serviceName:name 
     objectPath:SERVICE_PATH sessionId:sessionId];
   [rip introspectRemoteObject];
   
  }
 }
}


세션 연결시 룩업해 얻어온 프록시 오브젝트로부터 다른 피어가 제공하는 서비스 메소드를 호출할 수 있습니다.