본문으로 건너뛰기

시그널 유형

시그널링 3가지 유형

IO 시그널은 3가지 시그널링 유형이 존재합니다.

  1. 채널 구독 발행형(멀티캐스트): 채널 구독 클라이언트에게 해당 채널에 발행된 메시지를 멀티캐스트 해주는 시그널링
  2. CID 유니캐스트형: 특정 수신자 CID를 가진 클라이언트에게만 메시지를 보내는 유니캐스트 시그널링
  3. CID 구독 발행형(멀티캐스트): 송신자 CID를 구독하고, 해당 CID가 발행한 메시지를 멀티캐스트 해주는 시그널링

IOSignal_types

일반적인 PubSub 브로커에서 지원되는 방식이 1번 방식이고, 2번 방식도 클라이언트 전용 채널값을 사용하는 방식으로 지원 가능합니다. 3번 방식은 IO시그널에서 새롭게 지원하는 방식으로, 채널 대신 특정 (송신자)클라이언트의 CID 를 구독하고 해당 CID가 발행한 메시지를 수신할 수 있습니다. 채널 구독, 채널 발행과 구분하기 위해 CID 구독, CID 발행(시그널)이라는 표현을 사용합니다. 일반적인 채널 구독 발행 방식은 발행자 제한이 없으므로, 채널명만 알면 어떤 클라이언트나 메시지를 보낼 수 있습니다. 반면 CID 구독 발행의 경우 해당 CID가 발행한 시그널만 전송됩니다.

시그널 유형별 태그 사용법

아래의 그림은 3가지 유형의 시그널링에서 사용되는 태그(tag)의 사용법을 정리한 것입니다. 이때 시그널링 유형에 따라 태그 형태의 차이를 볼 수 있습니다. 태그 형태의 차이는 CID같은 암묵적으로 알 수 있는 정보를 생략하기 때문이며, 이를 통해 코딩을 더 간단하게 해줍니다. Javascript 같은 개발 언어에서 사용되는 this 와 유사한 맥락으로 볼 수 있습니다.

IOSignal_tags

1. 채널 구독 형

일반적인 PubSub 채널 구독과 동일한 방식입니다. 이 경우 구독 태그(subscribe tag), 발행 태그(publish tag) 그리고 수신자 측 데이타 핸들러에 전달되는 태그(data handler tag, 이하 핸들러 태그)가 모두 동일한 경우입니다. 모든 태그가 동일하므로 가장 이해하기 쉽습니다. 아래는 자바스크립트와 C/C++ 소스 코드 예 입니다.

1-1. Javascript

송신 태그, 구독 태그, 수신 핸들러 태그가 모두 “channel#topic“입니다. 더불어 토픽이 생략된 “channel” 태그나 채널이 생략된 “#topic” 태그의 경우도 마찬가지로 송 수신 태그 및 핸들러 태그가 모두 동일합니다.

// receiver 수신자:   구독, 핸들러 등록시 태그

io.subscirbe("channel#topic") // 구독태그

io.on("channel#topic", handler ) // 이벤트 핸들러 등록 태그

io.listen("channel#topic", handler ) // 구독과 핸들러 등록을 한번에 하는 listen 용 태그.

// receiver data handler

function handler( [message,] tag ){ // 항상 마지막 인자 값이 태그 정보입니다.
// 핸들러 함수에 전달되는 message(payload) 인자는 없거나 여러개일 수 있습니다.
// 모든 인자를 배열로 전달 받기위해서...args 문법 사용을 권장합니다.

// tag 정보를 활용한 연산 수행이 가능합니다.
if( tag == "channel#topic"){ // true }

}

// sender 송신자: 송신(발행) 태그

io.signal("channel#topic", "message") // 시그널 태그

io.publish("channel#topic", "message") // publish 명령은 signal 명령과 동일합니다.

io.signal("channel#topic" ) // 참고로, 메시지 데이타(payload)가 없는(emtpy) 순수 시그널도 있습니다.

1-2. Arduino C/C++

아래의 코드는 IO시그널 아두이노 라이브러리에 공개된 예제에서 ready 핸들러와 데이타 핸들러 부분입니다.

시그널이 도착하면 사전에 정의된 데이타 핸들러가 호출되고 tag, payload type, payload buffer, payload size 가 인자로 전달 됩니다. 이때 전달되는 데이타를 포괄적인 의미로 payload(수화물)라는 표현을 사용합니다. 태그는 문자열 정보로 전달되므로 핸들러 내부에서 미리 약속된 태그와 비교를 하여 일치 할 경우 약속된 방식으로 데이타(페이로드)를 처리할 수 있습니다. 또한 전달되는 페이로드의 타입에 따른 해석 처리도 가능합니다. 페이로드 유형은 총 6가지가 있지만 아두이노의 경우 0:EMPTY(공백)형, 1:TEXT문자열)형, 2: BINARY바이너리)형을 기본 지원합니다. (참고로, 자바스크립트의 경우엔 6가지 자료형을 자동으로 해석 및 복원하여 전달해 주므로 따로 payload type, size 정보가 데이타 핸들러 인자로 전달 되지 않습니다.)

