WebRTC 앱에 필요한 백엔드 서비스 빌드

시그널링이란 무엇인가요?

시그널링은 커뮤니케이션을 조정하는 프로세스입니다. WebRTC 앱이 통화를 설정하려면 클라이언트가 다음 정보를 교환해야 합니다.

  • 통신을 열거나 닫는 데 사용되는 세션 제어 메시지
  • 오류 메시지
  • 코덱, 코덱 설정, 대역폭, 미디어 유형과 같은 미디어 메타데이터
  • 보안 연결을 설정하는 데 사용되는 주요 데이터
  • 외부에서 보이는 호스트의 IP 주소 및 포트와 같은 네트워크 데이터

이 신호 프로세스에는 클라이언트가 메시지를 주고받을 수 있는 방법이 필요합니다. 이 메커니즘은 WebRTC API에 의해 구현되지 않습니다. 직접 빌드해야 합니다. 이 도움말의 뒷부분에서는 시그널링 서비스를 빌드하는 방법을 알아봅니다. 하지만 먼저 배경 정보가 필요합니다.

WebRTC에서 시그널링이 정의되지 않는 이유는 무엇인가요?

중복을 방지하고 기존 기술과의 호환성을 극대화하기 위해 신호 방법과 프로토콜은 WebRTC 표준에 지정되지 않습니다. 이 접근 방식은 JavaScript 세션 설정 프로토콜 (JSEP)에 설명되어 있습니다.

또한 JSEP 아키텍처는 브라우저가 상태를 저장해야 하는 것, 즉 신호 상태 머신으로 작동해야 하는 것을 방지합니다. 예를 들어 페이지를 새로고침할 때마다 신호 데이터가 손실되면 문제가 됩니다. 대신 신호 상태를 서버에 저장할 수 있습니다.

JSEP 아키텍처 다이어그램
JSEP 아키텍처

JSEP에는 위에 언급된 미디어 메타데이터인 offeranswer 간의 피어 교환이 필요합니다. 오퍼와 응답은 세션 설명 프로토콜 (SDP) 형식으로 전달되며 다음과 같습니다.

v=0
o=- 7614219274584779017 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS
m=audio 1 RTP/SAVPF 111 103 104 0 8 107 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:W2TGCZw2NZHuwlnf
a=ice-pwd:xdQEccP40E+P0L5qTyzDgfmW
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=mid:audio
a=rtcp-mux
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:9c1AHz27dZ9xPI91YNfSlI67/EMkjHHIHORiClQe
a=rtpmap:111 opus/48000/2

이 모든 SDP 용어가 실제로 무엇을 의미하는지 알고 싶으신가요? 인터넷 엔지니어링 태스크포스 (IETF) 예시를 참고하세요.

WebRTC는 SDP 텍스트의 값을 수정하여 로컬 또는 원격 설명으로 설정하기 전에 제안 또는 응답을 조정할 수 있도록 설계되었습니다. 예를 들어 appr.tcpreferAudioCodec() 함수를 사용하여 기본 코덱과 비트 전송률을 설정할 수 있습니다. SDP는 JavaScript로 조작하기가 다소 어렵고 향후 WebRTC 버전에서 JSON을 대신 사용해야 하는지에 관한 논의가 있지만 SDP를 고수하는 데는 몇 가지 이점이 있습니다.

RTCPeerConnection API 및 시그널링: 제안, 응답, 후보

RTCPeerConnection는 WebRTC 앱이 피어 간 연결을 만들고 오디오와 동영상을 통신하는 데 사용하는 API입니다.

이 프로세스를 초기화하기 위해 RTCPeerConnection에는 두 가지 작업이 있습니다.

  • 해상도 및 코덱 기능과 같은 현지 미디어 조건을 확인합니다. 이것은 제안 및 응답 메커니즘에 사용되는 메타데이터입니다.
  • 후보라고 하는 앱 호스트의 잠재적 네트워크 주소를 가져옵니다.

이 로컬 데이터가 확인되면 시그널링 메커니즘을 통해 원격 피어와 교환해야 합니다.

앨리스가 이브에게 전화를 걸려고 한다고 가정해 보겠습니다. 다음은 전체 제안/응답 메커니즘의 자세한 내용입니다.

  1. 앨리스가 RTCPeerConnection 객체를 만듭니다.
  2. 앨리스는 RTCPeerConnection createOffer() 메서드를 사용하여 제안 (SDP 세션 설명)을 만듭니다.
  3. 앨리스가 혜택을 가지고 setLocalDescription()에 전화를 겁니다.
  4. Alice는 제안을 문자열화하고 신호 메커니즘을 사용하여 Eve에게 전송합니다.
  5. 이브는 앨리스의 설정에 대해 RTCPeerConnection가 알 수 있도록 앨리스의 제안으로 setRemoteDescription()를 호출합니다.
  6. 이브가 createAnswer()를 호출하고 이 호출의 성공 콜백에 로컬 세션 설명(이브의 대답)이 전달됩니다.
  7. 이브는 setLocalDescription()를 호출하여 대답을 로컬 설명으로 설정합니다.
  8. 그런 다음 Eve는 신호 메커니즘을 사용하여 문자열로 변환된 답변을 Alice에게 보냅니다.
  9. 앨리스는 setRemoteDescription()를 사용하여 이브의 대답을 원격 세션 설명으로 설정합니다.

