피어 연결 시작하기

피어 연결은 P2P 프로토콜을 사용하여 통신하기 위해 다양한 컴퓨터에서 두 애플리케이션을 연결하는 것을 처리하는 WebRTC 사양의 일부입니다. 피어 간 통신은 동영상, 오디오 또는 임의 바이너리 데이터 (RTCDataChannel API를 지원하는 클라이언트용)일 수 있습니다. 두 피어가 연결될 수 있는 방법을 알아보려면 두 클라이언트에서 ICE 서버 구성을 제공해야 합니다. 이는 STUN 또는 TURN 서버이며, 원격 클라이언트로 전송되는 각 클라이언트에 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은 NAT용 세션 순회 유틸리티(Session Traversal Utility)를 의미하며, 대부분의 WebRTC 애플리케이션에서 간접적으로 사용됩니다.

TURN (Relay NAT를 사용한 순회)는 STUN 프로토콜을 통합하는 고급 솔루션이며 대부분의 상용 WebRTC 기반 서비스가 피어 간 연결을 설정하는 데 TURN 서버를 사용합니다. WebRTC API는 STUN과 TURN을 직접 지원하며, 보다 완전한 인터넷 연결 설정 아래에 수집됩니다. WebRTC 연결을 만들 때 일반적으로 RTCPeerConnection 객체의 구성에 하나 이상의 ICE 서버가 제공됩니다.

Trickle ICE

RTCPeerConnection 객체가 생성되면 기본 프레임워크는 제공된 ICE 서버를 사용하여 연결 설정 (ICE 후보)의 후보를 수집합니다. RTCPeerConnectionicegatheringstatechange 이벤트는 ICE 수집의 상태 (new, gathering 또는 complete)를 나타냅니다.

피어가 ICE 수집이 완료될 때까지 기다려야 하는 경우도 있지만, 일반적으로 'trickle 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 후보를 받으면 피어 연결 상태가 결국 연결된 상태로 변경될 것입니다. 이를 감지하기 위해 RTCPeerConnection에 리스너를 추가하여 connectionstatechange 이벤트를 수신합니다.

// Listen for connectionstatechange on the local RTCPeerConnection
peerConnection.addEventListener('connectionstatechange', event => {
    if (peerConnection.connectionState === 'connected') {
        // Peers connected!
    }
});

RTCPeerConnection API 문서