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. 앨리스는 혜택을 문자열화하고 신호 메커니즘을 사용하여 이브에게 전송합니다.
  5. 이브가 윤아의 제안을 받아 setRemoteDescription()에 전화를 걸어 RTCPeerConnection에서 윤아의 설정을 알 수 있도록 합니다.
  6. 이브가 createAnswer()를 호출하고 이에 대한 성공 콜백에는 로컬 세션 설명(이브의 답변)이 전달됩니다.
  7. 이브는 setLocalDescription()를 호출하여 답변을 지역 설명으로 설정합니다.
  8. 그런 다음 이브는 신호 메커니즘을 사용하여 문자열화된 답변을 앨리스에게 보냅니다.
  9. 앨리스는 setRemoteDescription()를 사용하여 이브의 답변을 원격 세션 설명으로 설정합니다.

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

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

JSEP는 ICE 후보 트릭링(ICE Candidate Trickling)을 지원합니다. 이를 통해 호출자는 최초 제안 후 피호출자에게 후보자를 점진적으로 제공할 수 있으며, 수신자는 모든 후보자가 도착할 때까지 기다리지 않고도 통화에서 조치를 취하고 연결을 설정할 수 있습니다.

신호를 위한 코드 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을 참고하고 콘솔 로그에서 단일 페이지 영상 채팅 예를 확인하세요. 더 자세히 알아보려면 Chrome의 about://webrtc-internals 페이지 또는 Opera의 opera://webrtc-internals 페이지에서 WebRTC 신호 및 전체 덤프를 다운로드하세요.

피어 검색

이것은 "상담할 사람을 어떻게 찾나요?"라고 질문하는 것입니다.

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

동종 업체 검색 메커니즘은 WebRTC에 의해 정의되지 않으므로 여기에서 옵션을 다루지 않습니다. 이 과정은 URL을 이메일로 보내거나 메시지를 보내는 것처럼 간단할 수 있습니다. Talky, tawk.to, 브라우저 회의와 같은 영상 채팅 앱의 경우 맞춤 링크를 공유하여 사용자를 통화에 초대할 수 있습니다. 개발자인 Chris Ball은 WebRTC 통화 참여자가 메신저, 이메일, 홈피전 등 원하는 메시지 서비스로 메타데이터를 교환할 수 있는 흥미로운 serverless-webrtc 실험을 개발했습니다.

신호 서비스를 빌드하려면 어떻게 해야 할까요?

재차 강조하지만, 신호 프로토콜과 메커니즘은 WebRTC 표준에 의해 정의되지 않습니다. 어떤 방식을 선택하든 클라이언트 간에 신호 메시지와 앱 데이터를 교환하려면 중개 서버가 필요합니다. 안타깝게도 웹 앱은 인터넷에 '나를 친구에게 연결해 줘'라고 외치지 못합니다.

다행히 신호 메시지는 작으며 통화 시작 시 주로 교환됩니다. 영상 채팅 세션 appr.tc로 테스트할 때 총 크기가 약 10KB인 모든 메시지의 크기로 신호 서비스에서 총 약 30~45개의 메시지를 처리했습니다.

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의 고성능 브라우저 네트워킹에서 WebRTC 장을 참조하세요.)

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

체중계 신호

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

  • 확장 가능 메시지 및 접속 상태 프로토콜(XMPP). 원래는 신호에 사용할 수 있는 인스턴트 메시징을 위해 개발된 Jabber-a 프로토콜입니다. 서버 구현에는 ejabberdOpenfire가 포함됩니다. Strophe.js와 같은 JavaScript 클라이언트는 BOSH를 사용하여 양방향 스트리밍을 에뮬레이션하지만 다양한 이유로 인해 BOSH는 WebSocket만큼 효율적이지 않을 수 있으며 같은 이유로 확장성이 떨어질 수 있습니다. 탄젠트에서 Jingle은 음성 및 동영상을 사용하기 위한 XMPP 확장 프로그램입니다. WebRTC 프로젝트는 Jingle의 C++ 구현인 libjingle 라이브러리의 네트워크 및 전송 구성요소를 사용합니다.)

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

  • Pusher, Kaazing, PubNub과 같이 WebSocket을 사용하는 상용 클라우드 메시징 플랫폼 (단, 긴 폴링으로 대체될 수 있음)(PubNub에는 WebRTC용 API도 있습니다.)

  • 상용 WebRTC 플랫폼(예: vLine)

