📹
WebRTC는 어떻게 실시간으로 데이터를 교환할 수 있을까?

WebRTC 연결 절차에 대해 알아보고, 이 과정에서 접할 수 있는 낯선 용어들에 대해 정리해봅니다.

January 24, 2021


FrontEnd JavaScript


최근에 합류한 스타트업에서는 WebRTC를 활용한 반려동물 모니터링 웹뷰 어플리케이션을 만들고 있습니다.

잉어킹 하지만 안타깝게도 저는 반려동물이 없기 때문에(…) 집에 있는 인어킹 인형이 포효하는 영상을 찍어보았습니다

사실 저 역시도 WebRTC에 대해서 아는 것이 전혀 없는 상태에서 시작한 프로젝트였습니다. 결국 처음에는 WebRTC 자체에 대한 이해 없이 일단 돌아가기만 하는 코드 를 만들게 되었습니다. 이 때문에 기술 부채를 많이 졌지만, 다행히도 지금은 열심히 리팩토링을 해서 빚을 다 청산한 상태입니다. 덕분에 현재는 간단한 화상 통화 어플리케이션을 제작하고 유지보수할 수 있는 수준이 되었습니다.

하지만 WebRTC에서 등장하는 낯선 용어들을 아직 완벽하게 정복하지는 못했습니다. 이 때문에 오늘 포스트에서는 WebRTC가 어떤 절차를 통해 연결되고, 이 과정에서 등장하는 낯선 용어들에 대해 정리해보고 넘어가는 시간을 갖고자 합니다. 이번 포스트를 통해 WebRTC에 관심이 있으신 프론트엔드 개발자 분들께 도움이 되기를 바랍니다.

결론부터 이야기하자면

WebRTC가 실시간으로 웹에서 데이터를 교환할 수 있는 이유는 시그널링이라고 일컫어지는 NAT 우회 과정을 거치기 때문입니다.

WebRTC란?

webrtc

MDN의 WebRTC 문서에서는 WebRTC를 다음과 같이 정의하고 있습니다.

WebRTC(Web Real-Time Communication)은 웹 애플리케이션과 사이트가 중간자 없이 브라우저 간에 오디오나 영상 미디어를 포착하고 마음대로 스트림 할 뿐 아니라, 임의의 데이터도 교환할 수 있도록 하는 기술입니다.

한마디로 요약하자면 드라이버나 플러그인 설치 없이 웹 브라우저 간 P2P 연결을 통해 데이터 교환을 가능하게 하는 기술입니다. 이 WebRTC의 등장과 발전은 개인화된 웹을 상징하는 웹 3.0과도 같은 길을 걷고 있는데요.

월드 와이드 웹(World Wide Web)이 1990년대에 처음 등장했을 때, 웹은 단순히 하이퍼링크로 연결된 정적인 문서 기반의 모델이었습니다. 그러다가 2000년대 중반에 이르러서는 XHR(XMLHttpRequest) 방식을 통해 페이지 전환 없이 동적으로 데이터를 받아올 수 있게 되면서, 웹은 동적인 어플리케이션을 제작할 수 있는 플랫폼이 되었습니다. 이때 등장한 웹 어플리케이션들이 바로 지메일이나 페이스북, 트위터 같은 것들이 있죠.

WebRTC는 기존의 웹 2.0에서 한층 더 나아가, 서버와 같은 중간자를 거치지 않고 브라우저 간을 P2P로 연결하는 기술입니다. 사실 우리에게 낯설지 않은 기술들인 화상 통화와 실시간 스트리밍, 파일 공유, 스크린 공유 등이 WebRTC를 기반으로 하고 있습니다. P2P 연결은 중개 서버를 거치지 않기 때문에 빠른 속도가 보장되며, HTTPS가 강제되기 때문에 중간자 공격에 대한 보안이 보장됩니다. 그리고 실시간으로 상호작용 할 수 있다는 특성을 바탕으로 더욱 개인화되고 참여 유도적인 웹 어플리케이션을 제작할 기회가 되기도 합니다.

P2P 연결에서는 속도와 보안, 효율성을 고려해야겠지만, WebRTC가 범용적으로 사용되기 위해서는 다양한 플랫폼과 브라우저에서 접속하는 사용자들에게 동일한 사용자 경험을 제공하는 일이 첫 번째일 것입니다. 때문에 WebRTC에서 브라우저와 플랫폼 간 호환성은 가장 큰 숙제일 수밖에 없습니다.

