LODY/정리

미디어 서버와 스트리밍 / WebRTC: 실시간 연결의 구조

WebRTC: 실시간 연결의 구조

🟣 파일로 자를 수 없는 대화

HLS는 영상을 조각으로 잘라 뿌리는 대신 10초쯤의 지연을 감수했다. 중계나 방송에서는 이 지연이 문제가 안 된다. 그런데 화상 통화에서는 이야기가 완전히 달라진다. 내가 말하고 상대가 답하는 데 매번 몇 초씩 밀리면 대화가 성립하지 않는다. 실시간 소통에서는 지연 자체가 서비스의 품질이다.

그러면 HLS의 지연을 줄이면 되지 않느냐고 생각하기 쉬운데, 앞 장에서 봤듯 그 지연은 세그먼트로 자르고 버퍼를 쌓는 구조에서 나온다. 조각을 아무리 짧게 해도 파일로 모으는 발상을 유지하는 한 한계가 있다. 그래서 WebRTC는 발상을 통째로 바꾼다. 파일로 자르지 않고, 기기와 기기 사이에 직접 통로를 뚫어 영상을 곧바로 흘려보낸다. 중간에 모으는 단계가 없으니 지연이 수백 밀리초까지 내려간다.

문제는 이 "직접 통로 뚫기"가 생각보다 훨씬 까다롭다는 데 있다. 이 장은 그 통로를 뚫기 위해 WebRTC가 동원하는 장치들을 순서대로 본다.


🟣 서로의 주소를 어떻게 아나: 시그널링

두 기기가 직접 연결하려면 먼저 상대가 어디 있는지 알아야 한다. 그런데 방금 처음 통화를 거는 두 사람의 브라우저는 서로의 주소도, 지원하는 코덱도, 아무것도 모른다. 이 첫 정보 교환을 중개하는 과정이 시그널링이다.

여기서 중요한 사실 하나. WebRTC 표준은 시그널링을 어떻게 할지 정해 두지 않았다. 연결에 필요한 정보(어떤 코덱을 쓸지, 어떤 후보 주소들이 있는지)를 담은 메시지를 서로에게 전달만 하면 되고, 그 전달을 무엇으로 하든 상관없다. 그래서 실무에서는 보통 WebSocket 서버를 하나 두고, 그 서버가 두 기기 사이에서 이 초기 메시지를 중계한다.

기기 A시그널링 서버(WebSocket)기기 B연결 정보만 교환하고, 영상은 여기로 안 흐른다내 연결 정보전달

시그널링 서버는 연결을 맺는 초반에만 관여한다. 두 기기가 서로의 정보를 확인하고 직접 통로가 뚫리고 나면, 실제 영상과 음성은 이 서버를 거치지 않고 기기끼리 곧장 흐른다. 시그널링은 소개만 시켜 주고 빠지는 역할이다.


🟣 공유기 뒤에 숨은 기기: STUN과 TURN

서로의 정보를 교환했다고 바로 연결되지는 않는다. 현실의 기기 대부분은 공유기 뒤에 있다. 집이든 회사든, 기기가 실제로 쓰는 주소는 192.168.x.x 같은 내부 주소이고, 바깥 인터넷에서 이 주소로는 도달할 수 없다. 공유기가 주소를 바꿔치기(NAT)해서 내보내기 때문이다. 상대에게 내 내부 주소를 알려 줘 봐야 소용이 없다.

그래서 먼저 필요한 게 "밖에서 본 내 진짜 주소"다. 이걸 알려 주는 게 STUN 서버다. 기기가 STUN 서버에 한 번 물어보면, 서버는 "너는 밖에서 이 주소로 보인다"고 답해 준다. 이 외부 주소를 상대에게 건네면, 많은 경우 두 기기가 이걸로 직접 연결에 성공한다.

문제는 일부 엄격한 공유기 환경에서는 외부 주소를 알아도 직접 연결이 안 뚫린다는 것이다. 이때 최후의 수단이 TURN 서버다. TURN은 직접 통로를 포기하고, 모든 영상을 자기가 대신 중계한다. 두 기기가 각자 TURN 서버에 붙고, 서버가 한쪽에서 받은 걸 다른 쪽으로 넘긴다. 직접 연결이 아니니 지연과 서버 비용이 늘지만, 어떤 환경에서도 연결은 된다는 보장을 준다.

기기 A(공유기 뒤)STUN외부 주소 알려줌TURN영상 중계기기 B(공유기 뒤)내 외부 주소는?직접 연결(성공 시)직접 실패 시 우회중계

정리하면 연결에는 우선순위가 있다. 직접 연결을 먼저 시도하고, STUN으로 외부 주소를 알아내 그걸로 뚫어 보고, 그래도 안 되면 TURN으로 중계한다. 가능한 한 직접, 안 되면 우회라는 순서다. 이 후보들을 모아 하나씩 시도하는 절차를 ICE라고 부른다.


🟣 세 명부터 무너지는 P2P