(개발자 Phil Leggetter의 실시간 웹 기술 가이드에서 메시징 서비스 및 라이브러리의 포괄적인 목록을 제공합니다.)

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

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

Socket.io는 AJAX 긴 폴링, AJAX 멀티파트 스트리밍, Forever iframe, JSONP 폴링과 같은 대체 기능과 함께 WebSocket을 사용합니다. 다양한 백엔드로 포팅되었지만 이 예시에서 사용되는 노드 버전으로 가장 잘 알려져 있습니다.

이 예에는 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.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에서는 Ctrl+Shift+J (Mac에서는 Command+Option+J)를 사용하여 Chrome 개발자 도구를 통해 콘솔에 액세스할 수 있습니다.

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

신호 관련 유의사항

  • RTCPeerConnectionsetLocalDescription()가 호출될 때까지 후보 수집을 시작하지 않습니다. 이는 JSEP IETF 초안에 명시되어 있습니다.
  • Trickle ICE를 이용해 보세요. 지원자가 도착하는 즉시 addIceCandidate()에 전화하세요.

미리 준비된 신호 서버

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

  • webRTC.io는 WebRTC를 위한 최초의 추상화 라이브러리 중 하나입니다.
  • SignalmasterSimpleWebRTC JavaScript 클라이언트 라이브러리와 함께 사용하도록 만들어진 신호 서버입니다.

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

기록을 위해 Ericsson은 WebRTC 초기에 Apache에서 PHP를 사용하여 신호 서버를 구축했습니다. 지금은 더 이상 사용되지 않지만 유사한 것을 고려하고 있다면 코드를 살펴볼 필요가 있습니다.

신호 보안

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

샐만 루시디

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

그러나 신호 메커니즘은 WebRTC 표준에 의해 정의되지 않으므로 사용자가 직접 신호를 안전하게 설정해야 합니다. 공격자가 신호를 하이재킹하는 데 성공하면 세션을 중단하고 연결을 리디렉션하며 콘텐츠를 기록, 변경 또는 삽입할 수 있습니다.

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

신호 후: ICE를 사용하여 NAT 및 방화벽에 대처

메타데이터 신호의 경우 WebRTC 앱이 중간 서버를 사용하지만 세션이 설정되면 실제 미디어 및 데이터 스트리밍을 위해 RTCPeerConnection는 클라이언트에 직접 또는 P2P 연결을 시도합니다.

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

간단한 P2P 연결
NAT와 방화벽이 없는 환경

실제로 대부분의 기기는 하나 이상의 NAT 레이어에 상주하며 특정 포트와 프로토콜을 차단하는 바이러스 백신 소프트웨어를 포함하고 있으며 대부분은 프록시와 기업 방화벽의 보호를 받습니다. 방화벽과 NAT는 실제로 가정용 Wi-Fi 라우터와 같은 동일한 장치에 의해 구현될 수 있습니다.

NAT 및 방화벽 뒤의 피어
실제 세상

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

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

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

모든 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 서버를 사용합니다.

스턴트

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

STUN 서버는 공개 인터넷에 상주하며 한 가지 간단한 작업이 있습니다. NAT 뒤에서 실행되는 앱에서 수신되는 요청의 IP:port 주소를 확인하고 해당 주소를 응답으로 다시 전송하는 것입니다. 즉, 이 앱은 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 서버에는 스트림을 릴레이하는 개념적으로 간단한 작업이 있습니다. 하지만 STUN 서버와는 달리 기본적으로 많은 대역폭을 소비합니다. 즉, TURN 서버는 더 강력해야 합니다.

STUN 서버를 사용한 P2P 연결
전체 Monty: STUN, TURN, 신호

다음 다이어그램은 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 서버가 복구되어, 소스 코드로 사용할 수 있으며 AWS에도 사용할 수 있습니다. 다음은 Compute Engine에서 RESTund를 설정하는 방법에 대한 안내입니다.

  1. tcp=443, udp/tcp=3478에 대해 필요에 따라 방화벽을 엽니다.
  2. 각 공개 IP, Standard Ubuntu 12.06 이미지마다 하나씩, 총 4개의 인스턴스를 만듭니다.
  3. 로컬 방화벽 구성을 설정합니다 (ANY에서 모든 항목 허용).
  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. 다음을 구성합니다.
    1. LD_LIBRARY_PATH을 설정합니다.
    2. restund.conf/etc/restund.conf로 복사합니다.
    3. 오른쪽 10을 사용하도록 restund.conf를 설정합니다. IP 주소.
  12. 달리기
  13. 원격 머신(./client IP:port)에서 stund 클라이언트를 사용하여 테스트

