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

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

  1. 앨리스는 onicecandidate 핸들러로 RTCPeerConnection 객체를 만듭니다.
  2. 핸들러는 네트워크 후보가 사용 가능해지면 호출됩니다.
  3. 핸들러에서 앨리스는 신호 채널을 통해 문자열로 변환된 후보 데이터를 이브에게 전송합니다.
  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, 브라우저 회의와 같은 영상 채팅 앱의 경우 맞춤 링크를 공유하여 사용자를 통화에 초대합니다. 개발자 크리스 볼은 WebRTC 통화 참여자가 IM, 이메일, 비둘기 등 원하는 메시지 서비스로 메타데이터를 교환할 수 있는 흥미로운 serverless-webrtc 실험을 구축했습니다.

신호 전달 서비스를 빌드하려면 어떻게 해야 하나요?

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

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

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

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

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

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

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

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

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

확장 신호

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

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

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

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

  • vLine과 같은 상용 WebRTC 플랫폼

개발자 필 레그게터의 실시간 웹 기술 가이드에서 메시지 서비스 및 라이브러리의 포괄적인 목록을 확인할 수 있습니다.

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

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

Socket.io는 대체 수단(AJAX 긴 포ーリング, AJAX 멀티파트 스트리밍, Forever Iframe, JSONP 폴링)이 있는 WebSocket을 사용합니다. 다양한 백엔드로 포팅되었지만 이 예시에서 사용된 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에서는 Ctrl+Shift+J (Mac의 경우 Command+Option+J)를 사용하여 Google 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 서버와 협력합니다.

STUN

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

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

STUN 서버는 많은 작업을 하거나 많은 정보를 기억할 필요가 없으므로 상대적으로 사양이 낮은 STUN 서버도 많은 요청을 처리할 수 있습니다.

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

STUN 서버를 사용한 피어 투 피어 연결
STUN 서버를 사용하여 공개 IP:포트 주소 가져오기

TURN

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

다시 한번 강조하지만 TURN은 신호 데이터가 아닌 피어 간에 오디오, 동영상, 데이터 스트리밍을 전달하는 데 사용됩니다.

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

STUN 서버를 사용한 피어 투 피어 연결
전체 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 서버는 restund이며 소스 코드로 제공되며 AWS에서도 사용할 수 있습니다. Compute Engine에서 restund를 설정하는 방법은 다음과 같습니다.

  1. 필요한 경우 tcp=443, udp/tcp=3478에 대해 방화벽을 엽니다.
  2. 공개 IP마다 하나씩 표준 Ubuntu 12.06 이미지를 사용하여 인스턴스 4개를 만듭니다.
  3. 로컬 방화벽 구성을 설정합니다 (ANY에서 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. make, sudo make install를 실행하여 libre 및 restund를 실행합니다.
  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. restund 실행
  13. 원격 머신에서 스터드 클라이언트를 사용하여 테스트합니다. ./client IP:port

일대일 외: 멀티파티 WebRTC

TURN 서비스에 액세스하기 위한 REST API에 관한 저스틴 우베르티의 제안된 IETF 표준도 살펴보세요.

간단한 일대일 통화를 넘어서는 미디어 스트리밍의 사용 사례는 쉽게 상상할 수 있습니다. 예를 들어 동료 그룹 간의 화상 회의나 한 명의 연사와 수백 또는 수백만 명의 시청자가 참여하는 공개 이벤트가 여기에 해당합니다.

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

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

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

Chrome 31 및 Opera 18부터 한 RTCPeerConnectionMediaStream가 다른 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 트래픽 (동영상 및 오디오)은 피어 투 피어로 직접 전송될 수 있습니다.

공중 전화 교환망 (PSTN)은 모든 '일반적인' 아날로그 전화의 회로 전환 네트워크입니다. 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는 OpenBTS 셀을 사용하여 재해 커뮤니케이션을 위한 프레임워크를 '브리프케이스'로 시연하여 WebRTC를 통해 피처폰과 컴퓨터 간의 통신을 지원했습니다. 이동통신사 없이 전화 통신을 하는 것입니다.

자세히 알아보기

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

WebRTC 기술 책임자인 저스틴 우베르티가 참여한 2013년 Google I/O WebRTC 프레젠테이션

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

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

WebRTC 및 신호 전달: 2년 동안 얻은 교훈 - 신호 전달을 사양에서 제외하는 것이 좋은 이유에 관한 TokBox 블로그 게시물

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

Ilya Grigorik고성능 브라우저 네트워킹WebRTC 장에서는 WebRTC 아키텍처, 사용 사례, 성능을 자세히 설명합니다.