브라우저 호환성

브라우저호환성

WebRTC는 구글이 주도한 오픈소스 프로젝트를 기반으로 하는 웹 표준이기 때문에, 특히 크롬(Chrome)에서 호환성이 높습니다. 그리고 파이어폭스(Firefox)와 오페라(Opera) 등이 WebRTC 표준을 적극적으로 후원하고 있죠. 한편 사파리(Safari) 역시 WebKit 기반 브라우저이기 때문에 WebRTC가 지원되기는 하지만, 애플의 정책이 늘 그렇듯 다른 브라우저에 비해 호환성도 가장 떨어지고 기본으로 지원해주는 설정들이 적은 편입니다. 그래서 저는 사파리를 럭키 IE(…) 라고 부르곤 합니다.

WebRTC는 아직까지 다양한 플랫폼에서 표준화가 완전히 구현되지는 않은 기술입니다. 정확히 이야기하자면 WebRTC 자체는 1.0 버전의 표준이 있지만, 이 규격을 모두 준수하는 플랫폼들이 아직까지 많지 않습니다. 그러니까 브라우저와 운영체제 별로도 호환성과 상호 운용성이 상이하다는 이야기죠. 그래서 각 브라우저의 WebRTC API에는 moz, webkit 같은 벤더 프리픽스(vendor prefix)가 붙어있습니다.

이런 크로스 브라우징 이슈를 해결하기 위해서는 adapter.js 라이브러리를 함께 사용하는 것이 필수적입니다. 이 라이브러리는 shim 패턴 및 폴리필을 이용해 다양한 브라우저에서 발생할 수 있는 크로스 브라우징 이슈를 사전에 처리해줍니다. 또한, 벤더 프리픽스를 신경 쓸 필요 없이 동일한 API를 호출할 수 있게 만들어 주기 때문에, 코드 컨벤션 유지와 개발 생산성 향상에서도 큰 도움을 줍니다.

결론적으로 WebRTC는 단일 브라우저 벤더에서 제공하는 API가 아니며, 브라우저와 운영체제별로 개발되는 속도와 지원되는 버전이 다르므로 호환성과 상호 운용성을 항상 체크해야 합니다.

P2P 절차

WebRTC는 P2P 방식의 커뮤니케이션이기 때문에 각각의 웹 브라우저는 다음과 같은 절차를 밟아야 합니다.

  1. 각 브라우저가 P2P 커뮤니케이션에 동의
  2. 서로의 주소를 공유
  3. 보안 사항 및 방화벽 우회
  4. 멀티미디어 데이터를 실시간으로 교환

여기에서 우리는 2번과 3번 단계가 일반적인 웹 개발의 접근 방법으로는 해결하기 어렵다는 것을 확인할 수 있습니다. 왜냐하면 브라우저는 웹 서버가 아니기 때문에, 외부에서 접근할 수 있는 주소가 없기 때문이죠. 때문에 WebRTC가 P2P 기반이긴 하지만 통신 설정 초기 단계에서는 중재자의 역할이 필요합니다.

방화벽과 NAT 트래버셜

일반적인 컴퓨터에는 공인 IP가 할당되어 있지 않습니다. 그 원인으로는 방화벽(Firewall), 여러 대의 컴퓨터가 하나의 공인 IP를 공유하는 NAT, 유휴 상태의 IP를 일시적으로 임대받는 DHCP 때문입니다. 이 때문에 단순히 공인 IP만을 알아낸다고 해서, 특정한 사용자를 가리킬 수는 없습니다. 공인 IP뿐만 아니라 해당 네트워크에 연결된 사설 IP 주소까지 알아내야 특정한 사용자를 지정할 수 있게 되죠.

일반적으로는 라우터가 NAT 역할을 합니다. 외부에서 접근하는 공인 IP와 포트 번호를 확인하여 현재 네트워크 내의 사설 IP들을 적절히 매핑시켜주죠. 그러니까 어떤 브라우저 두 개가 서로 직접 통신을 하려면, 각자 현재 연결된 라우터의 공인 IP 주소와 포트를 먼저 알아내야 합니다.

