피어 연결은 피어 투 피어 프로토콜을 사용하여 통신하기 위해 서로 다른 컴퓨터의 두 애플리케이션을 연결하는 WebRTC 사양의 일부입니다. 피어 간 통신은 동영상, 오디오 또는 임의의 바이너리 데이터 (RTCDataChannel API를 지원하는 클라이언트의 경우)일 수 있습니다. 두 피어가 연결되는 방법을 검색하려면 두 클라이언트 모두 ICE 서버 구성을 제공해야 합니다. 이는 STUN 또는 TURN 서버이며 역할은 각 클라이언트에 ICE 후보를 제공하는 것입니다. ICE 후보는 원격 피어로 전송됩니다. ICE 후보 전송을 일반적으로 시그널링이라고 합니다.
신호
WebRTC 사양에는 ICE (인터넷 연결 설정) 서버와 통신하기 위한 API가 포함되어 있지만 시그널링 구성요소는 포함되어 있지 않습니다. 두 피어가 연결 방법을 공유하려면 시그널링이 필요합니다. 일반적으로 이는 웹 애플리케이션이 피어 연결이 시작되기 전에 필요한 정보를 전달할 수 있는 일반 HTTP 기반 웹 API (즉, REST 서비스 또는 기타 RPC 메커니즘)를 통해 해결됩니다.
다음 코드 스니펫은 이 가상 신호 서비스가 비동기적으로 메시지를 보내고 받는 데 사용될 수 있는 방법을 보여줍니다. 이 값은 이 가이드의 나머지 예에서 필요한 경우 사용됩니다.
// Set up an asynchronous communication channel that will be
// used during the peer connection setup
const signalingChannel = new SignalingChannel(remoteClientId);
signalingChannel.addEventListener('message', message => {
// New message from remote client received
});
// Send an asynchronous message to the remote client
signalingChannel.send('Hello!');
시그널링은 다양한 방식으로 구현할 수 있으며 WebRTC 사양은 특정 솔루션을 선호하지 않습니다.
피어 연결 시작
각 피어 연결은 RTCPeerConnection 객체에 의해 처리됩니다. 이 클래스의 생성자는 단일 RTCConfiguration 객체를 매개변수로 사용합니다. 이 객체는 피어 연결이 설정되는 방식을 정의하며 사용할 ICE 서버에 관한 정보를 포함해야 합니다.
RTCPeerConnection가 생성되면 호출 피어인지 수신 피어인지에 따라 SDP 제안 또는 응답을 생성해야 합니다. SDP 제안 또는 응답이 생성되면 다른 채널을 통해 원격 피어에 전송해야 합니다. SDP 객체를 원격 피어에 전달하는 것을 시그널링이라고 하며 WebRTC 사양에 포함되지 않습니다.
호출 측에서 피어 연결 설정을 시작하려면 RTCPeerConnection 객체를 만든 다음 createOffer()를 호출하여 RTCSessionDescription 객체를 만듭니다. 이 세션 설명은 setLocalDescription()를 사용하여 로컬 설명으로 설정된 후 신호 채널을 통해 수신 측으로 전송됩니다. 또한 수신 측에서 제공된 세션 설명에 대한 대답을 수신할 때를 위해 신호 채널에 리스너를 설정합니다.
async function makeCall() {
const configuration = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]}
const peerConnection = new RTCPeerConnection(configuration);
signalingChannel.addEventListener('message', async message => {
if (message.answer) {
const remoteDesc = new RTCSessionDescription(message.answer);
await peerConnection.setRemoteDescription(remoteDesc);
}
});
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
signalingChannel.send({'offer': offer});
}
수신 측에서는 수신되는 제안을 기다린 후 RTCPeerConnection 인스턴스를 만듭니다. 이 작업이 완료되면 setRemoteDescription()를 사용하여 수신된 혜택을 설정합니다. 그런 다음 createAnswer()를 호출하여 수신된 제안에 대한 답변을 만듭니다. 이 대답은 setLocalDescription()를 사용하여 로컬 설명으로 설정된 후 신호 서버를 통해 호출 측에 전송됩니다.
const peerConnection = new RTCPeerConnection(configuration);
signalingChannel.addEventListener('message', async message => {
if (message.offer) {
peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
signalingChannel.send({'answer': answer});
}
});
두 피어가 로컬 및 원격 세션 설명을 모두 설정하면 원격 피어의 기능을 알 수 있습니다. 피어 간 연결이 준비되었다는 의미는 아닙니다. 이를 위해서는 각 피어에서 ICE 후보를 수집하고 시그널링 채널을 통해 다른 피어로 전송해야 합니다.
ICE 후보
두 피어가 WebRTC를 사용하여 통신하려면 연결 정보를 교환해야 합니다. 네트워크 조건은 여러 요인에 따라 달라질 수 있으므로 일반적으로 외부 서비스를 사용하여 피어에 연결할 수 있는 후보를 검색합니다. 이 서비스는 ICE라고 하며 STUN 또는 TURN 서버를 사용합니다. STUN은 Session Traversal Utilities for NAT의 약자로, 대부분의 WebRTC 애플리케이션에서 간접적으로 사용됩니다.
TURN (Traversal Using Relay NAT)은 STUN 프로토콜을 통합하는 고급 솔루션이며 대부분의 상업용 WebRTC 기반 서비스는 피어 간 연결을 설정하기 위해 TURN 서버를 사용합니다. WebRTC API는 STUN과 TURN을 모두 직접 지원하며, 더 완전한 용어인 인터넷 연결 설정 아래에 모여 있습니다. WebRTC 연결을 만들 때 일반적으로 RTCPeerConnection 객체의 구성에 하나 이상의 ICE 서버를 제공합니다.
트리클 ICE
RTCPeerConnection 객체가 생성되면 기본 프레임워크는 제공된 ICE 서버를 사용하여 연결 설정 후보 (ICE 후보)를 수집합니다. RTCPeerConnection의 icegatheringstatechange 이벤트는 ICE 수집이 어떤 상태인지 (new, gathering 또는 complete) 신호를 보냅니다.
피어가 ICE 수집이 완료될 때까지 기다릴 수 있지만 일반적으로 '트리클 ICE' 기술을 사용하여 ICE 후보가 발견될 때마다 원격 피어에 전송하는 것이 훨씬 효율적입니다. 이렇게 하면 피어 연결 설정 시간이 크게 줄어들고 지연 시간이 짧은 동영상 통화를 시작할 수 있습니다.
ICE 후보를 수집하려면 icecandidate 이벤트의 리스너를 추가하면 됩니다.
해당 리스너에서 내보낸 RTCPeerConnectionIceEvent에는 원격 피어에 전송해야 하는 새 후보를 나타내는 candidate 속성이 포함됩니다 (시그널링 참고).
// Listen for local ICE candidates on the local RTCPeerConnection
peerConnection.addEventListener('icecandidate', event => {
if (event.candidate) {
signalingChannel.send({'new-ice-candidate': event.candidate});
}
});
// Listen for remote ICE candidates and add them to the local RTCPeerConnection
signalingChannel.addEventListener('message', async message => {
if (message.iceCandidate) {
try {
await peerConnection.addIceCandidate(message.iceCandidate);
} catch (e) {
console.error('Error adding received ice candidate', e);
}
}
});
연결됨
ICE 후보가 수신되면 피어 연결 상태가 결국 연결됨 상태로 변경될 것으로 예상해야 합니다. 이를 감지하기 위해 connectionstatechange 이벤트를 수신 대기하는 RTCPeerConnection에 리스너를 추가합니다.
// Listen for connectionstatechange on the local RTCPeerConnection
peerConnection.addEventListener('connectionstatechange', event => {
if (peerConnection.connectionState === 'connected') {
// Peers connected!
}
});