구독은 onReady 핸들러 내에서 실행합니다. ( READY 상태란 서버에서 CID를 부여받은 뒤 통신 준비가 된 상태입니다)

아래 예제는 데이타 핸들러에서는 수신된 태그 값을 비교 검사하여 약속된 태그가 “#search” 인 시그널을 받은 경우 응답으로 자신의 CID를 #notify 채널에 보내는 예제입니다. ( #search 와 #notify 에 대해서는 별도 문서로 소개 드리겠습니다.)

여기서 중요한건  구독 태그[1], 수신 핸들러 태그[2], 송신 시그널 태그[3]가  모두 "#screen"으로 동일한 점입니다.

송신 시그널 태그[3]는 본 예제 소스에 포함되지 않았습니다만 “#screen” 으로 동일합니다. (참고로 “#screen” 은 채널이 생략된 유형의 태그로 타겟이 홈채널로 인식됩니다. 홈채널은 장치들의 IP주소 정보를 기반으로 연계된(가령 같은 공유기를 사용하여 동일한 IP주소를 가진) 장치들끼리만 소통이 가능하도록 지원해주는 암묵적 채널입니다. 자세한 내용은 별도의 문서로 소개드리겠습니다. )

// uno-ethernet-button.ino

void onReadyHandler()
{
... 생략
io.subscribe("#search"); // [1] 구독 태그
}

// 데이타 핸들러 부분

void onMessageHandler( char *tag, uint8_t payloadType, uint8_t* payload, size_t payloadSize)
{
... 생략

if( strcmp(tag, "#search") == 0){ // [2] 데이타 핸들러 태그
io.signal( "#notify",io.cid);
}
.... 생략

}

// 다른 클라이언트 내부 소스코드내 송신 명령 코드

io.signal("#search") // [3] 시그널 송신(발행) 태그

2. CID 유니캐스트 형

이번에는 1:1 전송시 태그의 사용법에 대해 소개드립니다. 기본 규칙은 아래와 같습니다.

  • 시그널 송신시 수신자 CID를 포함한 태그를 사용합니다. “receiver_CID@topic”
  • 1:1 메시지 수신을 위한 구독을 하지 않습니다.
  • 수신자 데이타 핸들러 태그 등록시 자신의 CID 를 생략하여 입력합니다. “@topic”
  • 수신자 핸들러에 전달되는 태그도 자신의 CID(수신자CID) 부분이 생략되어 전달됩니다. “@topic”
  • 한줄요약: 송신 태그는 “CID@topic” 이지만, 수신자 측에선 “@topic” 같이 CID가 생략된 형태를 사용합니다.

2-1. Javascript 예제( Arduino C/C++ 태그도 동등 )

// 송신자
sender.signal("receiver_CID@topic", "message")

// 수신자
// 데이타 핸들러 태그에서 수신자 CID가 생략된 형태를 사용합니다.
// 구독을 하지않습니다.

receiver.on("@topic", handler) // 데이타 핸들러 등록시 태그에서 자신의 CID를 생략합니다.
receiver.listen("@topic", handler) // 구독과 핸들러 등록을 함께 해주는 listen 태그도 동일.