하지만 어떤 라우터들은 특정 주소나 포트와의 연결을 차단하는 방화벽 설정이 되어 있을 수도 있습니다. 이처럼 라우터를 통과해서 연결할 방법을 찾는 과정NAT 트래버셜(NAT traversal)이라고 합니다.

STUN, TURN

stun STUN 방식 vs TURN 방식

이 NAT 트래버셜 작업은 STUN(Session Traversal Utilities for NAT) 서버에 의해 이루어집니다. STUN 방식은 단말이 자신의 공인 IP 주소와 포트를 확인하는 과정에 대한 프로토콜입니다. 즉, STUN 서버는 인터넷의 복잡한 주소들 속에서 유일하게 자기 자신을 식별할 수 있는 정보를 반환해줍니다. 즉, WebRTC 연결을 시작하기 전에 STUN 서버를 향해 요청을 보내면, STUN 서버는 NAT 뒤에 있는 피어(Peer)들이 서로 연결할 수 있도록 공인 IP와 포트를 찾아줍니다.

쉽게 말해서 다른 사람이 우리 집에 쉽게 찾아올 수 있도록 사전에 우리 집 주소를 조회해서 알아내는 것과 같습니다. 만약 두 개의 장치가 성공적으로 STUN 서버에서 자기 자신의 주소를 알아냈을 경우에는 P2P 연결을 시도할 두 개의 고유한 주소가 생긴 셈입니다.

하지만 STUN 서버를 이용하더라도 항상 자신의 정보를 알아낼 수 있는 것은 아닙니다. 어떤 라우터들은 방화벽 정책을 달리 할 수도 있고, 이전에 연결된 적이 있는 네트워크만 연결할 수 있게 제한을 걸기도 합니다(Symmetric NAT). 이 때문에 STUN 서버를 통해 자기 자신의 주소를 찾아내지 못했을 경우, TURN(Traversal Using Relay NAT) 서버를 대안으로 이용하게 됩니다.

TURN 방식은 네트워크 미디어를 중개하는 서버를 이용하는 것입니다. TURN 방식은 중간에 서버를 한 번 거치기 때문에, 엄밀히 이야기하자면 P2P 통신이 아니게 되며 그 구조상 지연이 필연적으로 발생하게 됩니다. 하지만 보안 정책이 엄격한 개인 NAT 내부에 위치한 브라우저와 P2P 통신을 할 수 있는 유일한 방법이기 때문에, TURN 방식은 최후의 수단으로 선택되어야 합니다.

ICE와 Candidate

여태껏 이야기한 STUN, TURN 서버를 이용해서 획득했던 IP 주소와 프로토콜, 포트의 조합으로 구성된 연결 가능한 네트워크 주소들을 후보(Candidate)라고 부릅니다. 그리고 이 과정을 후보 찾기(Finding Candidate)라고 부릅니다.

이렇게 후보들을 수집하면 일반적으로 3개의 주소를 얻게 됩니다.

  • 자신의 사설 IP와 포트 넘버
  • 자신의 공인 IP와 포트 넘버 (STUN, TURN 서버로부터 획득 가능)
  • TURN 서버의 IP와 포트 넘버 (TURN 서버로부터 획득 가능)

한편, 이 모든 과정은 ICE(Interactive Connectivity Establishment)라는 프레임워크 위에서 이루어집니다. ICE는 두 개의 단말이 P2P 연결을 가능하게 하도록 최적의 경로를 찾아주는 프레임워크입니다.

지금까지의 내용을 요약하자면, ICE 프레임워크가 STUN, 또는 TURN 서버를 이용해 상대방과 연결 가능한 후보들을 갖고 있다는 것입니다. 그러니까 두 브라우저가 P2P 통신을 위해 통신할 수 있는 주소만큼은 확실하게 알아낸 셈입니다. 따라서 마지막으로 남은 것은 이제 미디어와 관련된 정보를 교환하는 것입니다.

SDP

sdp 직렬화된 SDP 데이터. JS에서 읽기 쉽게 보려면 별도의 라이브러리를 쓰는 것이 좋습니다.