앨리스와 이브는 네트워크 정보도 교환해야 합니다. '후보 찾기'라는 표현은 ICE 프레임워크를 사용하여 네트워크 인터페이스와 포트를 찾는 프로세스를 의미합니다.

  1. 앨리스는 onicecandidate 핸들러를 사용하여 RTCPeerConnection 객체를 만듭니다.
  2. 네트워크 후보가 사용 가능해지면 핸들러가 호출됩니다.
  3. 핸들러에서 Alice는 시그널링 채널을 통해 문자열화된 후보 데이터를 Eve에게 전송합니다.
  4. 앨리스로부터 후보 메시지를 받은 이브는 addIceCandidate()를 호출하여 후보를 원격 피어 설명에 추가합니다.

JSEP는 ICE 후보 트릭클링을 지원하므로 발신자는 초기 제안 후 수신자에게 후보를 점진적으로 제공할 수 있고 수신자는 모든 후보가 도착할 때까지 기다리지 않고 통화에 대한 작업을 시작하고 연결을 설정할 수 있습니다.

신호용 WebRTC 코딩

다음 코드 스니펫은 전체 신호 처리 프로세스를 요약한 W3C 코드 예시입니다. 이 코드에서는 일부 신호 메커니즘 SignalingChannel이 있다고 가정합니다. 시그널링에 대해서는 나중에 자세히 설명합니다.

// handles JSON.stringify/parse
const signaling = new SignalingChannel();
const constraints = {audio: true, video: true};
const configuration = {iceServers: [{urls: 'stun:stun.example.org'}]};
const pc = new RTCPeerConnection(configuration);

// Send any ice candidates to the other peer.
pc.onicecandidate = ({candidate}) => signaling.send({candidate});

// Let the "negotiationneeded" event trigger offer generation.
pc.onnegotiationneeded = async () => {
  try {
    await pc.setLocalDescription(await pc.createOffer());
    // send the offer to the other peer
    signaling.send({desc: pc.localDescription});
  } catch (err) {
    console.error(err);
  }
};

// After remote track media arrives, show it in remote video element.
pc.ontrack = (event) => {
  // Don't set srcObject again if it is already set.
  if (remoteView.srcObject) return;
  remoteView.srcObject = event.streams[0];
};

// Call start() to initiate.
async function start() {
  try {
    // Get local stream, show it in self-view, and add it to be sent.
    const stream =
      await navigator.mediaDevices.getUserMedia(constraints);
    stream.getTracks().forEach((track) =>
      pc.addTrack(track, stream));
    selfView.srcObject = stream;
  } catch (err) {
    console.error(err);
  }
}

signaling.onmessage = async ({desc, candidate}) => {
  try {
    if (desc) {
      // If you get an offer, you need to reply with an answer.
      if (desc.type === 'offer') {
        await pc.setRemoteDescription(desc);
        const stream =
          await navigator.mediaDevices.getUserMedia(constraints);
        stream.getTracks().forEach((track) =>
          pc.addTrack(track, stream));
        await pc.setLocalDescription(await pc.createAnswer());
        signaling.send({desc: pc.localDescription});
      } else if (desc.type === 'answer') {
        await pc.setRemoteDescription(desc);
      } else {
        console.log('Unsupported SDP type.');
      }
    } else if (candidate) {
      await pc.addIceCandidate(candidate);
    }
  } catch (err) {
    console.error(err);
  }
};

작동 중인 제안/응답 및 후보 교환 프로세스를 확인하려면 simpl.info RTCPeerConnection을 참고하고 싱글 페이지 동영상 채팅 예제의 콘솔 로그를 확인하세요. 자세한 내용을 보려면 Google Chrome의 about://webrtc-internals 페이지 또는 Opera의 opera://webrtc-internals 페이지에서 WebRTC 신호 및 통계의 전체 덤프를 다운로드하세요.

피어 검색

'대화할 사람을 어떻게 찾지?'라고 묻는 멋진 방법입니다.

전화 통화의 경우 전화번호와 디렉터리가 있습니다. 온라인 영상 채팅 및 메시지의 경우 ID 및 상태 관리 시스템과 사용자가 세션을 시작할 수 있는 수단이 필요합니다. WebRTC 앱에는 클라이언트가 통화를 시작하거나 참여하고 싶다는 신호를 서로에게 보낼 수 있는 방법이 필요합니다.