function handler( message,tag ){
if(tag == "@topic"){ // true } // data 핸들러에 전달되는 태그도 수신자CID가 생략됩니다.
}

topic이 생략된 다이렉트 시그널의 경우도 마찬가지로 수신자 측에서 수신자 CID가 생략됩니다.

  • 송신자 태그는 “receiver_CID@”
  • 수신자 측 태그는 CID가 생략된 “@” 가 사용 됩니다.
// 송신자
sender.signal("receiver_CID@", "message")

// 수신자
// on, listen 핸들러 등록 태그에서 자신의 CID를 생략합니다.
// 참고로 Arduino 에는 이벤트핸들러 등록 명령어인 on 과 listen은 없습니다.

receiver.on("@", handler)
receiver.listen("@", handler)

function handler( message,tag ){

if(tag == "@"){ // true }
// data 핸들러에 전달되는 태그도 자신의 CID가 제거됩니다.

}

위와 같은 1:1 유니캐스트 시그널링 태그의 경우 수신자 측 CID를 생략 했습니다. 반면 아래에 소개될 CID구독형의 경우 반대로 송신자의 CID를 생략한 태그가 사용됩니다.

2-2. 태그에서 CID를 생략하는 이유

이제 CID를 생략하는 이유를 알아봅시다. 코딩을 할때 CID 같은 확정된 값을 사용할 경우 몇가지 문제가 있습니다.

  • CID 는 서버에서 관리하는 정보이며 동적으로 변경될 수 있습니다.
  • CID 는 클라이언트에 보관되는 정보가 아니고 서버에 접속이 완료될 경우 서버에서 클라이언트로 전송됩니다.
  • 즉, CID 는 서버로부터 수신될 때 까지 확정되지 않습니다.
  • 익명 접속의 경우 서버에 새로 접속할때마다 CID가 변경됩니다.
  • 이때문에 프로그램 코드에 고정된 CID 값을 사용하는 것은 대부분의 경우 좋은 방법이 아닙니다.
  • 또한 데이타 핸들러에서 태그를 검사할 때 CID 값을 사용하여 비교할 경우 연산이 더 복잡해집니다.

이 문제를 해결하고자 CID가 타겟인 태그에서 CID를 생략하는 용법을 사용하였습니다.

  • “@” 나 “@topic” 같이 CID가 생략된 태그는 암묵적으로 해당 클라이언트 CID 가 생략된 것으로 인식합니다.
  • 이 방식을 사용하면 자신의 CID가 수시로 변해도 태그는 바뀌지 않고 동일합니다.
  • 특히 데이타 핸들러 태그를 비교 검사 할 경우, 명시적 CID 가 코드에서 생략 되므로 코드 구현이 간단해집니다.
  • 수신자 데이타 핸들러 태그가 CID가 생략된 “@” 문자로 시작할 경우 자신에게 온 다이렉트 시그널임을 알 수 있습니다.
  • 송신 시그널 태그가 CID가 생략된 “@”문자로 시작할 경우 중계 서버는 CID발행으로 인식하고 송신자 CID를 구독한 구독자들에게 시그널을 전달해줍니다.

3. CID 구독 형

이 유형의 시그널링은 채널이 아닌 CID를 구독하는 방식입니다.

CID구독과 CID 발행 태그는 CID 유니캐스트와 반대로 송신자의 시그널 전송 태그에서 송신자 CID를 생략합니다. 물론 CID구독자는 구독 태그에서 구독할 발행자의 CID를 포함해줘야 합니다.

3-1. Javascript 예제 ( Arduino C/C++도 동등)

// 송신자 (CID 발행자)
sender.signal("@topic", "message") // * 송신자 자신의 CID를 생략합니다.

// 수신자 (CID 구독자)
// 구독, 이벤트 핸들러 등록, 데이타 핸들러 태그 모두 동일하게 송신자 CID가 명시됩니다.

receiver.subscribe("sender_CID@topic") // Arduino , JS 공용

receiver.on("sender_CID@topic", handler) // on 은 JS 전용

receiver.listen("sender_CID@topic", handler) // listen 은 JS 전용

function handler( message,tag ){

if(tag == "sender_CID@topic"){ // true }
// data 핸들러에 송신자 CID가 포함되므로, 특정 송신자의 시그널을 구분할 수 있습니다.

}

3-2. CID 구독의 장점과 용도

채널을 통한 시그널링(송수신)이 가능함에도 별도로 CID를 구독하는 방식을 사용 하는 이유를 알아 봅시다.

  • 채널발행 태그보다 CID발행 태그가 더 간단합니다.
  • 채널 보다 태그 비교연산이 간단합니다.
  • 별도의 통신 채널을 생성하거나 관리할 필요가 없습니다.
  • 시그널링 서버가 구독한 CID장치의 상태정보를 전달해주는 부가 기능을 제공합니다.
  • 특정 장치의 상태정보를 다수의 장치가 실시간 공유하는 용도로 유용합니다.

4. IOSignal 시그널링의 차별성

총 3종의 시그널링 유형과 태그 사용법을 알아봤습니다. 참고로 MQTT나 Socket.io 경우도 특정 클라이언트 전용 채널(Socket.io는 room)을 ID 대신 사용하는 방식을 통해 1:1 통신이나 CID구독과 비슷한 기능을 수행할 수 있습니다. 하지만 IO 시그널은 채널과 분리된 CID 개념과 내장된 인증 기능이 더해져서 좀더 확장된 기능의 지원이 가능합니다.

  • 인증 시스템 적용으로 인증 된 CID 장치와의 소통임을 서버가 보증합니다.
  • 인증된 CID(Communication Id)는 장치용 e메일 주소와 같은 역활을 하며 장치가 아닌 서버에서 변경 가능합니다.
  • 채널과 CID의 개념과 전송 방식이 명확히 분리되어 있습니다.
  • 1:1 송신시 해당 CID를 가진 클라이언트만 수신함을 보장합니다.
  • CID 발행의 경우 해당 장치만 해당 CID로 발행(시그널송신)할 수 있습니다.
  • 즉, CID 가 공개 되더라도 다른 클라이언트는 해당 CID로 CID발행을 할 수 없습니다.
  • CID를 이용한 다이렉트 시그널의 경우도 다른 클라이언트는 해당 메시지를 수신할 수 없습니다.
  • 서버에서 CID 를 이용한 접속 상태 정보를 관리 및 공유해주는 기능이 제공됩니다. ( state 시그널 전달)
  • 서버에서 필터를 설정하면 송수신 대상을 한정할 수 있습니다.
  • 인증된 CID 장치는 암호통신이 기본으로 지원 되며 종단간 암호통신(End to End Encryption)도 가능합니다.