SDP(Session Description Protocol)는 WebRTC에서 스트리밍 미디어의 해상도나 형식, 코덱 등의 멀티미디어 컨텐츠의 초기 인수를 설명하기 위해 채택한 프로토콜입니다. 화상 통화 어플리케이션을 예시로 들어보자면 웹캠 비디오의 해상도를 보낼 수 있고, 오디오 전송 또는 수신 여부를 보낼 수도 있겠죠.

이처럼 미디어와 관련된 초기 세팅 정보를 기술하는 SDP는 발행 구독 모델(Pub/Sub)과 유사한 제안 응답 모델(Offer/Answer)을 갖고 있습니다. 그러니까 어떤 피어가 이러한 미디어 스트림을 교환할 것이라고 제안 을 하면, 상대방으로부터 응답 이 오기를 기다린다는 의미입니다.

그렇게 응답 을 받게 되면, 각자의 피어가 수집한 ICE 후보 중에서 최적의 경로를 결정하고 협상하는 프로세스가 발생합니다. 수집한 ICE 후보들로 패킷을 보내 가장 지연 시간이 적고 안정적인 경로를 찾는 것이죠. 이렇게 최적의 ICE 후보가 선택되면, 기본적으로 필요한 모든 메타 데이터와 IP 주소 및 포트, 미디어 정보가 피어 간 합의가 완료됩니다.

이 과정을 통해 피어 간의 P2P 연결이 완전히 설정되고 활성화됩니다. 그 후 각 피어에 의해 로컬 데이터 스트림의 엔드포인트가 생성되며, 이 데이터는 양방향 통신 기술을 사용하여 최종적으로 양방향으로 전송됩니다.

이 과정에서 NAT의 보안 이슈 등으로 최선의 ICE 후보를 찾지 못할 수도 있기 때문에, 이때에는 폴백으로 세팅한 TURN 서버를 P2P 대용으로 설정합니다.

통신에 TURN 폴백을 사용할 때 각 피어는 굳이 귀찮게 P2P로 데이터를 연결하고 전송하는 방법을 알 필요가 없습니다. 대신, 통신 세션 중에 실시간 멀티미디어 데이터를 중개하는 공용 TURN 서버를 알고 있어야 합니다.

Trickle ICE

trickle Trickle은 물이 가늘게 똑똑 떨어지고 있다는 의미입니다

한편, ICE 프레임워크를 설정하다보면 Trickle ICE 라는 옵션을 심심치않게 만나볼 수 있습니다. 위의 사진을 참고했을 때, 이 단어가 혹시 무슨 옵션인지 짐작이 가시나요?

일반적으로 각 피어는 ICE 후보들을 수집해서 그 목록을 완성한 후 한꺼번에 교환하게 됩니다. 하지만 이러한 방식은 SDP의 제안 응답 모델과 맞물리면서 단점으로 작용합니다.

후보를 모으는 데에도 시간이 오래 걸리고, 그 과정에서 네트워크 환경에 따라 지연이 걸릴 수 있습니다. 또한 한 쪽 피어의 ICE 후보 수집 작업이 완료되어야만 다른 피어가 ICE 후보를 모을 수 있기 때문에 비효율적이죠.

이러한 비효율적인 후보 교환 작업을 병렬 프로세스로 수행할 수 있게 만드는 것이 바로 Trickle ICE 입니다. 두 개의 피어가 ICE 후보를 수집하고 교환하는 과정을, 동기적 프로세스 에서 비동기적 프로세스 로 만드는 기술이라고 이야기할 수 있겠네요.

즉, Trickle 옵션이 활성화된 ICE 프레임워크는 각 피어에서 ICE 후보를 찾아내는 그 즉시 교환을 시작합니다. 그래서 상호 간 연결 가능한 ICE를 보다 빨리 찾아낼 수 있죠. 이러한 옵션 덕분에 ICE 프레임워크는 피어 간의 연결 상태를 체크함과 동시에 연결에 걸리는 시간을 최적화할 수 있습니다. 결론적으로 Trickle 옵션은 가능하다면 활성화하는 게 좋겠죠?

한편 WebRTC 한글 문서에서는 이를 직역해서 물방울 ICE라고 부르는 모양이네요.

시그널링

시그널링

위에서 이야기한 모든 과정을 일컬어 시그널링(Signaling)이라고 부릅니다. 즉 RTCPeerConnection 통신에 사용할 프로토콜, 채널, 미디어 코덱 및 형식, 데이터 전송 방법, 라우팅 정보와 NAT 통과 방법을 포함한 통신 규격을 교환하기 위해 두 장치의 제어 정보를 교환하는 과정을 의미합니다.