피어 검색 메커니즘은 WebRTC에 의해 정의되지 않으며 여기에서는 옵션을 다루지 않습니다. 이 과정은 URL을 이메일로 보내거나 메시지로 보내는 것만큼 간단할 수 있습니다. Talky, tawk.to, Browser Meeting과 같은 영상 채팅 앱의 경우 맞춤 링크를 공유하여 통화에 초대합니다. 개발자 Chris Ball은 WebRTC 통화 참여자가 IM, 이메일, 비둘기 등 원하는 메시지 서비스를 통해 메타데이터를 교환할 수 있는 흥미로운 serverless-webrtc 실험을 빌드했습니다.

시그널링 서비스를 빌드하려면 어떻게 해야 하나요?

다시 말하지만 시그널링 프로토콜과 메커니즘은 WebRTC 표준에 의해 정의되지 않습니다. 어떤 방법을 선택하든 클라이언트 간에 신호 메시지와 앱 데이터를 교환하려면 중간 서버가 필요합니다. 웹 앱은 단순히 인터넷에 '내 친구에게 연결해 줘'라고 외칠 수 없습니다.

다행히도 시그널링 메시지는 작고 대부분 통화 시작 시에 교환됩니다. 영상 채팅 세션을 위해 appr.tc로 테스트한 결과, 신호 서비스에서 총 30~45개의 메시지를 처리했으며 모든 메시지의 총 크기는 약 10KB였습니다.

WebRTC 신호 서비스는 대역폭 요구사항이 비교적 낮을 뿐만 아니라 메시지를 중계하고 연결된 클라이언트와 같은 소량의 세션 상태 데이터를 유지하기만 하면 되므로 처리나 메모리를 많이 사용하지 않습니다.

서버에서 클라이언트로 메시지 푸시

시그널링을 위한 메시지 서비스는 클라이언트에서 서버로, 서버에서 클라이언트로 양방향이어야 합니다. 양방향 통신은 HTTP 클라이언트/서버 요청/응답 모델에 위배되지만, 웹 서버에서 실행되는 서비스에서 브라우저에서 실행되는 웹 앱으로 데이터를 푸시하기 위해 수년에 걸쳐 긴 폴링과 같은 다양한 해킹이 개발되었습니다.

최근에는 EventSource API널리 구현되었습니다. 이를 통해 서버 전송 이벤트(HTTP를 통해 웹 서버에서 브라우저 클라이언트로 전송되는 데이터)가 사용 설정됩니다. EventSource는 단방향 메시징을 위해 설계되었지만 XHR과 함께 사용하여 신호 메시지를 교환하는 서비스를 빌드할 수 있습니다. 신호 서비스는 XHR 요청으로 전달된 호출자의 메시지를 EventSource를 통해 수신자에게 푸시하여 전달합니다.

WebSocket은 동시에 양방향으로 흐를 수 있는 메시지인 전이중 클라이언트-서버 통신을 위해 설계된 보다 자연스러운 솔루션입니다. 순수 WebSocket 또는 서버 전송 이벤트 (EventSource)로 빌드된 신호 서비스의 한 가지 장점은 이러한 API의 백엔드를 PHP, Python, Ruby와 같은 언어의 대부분의 웹 호스팅 패키지에 공통적인 다양한 웹 프레임워크에서 구현할 수 있다는 것입니다.

Opera Mini를 제외한 모든 최신 브라우저는 WebSocket을 지원하며, 더 중요한 점은 WebRTC를 지원하는 모든 브라우저가 데스크톱과 모바일 모두에서 WebSocket을 지원한다는 것입니다. 메시지가 암호화되지 않은 상태로 가로채지 않도록 하고 프록시 순회 관련 문제를 줄이기 위해 모든 연결에 TLS를 사용해야 합니다. (WebSocket 및 프록시 순회에 관한 자세한 내용은 Ilya Grigorik의 High Performance Browser Networking에 있는 WebRTC 챕터를 참고하세요.)

WebRTC 클라이언트가 Ajax를 통해 메시지 서버를 반복적으로 폴링하도록 하여 신호를 처리할 수도 있지만, 이 경우 중복 네트워크 요청이 많이 발생하며 이는 특히 모바일 기기에서 문제가 됩니다. 세션이 설정된 후에도 변경사항이나 다른 피어에 의한 세션 종료가 있을 수 있으므로 피어는 시그널링 메시지를 폴링해야 합니다. WebRTC Book 앱 예시에서는 폴링 빈도를 최적화하여 이 옵션을 사용합니다.

확장 신호

