피어 연결 시작하기

피어 연결은 컴퓨터 간에 두 애플리케이션을 연결하여 피어 투 피어 프로토콜을 사용하여 통신하는 것을 다루는 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은 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 후보를 수집하려면 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!
    }
});

RTCPeerConnection API 문서