위의 과정을 보면 알 수 있듯이 시그널링은 WebRTC 자체에서 지원하는 기능이 아닙니다. WebRTC 연결 전 미리 준비해야 하는 과정입니다. WebRTC 자체의 스펙도 아니기 때문에, 한 가지로 딱 정해진 방법이 없습니다. 정해진 방법이 없는 이유는 알 수 없는 두 장치가 언제 어떤 방식으로 연결될 수 있는지의 모든 경우를 예측하는 것이 불가능하기 때문입니다. 그래서 개발자는 자신에게 맞는 최적의 방법을 선택적으로 적용할 수 있죠.

이 때문에 일반적으로 두 개의 장치를 연결할 수 있는 시그널링 서버를 직접 구축하거나, 시그널링 서버를 제공해주는 외부 솔루션을 적용할 수 있습니다.

만약 시그널링 서버를 직접 구축한다면 웹 소켓(Web Socket)이나 서버 전송 이벤트(Server-sent Event) 방법을 적용할 수 있습니다. 시그널링 정보를 조회할 수 있는 API를 만든 후 브라우저 단에서 주기적으로 XHR을 요청하는 폴링(polling) 기법을 쓸 수도 있죠. 이 외 시그널링 서버의 구현 방법들로는 이곳을 참고해주세요.

한편 WebRTC를 처음 접하는 개발자에게는 오히려 표준이 없다는 것이 혼란을 일으키곤 하죠. 서버를 직접 구축하는 것도 일이 되구요. 때문에 시그널링 서버를 제공해주는 솔루션을 쓰는 것도 방법이 될 수 있습니다. 아마존의 Kinesis Video Stream 이 대표적인 예시이고, 구글의 AppRTC에서는 Google App Engine으로 구현된 시그널링 서버를 확인할 수 있습니다.

한계

trickle 제가 제일 좋아하는 TCP vs UDP 비교 짤

그렇다고 해서 WebRTC의 현실이 밝은 것만은 아닙니다.

첫 번째는 역시 브라우저 간 호환성 입니다. 아직 adapter.js 같은 라이브러리 없이는 다양한 브라우저의 호환성을 장담할 수 없습니다. 특히 사파리와 IE, 엣지가 그러하죠.

다음으로는 시그널링 서버에 대한 명시적인 표준이 없습니다. 개발자의 능력과 자율성에 맡겨둔 부분이 오히려 처음 WebRTC를 입문하는 사람들에게 혼란을 일으키기도 합니다.

마지막으로는 WebRTC는 기본적으로 실시간성이 매우 중요하기 때문에 UDP 위에서 동작합니다. 즉 데이터를 빠르게 전송할 수는 있지만, 이 과정에서 발생한 데이터 손실이 발생할 수도 있죠. 비디오라던가 오디오에 있어서 몇 프레임 정도가 끊기는 것은 큰 문제가 되지 않지만, 중요한 파일들을 전송할 때에는 이 데이터 손실이 문제를 일으킬 수도 있습니다. 작은 몇 바이트의 데이터 손실 때문에 전체 파일이 동작하지 않는 문제가 생길 수도 있죠. (TCP 방식을 사용할 수도 있지만 UDP를 사용할 수 없거나 미디어 스트리밍에 적합하지 않은 방식으로 제한되는 경우에만 사용되고, 모든 브라우저가 TCP 방식을 지원하는 것도 아닙니다.)

마무리

노잼 사실 저도 어렵게 느껴져요

그럼에도 불구하고 브라우저에서 P2P 통신을 가능하게 해주는 기술인 WebRTC는 앞으로의 발전이 더 기대되는 기술입니다.

사실 WebRTC에 관한 원리와 이론적인 내용을 다루다 보니, 제가 썼지만 상당히 재미가 없네요. 약간 학교 다닐 때 컴퓨터 네트워크 강의를 들었던 것이 어렴풋이 생각도 나네요.

재밌는 글은 아니지만 WebRTC와 관련된 한글 자료가 많이 없는 곳에서 이 글이 조금이나마 도움이 되기를 바랍니다.

참고자료