신호 서비스는 클라이언트당 비교적 적은 대역폭과 CPU를 사용하지만 인기 앱의 신호 서버는 동시성이 높은 여러 위치에서 많은 메시지를 처리해야 할 수 있습니다. 트래픽이 많은 WebRTC 앱에는 상당한 부하를 처리할 수 있는 신호 서버가 필요합니다. 여기에서는 자세히 설명하지 않지만 다음과 같은 대량 고성능 메시징을 위한 여러 옵션이 있습니다.

  • eXtensible Messaging and Presence Protocol(XMPP): 원래 Jabber로 알려진 이 프로토콜은 신호에 사용할 수 있는 인스턴트 메시지를 위해 개발되었습니다. 서버 구현에는 ejabberdOpenfire가 포함됩니다. JavaScript 클라이언트(예: Strophe.js)는 BOSH를 사용하여 양방향 스트리밍을 에뮬레이션하지만 다양한 이유로 BOSH가 WebSocket만큼 효율적이지 않을 수 있으며 동일한 이유로 확장성이 떨어질 수 있습니다.) (여담이지만 Jingle은 음성 및 동영상을 지원하는 XMPP 확장 프로그램입니다. WebRTC 프로젝트는 libjingle 라이브러리(Jingle의 C++ 구현)의 네트워크 및 전송 구성요소를 사용합니다.

  • ZeroMQ (TokBox가 Rumour 서비스에 사용) 및 OpenMQ (NullMQ는 WebSocket을 통해 STOMP 프로토콜을 사용하여 웹 플랫폼에 ZeroMQ 개념을 적용)과 같은 오픈소스 라이브러리

  • WebSocket을 사용하는 상업용 클라우드 메시지 플랫폼(긴 폴링으로 대체될 수 있음)(예: Pusher, Kaazing, PubNub(PubNub에는 WebRTC용 API도 있음))

  • vLine과 같은 상업용 WebRTC 플랫폼

(개발자 Phil Leggetter의 실시간 웹 기술 가이드에 메시지 서비스 및 라이브러리의 전체 목록이 나와 있습니다.)

Node에서 Socket.io를 사용하여 신호 서비스 빌드

다음은 Node에서 Socket.io로 빌드된 시그널링 서비스를 사용하는 간단한 웹 앱의 코드입니다. Socket.io의 설계는 메시지를 교환하는 서비스를 간단하게 빌드할 수 있도록 지원하며, 내장된 방 개념으로 인해 WebRTC 신호에 특히 적합합니다. 이 예는 프로덕션 등급 신호 서비스로 확장되도록 설계되지 않았지만 비교적 적은 수의 사용자가 이해하기 쉽습니다.

Socket.io는 대체 기능이 있는 WebSocket을 사용합니다(AJAX 긴 폴링, AJAX 멀티파트 스트리밍, Forever Iframe, JSONP 폴링). 다양한 백엔드로 포팅되었지만 이 예에 사용된 Node 버전으로 가장 잘 알려져 있습니다.

이 예시에는 WebRTC가 없습니다. 웹 앱에 신호를 빌드하는 방법을 보여주기 위해 설계되었습니다. 콘솔 로그를 확인하여 클라이언트가 방에 참여하고 메시지를 교환할 때 어떤 일이 일어나는지 확인하세요. 이 WebRTC Codelab에서는 이를 완전한 WebRTC 화상 채팅 앱에 통합하는 방법을 단계별로 안내합니다.

다음은 클라이언트 index.html입니다.

<!DOCTYPE html>
<html>
  <head>
    <title>WebRTC client</title>
  </head>
  <body>
    <script src='/socket.io/socket.io.js'></script>
    <script src='js/main.js'></script>
  </body>
</html>

다음은 클라이언트에서 참조되는 JavaScript 파일 main.js입니다.

const isInitiator;

room = prompt('Enter room name:');

const socket = io.connect();

if (room !== '') {
  console.log('Joining room ' + room);
  socket.emit('create or join', room);
}

socket.on('full', (room) => {
  console.log('Room ' + room + ' is full');
});

socket.on('empty', (room) => {
  isInitiator = true;
  console.log('Room ' + room + ' is empty');
});

socket.on('join', (room) => {
  console.log('Making request to join room ' + room);
  console.log('You are the initiator!');
});

socket.on('log', (array) => {
  console.log.apply(console, array);
});

전체 서버 앱은 다음과 같습니다.

const static = require('node-static');
const http = require('http');
const file = new(static.Server)();
const app = http.createServer(function (req, res) {
  file.serve(req, res);
}).listen(2013);

const io = require('socket.io').listen(app);

io.sockets.on('connection', (socket) => {

  // Convenience function to log server messages to the client
  function log(){
    const array = ['>>> Message from server: '];
    for (const i = 0; i < arguments.length; i++) {
      array.push(arguments[i]);
    }
      socket.emit('log', array);
  }

  socket.on('message', (message) => {
    log('Got message:', message);
    // For a real app, would be room only (not broadcast)
    socket.broadcast.emit('message', message);
  });

  socket.on('create or join', (room) => {
    const numClients = io.sockets.clients(room).length;

    log('Room ' + room + ' has ' + numClients + ' client(s)');
    log('Request to create or join room ' + room);

    if (numClients === 0){
      socket.join(room);
      socket.emit('created', room);
    } else if (numClients === 1) {
      io.sockets.in(room).emit('join', room);
      socket.join(room);
      socket.emit('joined', room);
    } else { // max two clients
      socket.emit('full', room);
    }
    socket.emit('emit(): client ' + socket.id +
      ' joined room ' + room);
    socket.broadcast.emit('broadcast(): client ' + socket.id +
      ' joined room ' + room);

  });

});

(이 단계에서는 node-static에 대해 알 필요가 없습니다. 이 예시에서는 이 값이 사용됩니다.)

localhost에서 이 앱을 실행하려면 Node, Socket.IO, node-static이 설치되어 있어야 합니다. Node는 Node.js에서 다운로드할 수 있습니다 (설치는 간단하고 빠름). Socket.IO와 node-static을 설치하려면 앱 디렉터리의 터미널에서 Node Package Manager를 실행하세요.

npm install socket.io
npm install node-static

서버를 시작하려면 앱 디렉터리의 터미널에서 다음 명령어를 실행합니다.

node server.js

브라우저에서 localhost:2013를 엽니다. 브라우저에서 새 탭 또는 창을 열고 localhost:2013을 다시 엽니다. 상황을 확인하려면 콘솔을 확인하세요. Chrome 및 Opera에서는 Google Chrome 개발자 도구를 통해 Ctrl+Shift+J (또는 Mac의 경우 Command+Option+J)로 콘솔에 액세스할 수 있습니다.

신호 전송에 어떤 방식을 선택하든 백엔드와 클라이언트 앱은 최소한 이 예와 유사한 서비스를 제공해야 합니다.

신호 관련 문제

  • RTCPeerConnectionsetLocalDescription()가 호출될 때까지 후보를 수집하지 않습니다. 이는 JSEP IETF 초안에 명시되어 있습니다.
  • 트리클 ICE를 활용합니다. 후보자가 도착하면 즉시 addIceCandidate()를 호출합니다.

기성품 신호 서버

직접 롤링하지 않으려면 이전 예와 같이 Socket.IO를 사용하고 WebRTC 클라이언트 JavaScript 라이브러리와 통합된 여러 WebRTC 신호 서버를 사용할 수 있습니다.

  • webRTC.io는 WebRTC의 첫 번째 추상화 라이브러리 중 하나입니다.
  • SignalmasterSimpleWebRTC JavaScript 클라이언트 라이브러리와 함께 사용하기 위해 생성된 시그널링 서버입니다.

코드를 전혀 작성하고 싶지 않다면 vLine, OpenTok, Asterisk와 같은 회사에서 상업용 WebRTC 플랫폼을 이용할 수 있습니다.

참고로 Ericsson은 WebRTC 초기 단계에 Apache에서 PHP를 사용하여 신호 서버를 빌드했습니다. 이제는 다소 오래되었지만 비슷한 것을 고려하고 있다면 코드를 살펴보는 것이 좋습니다.

신호 보안

'보안은 아무 일도 일어나지 않도록 하는 기술입니다.'

살만 루슈디

암호화는 모든 WebRTC 구성요소에 필수입니다.

하지만 시그널링 메커니즘은 WebRTC 표준에 의해 정의되지 않으므로 시그널링을 안전하게 만드는 것은 사용자의 몫입니다. 공격자가 신호 전송을 가로채면 세션을 중지하고, 연결을 리디렉션하고, 콘텐츠를 녹화하거나 변경하거나 삽입할 수 있습니다.

시그널링을 보호하는 데 가장 중요한 요소는 암호화되지 않은 메시지가 가로채지 않도록 하는 보안 프로토콜 (예: TLS)인 HTTPS와 WSS를 사용하는 것입니다. 또한 동일한 신호 서버를 사용하는 다른 호출자가 액세스할 수 있는 방식으로 신호 메시지를 브로드캐스트하지 않도록 주의하세요.

신호 전송 후: ICE를 사용하여 NAT 및 방화벽 처리

메타데이터 신호의 경우 WebRTC 앱은 중개 서버를 사용하지만 세션이 설정된 후 실제 미디어 및 데이터 스트리밍의 경우 RTCPeerConnection는 클라이언트를 직접 또는 피어 투 피어로 연결하려고 시도합니다.

간단한 세상에서는 모든 WebRTC 엔드포인트가 직접 통신하기 위해 다른 피어와 교환할 수 있는 고유한 주소를 갖습니다.

간단한 P2P 연결
NAT 및 방화벽이 없는 세상

실제로 대부분의 기기는 하나 이상의 NAT 뒤에 있으며, 일부는 특정 포트와 프로토콜을 차단하는 바이러스 백신 소프트웨어가 있고, 많은 기기가 프록시와 회사 방화벽 뒤에 있습니다. 방화벽과 NAT는 홈 Wi-Fi 라우터와 같은 동일한 기기에 의해 구현될 수 있습니다.

NAT 및 방화벽 뒤에 있는 피어
실제 세계

WebRTC 앱은 ICE 프레임워크를 사용하여 실제 네트워킹의 복잡성을 극복할 수 있습니다. 이를 사용 설정하려면 이 도움말에 설명된 대로 앱이 ICE 서버 URL을 RTCPeerConnection에 전달해야 합니다.

ICE는 동료를 연결하는 최적의 경로를 찾으려고 노력합니다. 가능한 모든 옵션을 병렬로 시도하고 작동하는 가장 효율적인 옵션을 선택합니다. ICE는 먼저 기기의 운영체제와 네트워크 카드에서 가져온 호스트 주소를 사용하여 연결을 시도합니다. 이 작업이 실패하면 (NAT 뒤에 있는 기기의 경우 실패함) ICE는 STUN 서버를 사용하여 외부 주소를 가져오고, 이 작업이 실패하면 트래픽이 TURN 릴레이 서버를 통해 라우팅됩니다.

즉, STUN 서버는 외부 네트워크 주소를 가져오는 데 사용되고 TURN 서버는 직접 (피어 투 피어) 연결이 실패하는 경우 트래픽을 중계하는 데 사용됩니다.

모든 TURN 서버는 STUN을 지원합니다. TURN 서버는 추가 내장 릴레이 기능이 있는 STUN 서버입니다. ICE는 NAT 설정의 복잡성에도 대처합니다. 실제로 NAT 홀 펀칭에는 공개 IP:포트 주소 이상이 필요할 수 있습니다.

STUN 및/또는 TURN 서버의 URL은 RTCPeerConnection 생성자의 첫 번째 인수이며 iceServers 구성 객체에 WebRTC 앱에 의해 지정됩니다 (선택사항). appr.tc의 경우 이 값은 다음과 같습니다.

{
  'iceServers': [
    {
      'urls': 'stun:stun.l.google.com:19302'
    },
    {
      'urls': 'turn:192.158.29.39:3478?transport=udp',
      'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
      'username': '28224511:1379330808'
    },
    {
      'urls': 'turn:192.158.29.39:3478?transport=tcp',
      'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
      'username': '28224511:1379330808'
    }
  ]
}

RTCPeerConnection에 이 정보가 있으면 ICE 마법이 자동으로 실행됩니다. RTCPeerConnection는 ICE 프레임워크를 사용하여 피어 간 최적의 경로를 파악하고 필요한 경우 STUN 및 TURN 서버와 함께 작동합니다.

STUN

NAT는 비공개 로컬 네트워크 내에서 사용할 수 있는 IP 주소를 기기에 제공하지만 이 주소는 외부에서 사용할 수 없습니다. 공개 주소가 없으면 WebRTC 피어가 통신할 수 없습니다. 이 문제를 해결하기 위해 WebRTC는 STUN을 사용합니다.

STUN 서버는 공개 인터넷에 있으며 한 가지 간단한 작업을 수행합니다. 즉, NAT 뒤에서 실행되는 앱에서 수신되는 요청의 IP:포트 주소를 확인하고 해당 주소를 응답으로 다시 전송합니다. 즉, 앱은 STUN 서버를 사용하여 공개 관점에서 IP:port를 검색합니다. 이 프로세스를 통해 WebRTC 피어가 공개적으로 액세스 가능한 주소를 가져온 다음 직접 링크를 설정하기 위해 시그널링 메커니즘을 통해 다른 피어에 전달할 수 있습니다. (실제로 서로 다른 NAT는 서로 다른 방식으로 작동하며 여러 NAT 레이어가 있을 수 있지만 원리는 동일합니다.)

STUN 서버는 많은 작업을 수행하거나 많은 것을 기억할 필요가 없으므로 사양이 비교적 낮은 STUN 서버가 많은 요청을 처리할 수 있습니다.

대부분의 WebRTC 통화는 STUN을 사용하여 연결을 설정합니다(Webrtcstats.com에 따르면 86%). 하지만 방화벽 및 복잡한 NAT 구성 뒤에 있는 피어 간 통화의 경우 이 비율이 낮을 수 있습니다.

STUN 서버를 사용하는 P2P 연결
STUN 서버를 사용하여 공개 IP:포트 주소 가져오기

TURN

RTCPeerConnection는 UDP를 통해 피어 간 직접 통신을 설정하려고 시도합니다. 이 방법이 실패하면 RTCPeerConnection는 TCP를 사용합니다. 이 경우 TURN 서버를 대체로 사용하여 엔드포인트 간에 데이터를 중계할 수 있습니다.

다시 말하지만 TURN은 신호 데이터가 아닌 피어 간 오디오, 동영상, 데이터 스트리밍을 중계하는 데 사용됩니다.

TURN 서버에는 공개 주소가 있으므로 피어가 방화벽이나 프록시 뒤에 있더라도 피어가 TURN 서버에 연결할 수 있습니다. TURN 서버는 스트림을 중계하는 개념적으로 간단한 작업을 수행합니다. 하지만 STUN 서버와 달리 많은 대역폭을 소비합니다. 즉, TURN 서버는 더 강력해야 합니다.

STUN 서버를 사용하는 P2P 연결
The full Monty: STUN, TURN, signaling

이 다이어그램은 TURN의 작동을 보여줍니다. 순수 STUN이 실패하여 각 피어가 TURN 서버를 사용합니다.

STUN 및 TURN 서버 배포

테스트를 위해 Google은 appr.tc에서 사용하는 공개 STUN 서버인 stun.l.google.com:19302를 실행합니다. 프로덕션 STUN/TURN 서비스의 경우 rfc5766-turn-server를 사용하세요. STUN 및 TURN 서버의 소스 코드는 GitHub에서 확인할 수 있으며, 서버 설치에 관한 여러 정보 소스로 연결되는 링크도 확인할 수 있습니다. Amazon Web Services용 VM 이미지도 사용할 수 있습니다.

대체 TURN 서버는 restund이며, 소스 코드로 제공되며 AWS에서도 사용할 수 있습니다. Compute Engine에서 restund를 설정하는 방법은 다음과 같습니다.

  1. tcp=443, udp/tcp=3478에 필요한 경우 방화벽을 엽니다.
  2. 각 공개 IP에 대해 하나씩, 총 4개의 인스턴스를 만듭니다(표준 Ubuntu 12.06 이미지).
  3. 로컬 방화벽 구성을 설정합니다 (모든 항목에서 모든 항목 허용).
  4. 도구를 설치합니다. shell sudo apt-get install make sudo apt-get install gcc
  5. creytiv.com/re.html에서 libre를 설치합니다.
  6. creytiv.com/restund.html에서 restund를 가져와 압축을 해제합니다.
  7. wget hancke.name/restund-auth.patch를 다운로드하고 patch -p1 < restund-auth.patch로 적용합니다.
  8. libre 및 restund에 대해 make, sudo make install 실행
  9. restund.conf를 필요에 맞게 조정하고 (IP 주소를 바꾸고 동일한 공유 비밀번호가 포함되어 있는지 확인) /etc에 복사합니다.
  10. restund/etc/restund/etc/init.d/로 복사합니다.
  11. restund 구성:
    1. LD_LIBRARY_PATH을 설정합니다.
    2. restund.conf/etc/restund.conf로 복사합니다.
    3. restund.conf를 설정하여 올바른 10을 사용합니다. 포드에 새 IP 주소가 할당됩니다
  12. restund 실행
  13. 원격 머신에서 stund 클라이언트를 사용하여 테스트합니다. ./client IP:port

일대일 이상의 통신: 멀티파티 WebRTC

TURN 서비스 액세스를 위한 REST API에 관한 Justin Uberti의 제안된 IETF 표준을 살펴볼 수도 있습니다.

간단한 일대일 통화를 넘어선 미디어 스트리밍 사용 사례를 쉽게 상상할 수 있습니다. 예를 들어 동료 그룹 간의 화상 회의 또는 연사 1명과 수백만 명의 시청자가 있는 공개 이벤트가 있습니다.

WebRTC 앱은 메시 구성에서 모든 엔드포인트가 다른 모든 엔드포인트에 연결되도록 여러 RTCPeerConnection을 사용할 수 있습니다. 이는 talky.io와 같은 앱에서 사용하는 접근 방식으로, 소수의 동료에게 매우 효과적입니다. 이 경우 특히 모바일 클라이언트의 처리 및 대역폭 소비가 과도해집니다.

메시: 소규모 N자 통화
전체 메시 토폴로지: 모든 사용자가 모든 사용자에게 연결됨

또는 WebRTC 앱이 별 구성에서 다른 모든 사용자에게 스트림을 배포할 엔드포인트를 하나 선택할 수 있습니다. 서버에서 WebRTC 엔드포인트를 실행하고 자체 재배포 메커니즘을 구성할 수도 있습니다 (샘플 클라이언트 앱은 webrtc.org에서 제공).

Chrome 31 및 Opera 18부터 한 RTCPeerConnectionMediaStream를 다른 RTCPeerConnection의 입력으로 사용할 수 있습니다. 이를 통해 웹 앱이 연결할 다른 피어를 선택하여 통화 라우팅을 처리할 수 있으므로 더 유연한 아키텍처를 사용할 수 있습니다. 실제로 작동하는 모습을 보려면 WebRTC 샘플 피어 연결 중계WebRTC 샘플 다중 피어 연결을 참고하세요.

Multipoint Control Unit

엔드포인트가 많은 경우 다지점 제어 장치 (MCU)를 사용하는 것이 좋습니다. 많은 참가자 간에 미디어를 배포하는 브리지 역할을 하는 서버입니다. MCU는 화상 회의에서 다양한 해상도, 코덱, 프레임 속도에 대응하고, 트랜스코딩을 처리하고, 선택적 스트림 전달을 실행하고, 오디오와 동영상을 믹싱하거나 녹화할 수 있습니다. 다자간 통화의 경우 여러 동영상 입력을 표시하고 여러 소스의 오디오를 혼합하는 방법을 비롯해 고려해야 할 문제가 많습니다. vLine과 같은 클라우드 플랫폼도 트래픽 라우팅을 최적화하려고 시도합니다.

완전한 MCU 하드웨어 패키지를 구매하거나 직접 제작할 수 있습니다.

Cisco MCU5300 후면 보기
Cisco MCU의 뒷면

여러 오픈소스 MCU 소프트웨어 옵션을 사용할 수 있습니다. 예를 들어 Licode (이전 명칭: Lynckia)는 WebRTC용 오픈소스 MCU를 생성합니다. OpenTok에는 Mantis가 있습니다.

브라우저 외: VoIP, 전화, 메시지

WebRTC의 표준화된 특성 덕분에 브라우저에서 실행되는 WebRTC 앱과 전화 또는 화상 회의 시스템과 같은 다른 통신 플랫폼에서 실행되는 기기 또는 플랫폼 간에 통신을 설정할 수 있습니다.

SIP는 VoIP 및 화상 회의 시스템에서 사용하는 신호 프로토콜입니다. WebRTC 웹 앱과 화상 회의 시스템과 같은 SIP 클라이언트 간의 통신을 사용 설정하려면 WebRTC에 신호를 중재하는 프록시 서버가 필요합니다. 신호는 게이트웨이를 통해 흐르지만 통신이 설정되면 SRTP 트래픽 (동영상 및 오디오)은 피어 투 피어로 직접 흐를 수 있습니다.

공중 교환 전화망 (PSTN)은 모든 '일반' 아날로그 전화의 회선 교환 네트워크입니다. WebRTC 웹 앱과 전화 간 통화의 경우 트래픽이 PSTN 게이트웨이를 통과해야 합니다. 마찬가지로 WebRTC 웹 앱은 IM 클라이언트와 같은 Jingle 엔드포인트와 통신하려면 중간 XMPP 서버가 필요합니다. Jingle은 메시지 서비스의 음성 및 동영상을 지원하기 위해 Google에서 XMPP의 확장 프로그램으로 개발했습니다. 현재 WebRTC 구현은 C++ libjingle 라이브러리를 기반으로 합니다. 이 라이브러리는 Talk를 위해 처음 개발된 Jingle 구현입니다.

다양한 앱, 라이브러리, 플랫폼에서 외부와 통신하는 WebRTC의 기능을 사용합니다.

  • sipML5: 오픈소스 JavaScript SIP 클라이언트
  • jsSIP: JavaScript SIP 라이브러리
  • Phono: 플러그인으로 빌드된 오픈소스 JavaScript 전화 API
  • Zingaya: 삽입 가능한 전화 위젯
  • Twilio: 음성 및 메시지
  • Uberconference: 회의

sipML5 개발자는 webrtc2sip 게이트웨이도 빌드했습니다. Tethr와 Tropo는 OpenBTS 셀을 사용하여 WebRTC를 통해 피처폰과 컴퓨터 간의 통신을 지원하는 재난 통신 프레임워크 '서류 가방'을 시연했습니다. 이동통신사 없이 전화 통신을 할 수 있습니다.

자세히 알아보기

WebRTC Codelab에서는 Node에서 실행되는 Socket.io 시그널링 서비스를 사용하여 동영상 및 텍스트 채팅 앱을 빌드하는 방법을 단계별로 안내합니다.

WebRTC 기술 책임자인 Justin Uberti가 진행한 2013년 Google I/O WebRTC 프레젠테이션

Chris Wilson의 SFHTML5 프레젠테이션 - WebRTC 앱 소개

350페이지 분량의 책 WebRTC: APIs and RTCWEB Protocols of the HTML5 Real-Time Web에는 데이터 및 신호 경로에 관한 많은 세부정보가 제공되며 여러 상세한 네트워크 토폴로지 다이어그램이 포함되어 있습니다.

WebRTC 및 시그널링: 2년간의 경험을 통해 얻은 교훈 - 사양에서 시그널링을 제외하는 것이 좋은 아이디어였던 이유에 관한 TokBox 블로그 게시물

Ben StrongA Practical Guide to Building WebRTC Apps에서는 WebRTC 토폴로지 및 인프라에 관한 많은 정보를 제공합니다.

Ilya GrigorikHigh Performance Browser Networking에 있는 WebRTC 챕터에서는 WebRTC 아키텍처, 사용 사례, 성능을 자세히 설명합니다.