여기까지는 두 명의 통화다. 그런데 참여자가 셋, 넷으로 늘면 직접 연결 방식이 급격히 무너진다. 이유는 연결 수에 있다.

모두가 모두와 직접 연결하는 방식(P2P 메시)에서는, 참여자가 N명이면 각자가 나머지 N-1명 모두에게 자기 영상을 따로 보내야 한다. 4명이면 각자 3개씩 올려보내야 하고, 전체 연결은 여섯 갈래가 된다. 참여자가 늘수록 각 기기가 감당할 업로드 대역폭과 연결 수가 제곱에 가깝게 불어난다. 휴대폰의 업로드 대역폭으로는 서너 명만 돼도 버겁다.

P2P 메시: 각자가 모두에게ABCDSFU: 서버가 받아 나눠줌SFUABCD

🟣 SFU: 한 번만 올리고 서버가 나눠 준다

이 문제를 푸는 게 SFU(Selective Forwarding Unit)라는 중계 서버다. 발상은 단순하다. 각 참여자는 자기 영상을 SFU에 한 번만 올린다. 그러면 SFU가 그 영상을 받아 나머지 참여자들에게 대신 나눠 보낸다. 기기 입장에서는 업로드가 한 갈래로 고정되니, 참여자가 늘어도 자기 부담은 거의 그대로다. 늘어나는 부하는 서버가 떠안는다.

여기서 앞 장의 TURN과 헷갈리기 쉬운데, 역할이 다르다. TURN은 연결이 안 뚫릴 때 어쩔 수 없이 통을 그대로 중계하는 우회로다. SFU는 처음부터 다수 연결을 감당하려고 설계된 중계 서버로, 누가 어떤 화질을 받을지 골라서 보내는 것 같은 판단까지 한다. 이름에 "선택적(Selective)"이 붙은 이유다. 네트워크가 느린 참여자에게는 낮은 화질만 골라 보내는 식이다.

이 대목에서 앞 장의 ABR과 같은 발상이 다시 보인다. HLS에서는 클라이언트가 화질을 골랐지만, SFU에서는 서버가 참여자별로 내보낼 화질을 고른다. 위치는 다르지만 네트워크 사정에 맞춰 화질을 조절한다는 원리는 동일하다. 실시간이라 그 판단을 클라이언트가 기다릴 여유가 없어 서버로 옮겨 왔을 뿐이다.

물론 대가가 있다. SFU는 강력한 서버가 필요하고 운영 비용이 든다. 그래서 실제 서비스는 참여자 수로 갈라 쓴다. 두 명이거나 아주 소수면 직접 연결이나 P2P로 서버 없이 처리하고, 다자간이면 SFU를 태운다. 앞 장들에서 반복된 것처럼 여기서도 하나의 정답 대신 상황에 맞춘 조합이다.


🟣 정리

  • 실시간 소통에서는 지연 자체가 품질이다. HLS의 세그먼트 구조로는 이 지연을 못 줄이기 때문에, WebRTC는 파일로 자르는 발상을 버리고 기기 간 직접 연결로 간다.
  • 시그널링은 두 기기가 서로의 연결 정보를 처음 교환하는 과정이다. WebRTC 표준은 방법을 정하지 않아 보통 WebSocket 서버로 중계하며, 연결이 뚫리면 영상은 이 서버를 거치지 않는다.
  • 대부분의 기기는 공유기 뒤(NAT)에 있어 내부 주소로는 도달할 수 없다. STUN이 외부 주소를 알려 직접 연결을 돕고, 그래도 안 되면 TURN이 영상을 대신 중계한다. 직접을 먼저, 우회는 최후로 시도한다.
  • 모두가 모두와 연결하는 P2P 메시는 참여자가 늘면 연결 수와 업로드 부담이 제곱으로 불어나 서너 명에서 무너진다.
  • SFU는 각자 한 번만 올리게 하고 서버가 나눠 보내 이 문제를 푼다. 참여자별로 화질을 골라 보내는 판단은 HLS의 ABR과 같은 원리이며, 위치만 클라이언트에서 서버로 옮겨 왔다.

면접에서 WebRTC를 물으면 "P2P라 빠르다"에서 멈추는 경우가 많다. 한 단계 더 내려가는 지점은 "그러면 NAT 뒤의 기기끼리 어떻게 연결되나(STUN/TURN)"와 "다자간에서는 왜 P2P가 안 되나(SFU)"다. 이 둘을 설명할 수 있으면 실제로 화상 서비스를 설계해 본 사람이라는 신호가 된다. 실무에서 통화 품질 이슈를 파고들면 결국 어떤 후보로 연결됐는지, TURN으로 우회 중인지를 확인하는 데서 원인이 갈린다.

지금까지 세 개 장은 프로토콜과 구조를 개념으로 정리했다. 마지막 장은 이걸 로컬에 직접 세운다. RTMP로 영상을 받아 HLS로 변환해 내보내는 미디어 서버를 nginx로 구성하고 Docker로 묶어, 카메라 입력이 브라우저 재생까지 도달하는 길을 손으로 확인한다.