일대일 이상: 다자간 WebRTC

TURN 서비스 액세스를 위한 REST API에 대해 저스틴 우버티가 제안한 IETF 표준도 살펴보시기 바랍니다.

단순한 일대일 통화를 넘어서는 미디어 스트리밍 사용 사례를 쉽게 상상할 수 있습니다. 예를 들어 동료 그룹 간의 화상 회의나 발표자 한 명과 수백 또는 수백만 명의 시청자가 참여하는 공개 일정을 예로 들 수 있습니다.

WebRTC 앱은 여러 RTCPeerConnections를 사용하여 모든 엔드포인트가 메시 구성의 다른 모든 엔드포인트에 연결되도록 할 수 있습니다. 이는 talky.io와 같은 앱에서 사용하는 접근 방식이며 소수의 동료에게 매우 잘 작동합니다. 그 이후에는 특히 모바일 클라이언트의 경우 처리 및 대역폭 소비가 과도하게 발생합니다.

메시: 소규모 N방향 호출
풀 메시 토폴로지: 모든 사용자가 모두에게 연결

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

Chrome 31 및 Opera 18부터는 하나의 MediaStream을 다른 RTCPeerConnection의 입력으로 사용할 수 있습니다. 이렇게 하면 웹 앱이 연결할 다른 피어를 선택하여 호출 라우팅을 처리할 수 있으므로 아키텍처를 보다 유연하게 구성할 수 있습니다. 실제 동작을 확인하려면 WebRTC 샘플 피어 연결 릴레이WebRTC 샘플 여러 피어 연결을 참고하세요.

멀티포인트 제어 장치

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

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

Cisco MCU5300 후면 이미지
Cisco MCU 뒷면

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

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

WebRTC의 표준화된 특성으로 인해 브라우저에서 실행되는 WebRTC 앱과 다른 통신 플랫폼(예: 전화 또는 화상 회의 시스템)에서 실행되는 기기 또는 플랫폼 간에 통신을 설정할 수 있습니다.

SIP는 VoIP 및 화상 회의 시스템에서 사용하는 신호 프로토콜입니다. WebRTC 웹 앱과 SIP 클라이언트(예: 화상 회의 시스템) 간의 통신을 사용 설정하려면 WebRTC에서 신호를 중재할 프록시 서버가 필요합니다. 신호는 게이트웨이를 통해 이루어져야 하지만, 통신이 이루어지면 SRTP 트래픽 (비디오 및 오디오)이 직접 P2P 방식으로 흐를 수 있습니다.

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

많은 앱, 라이브러리 및 플랫폼에서 외부 세계와 통신하는 WebRTC 기능을 사용합니다.

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

sipML5 개발자들은 webrtc2sip 게이트웨이도 빌드했습니다. Tethr 및 Tropo는 WebRTC를 통해 피처폰과 컴퓨터 간 통신을 가능하게 하는 OpenBTS 셀을 사용하여 '서류 가방 안에' 재해 커뮤니케이션을 위한 프레임워크를 시연했습니다. 이동통신사가 없는 전화 통신입니다.

자세히 알아보기

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

2013년 Google I/O WebRTC 프레젠테이션(WebRTC 기술 책임자, Justin Uberti)

크리스 윌슨의 SFHTML5 프레젠테이션 - WebRTC 앱 소개

350페이지 분량의 책인 WebRTC: API 및 RTCWEB 프로토콜(HTML5 실시간 웹의 API 및 RTCWEB 프로토콜)은 데이터 및 신호 전달 경로에 대한 많은 세부정보를 제공하며 다수의 상세한 네트워크 토폴로지 다이어그램을 포함합니다.

WebRTC 및 신호: 2년간의 교육 사례 - 신호를 사양에서 제외하는 것이 좋은 생각에 관한 TokBox 블로그 게시물

Ben StrongWebRTC 앱 빌드를 위한 실용적인 가이드에서는 WebRTC 토폴로지 및 인프라에 관한 많은 정보를 제공합니다.

일리야 그리고릭의 고성능 브라우저 네트워킹에 있는 WebRTC 챕터에서는 WebRTC 아키텍처, 사용 사례, 성능을 자세히 살펴봅니다.