시그널 유형
시그널링 3가지 유형
IO 시 그널은 3가지 시그널링 유형이 존재합니다.
- 채널 구독 발행형(멀티캐스트): 채널 구독 클라이언트에게 해당 채널에 발행된 메시지를 멀티캐스트 해주는 시그널링
- CID 유니캐스트형: 특정 수신자 CID를 가진 클라이언트에게만 메시지를 보내는 유니캐스트 시그널링
- CID 구독 발행형(멀티캐스트): 송신자 CID를 구독하고, 해당 CID가 발행한 메시지를 멀티캐스트 해주는 시그널링
일반적인 PubSub 브로커에서 지원되는 방식이 1번 방식이고, 2번 방식도 클라이언트 전용 채널값을 사용하는 방식으로 지원 가능합니다. 3번 방식은 IO시그널에서 새롭게 지원하는 방식으로, 채널 대신 특정 (송신자)클라이언트의 CID 를 구독하고 해당 CID가 발행한 메시지를 수신할 수 있습니다. 채널 구독, 채널 발행과 구분하기 위해 CID 구독, CID 발행(시그널)이라는 표현을 사용합니다. 일반적인 채널 구독 발행 방식은 발행자 제한이 없으므로, 채널명만 알면 어떤 클라이언트나 메시지를 보낼 수 있습니다. 반면 CID 구독 발행의 경우 해당 CID가 발행한 시그널만 전송됩니다.
시그널 유형별 태그 사용법
아래의 그림은 3가지 유형의 시그널링에서 사용되는 태그(tag)의 사용법을 정리한 것입니다. 이때 시그널링 유형에 따라 태그 형태의 차이를 볼 수 있습니다. 태그 형태의 차이는 CID같은 암묵적으로 알 수 있는 정보를 생략하기 때문이며, 이를 통해 코딩을 더 간단하게 해줍니다. Javascript 같은 개발 언어에서 사용되는 this 와 유사한 맥락으로 볼 수 있습니다.
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)도 가능합니다.