본문 바로가기

Computer science/WebRTC

WebRTC - peer connections

Peer connections는 WebRTC의 일부분으로 p2p로 두 개의 다른 컴퓨터에 있는 애플리케이션을 연결하는 것을 관장한다. 두 유저 사이의 연결은 비디오, 오디오 혹은 arbitrary binary data(클라이언트가 RTCDataChannel API를 지원한다면)가 될 수 있다. 

 

두 명의 유저가 연결하는 방법을 알기 위해서, 두 클라이언트는 ICE Server 구성을 필요로 한다. 이 서버는 STUN이나 TURN 서버가 될 수 있고, 이들 서버의 역할은 ICE candidates를 각각의 클라이언트에게 제공하는 것이다. ICE candidates를 전송하는 것을 보통 signaling이라고 부른다.

 

Signaling

WebRTC는 ICE(Internet Connectivity Establishment) Server와 연결하는 API들을 포함한다. 하지만 signaling component는 여기에 포함되지 않는다. Signaling은 두 유저가 어떻게 연결해야 하는가를 공유하기 위한 목적으로 사용된다. 보통은 HTTP 기반의 WebAPI(REST 혹은 다른 RPC 메커니즘)가 연결이 시작되기 전에 필요한 정보를 주고받음으로써 해결된다. 

아래의 코드는 signaling 서비스를 이용해 데이터를 비동기적으로 전송하고 수신하는지 보여준다.

// 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!');

Signaling은 여러 방법에 의해 구현될 수 있으며, WebRTC 설명서는 어떤 특별한 방법도 선호하지 않는다.

 

signaling은 3가지 종류의 정보를 교환한다.

1. Session control messages: 통신의 초기화, 종료, 에러 리포트를 위한 정보

2. Network configuration: 컴퓨터의 public IP 주소와 Port 번호 정보

3. Media capabilities: Media 데이터 전송 시 사용 가능한 코덱과 해상도에 대한 정보

 

Initiating peer connections

RTCPeerConnection 오브젝트로 peer connection을 다룰 수 있다. 이 클래스의 생성자는 하나의 RTCConfiguration 오브젝트를 파라미터로 받는다. 이 오브젝트는 어떤 방법으로 peer connection이 이루어지는지와 사용되는 ICE server에 대한 정보를 담고 있다. 

 

RTCPeerConnection이 생성되면, 이제는 SDP를 보내거나(offer) 받을(answer) 차례다. 보내는 쪽(calling peer)이라면 보내고 받는 쪽(receiving peer)이라면 받으면 된다. 

SDP가 offer나 answer가 생성되면, 다른 채널을 통해 remote peer에게 전송되어야만 한다. 이 과정을 signaling이라고 하며 이는 WebRTC에는 포함되어 있지 않다.

 

RTCPeerConnection은 다음과 같은 2가지 일을 한다.

1. local media 상태(해상도, 코덱 정보)를 알아낸다. 이 meta data는 offer/answer 시에 사용한다.

2. candidates(application's host)의 네트워크 주소를 가져온다. 

 

보내는 쪽에서 peer connection을 시작하려면, RTCPeerConnection을 생성하고 createOffer()함수를 호출해 RTCSessionDescription 오브젝트를 생성하면 된다. 이 session descriptionsetLocalDescription()함수를 이용해 local description으로 설정된다. 그 후 session description(이자 이제는 local description)은 signaling channel을 받는 쪽으로 보낸다. 받는 쪽으로부터 우리가 전송한 session description에 대한 답장을 받기 위해서는 signaling channellistener를 설정해야 한다.

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() 함수를 이용해 received offer를 설정한다. 그다음으로, createAnswer() 함수를 호출해 recieved offer에 대한 답을 만든다. 이 답을 setLocalDescription() 함수를 이용해 local description으로 설정하고, signaling server를 통해 제안을 보낸 쪽으로 답을 보낸다.

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});
    }
});

두 유저가 서로의 local and remote session descriptions(capabilities of the remote peer)를 설정했다고 해서 두 유저 사이의 연결이 준비가 완료된 것은 아니다. 두 유저의 연결이 완료하려면 각각 유저의 ICE candidates를 얻고 상대 유저에게 전송해야 한다.

 

ICE candidates

WebRTC를 이용해 두 유저가 소통하기 전에, 두 유저는 연결 정보를 공유해야 한다. 

네트워크 조건은 여러 가지 조건에 의해 다양하게 변화하기 때문에 외부 서비스(external service)가 유저들을 연결할 수 있는 후보들을 찾기 위해 사용된다. 이런 서비스가 ICE이고 STUN이나 TURN 서버를 사용한다. 대부분의 WebRTC 애플리케이션은 STUN을 우회적(indirectly)으로 사용한다. STUN은 Session Traversal of User Datagram Protocol의 약자이다. 

 

TURN(Traversal Using Relay NAT)은 STUN을 프로토콜을 포함한 보다 진보한 방법으로 대부분의 상업적 WebRTC 서비스들이 TURN 서버를 이용해 유저 간 연결을 생성한다. WebRTC API는 STUN과 TURN을 직접적(directly)으로 지원하며, ICE라는 더 완성된 용어 아래에 설명된다. WebRTC 연결을 생성할 때, RTCPeerConnection 오브젝트를 위해 하나 이상의 ICE 서버를 구성한다.

 

Trickle ICE

RTCPeerConnection 오브젝트가 생성되면, 프레임워크는 제공된 ICE 서버를 이용해 연결 시설 후보(ICE 후보)들을 모은다.  RTCPeerConnection의 icegatheringstatechange 이벤트는 ICE gathering이 어떤 상태(new, gathering, complete)에 있는지 신호를 보낸다.

 

ICE gathering이 완료될 때까지 기다릴 수도 있지만, "trickle ice" 기법을 사용해 각각의 ICE candidate를 다른 유저(remote peer)에게 보내는 것이 훨씬 효과적이다. 이 방법을 사용하면 연결 시 setup 시간을 효과적으로 줄일 수 있으며 video call에서 딜레이도 줄일 수 있다.

 

ICE candidates를 모으기 위해서는 간단히 icecandidate 이벤트 리스너를 추가하면 된다. 

// 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);
        }
    }
});

 

 

Connection established

ICE candidates를 수신하고 나면, peer connection 상태가 연결됨으로 변할 것이라고 기대할 수 있다. 이를 감지하기 위해선, RTCPeerConnection에 connectionstatechanges 이벤트를 수신(listen)할 수 있는 리스너를 추가한다. 

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

본 게시글은 www.webRTC.org 의 guide 부분을 번역한 글입니다.

'Computer science > WebRTC' 카테고리의 다른 글

WebRTC 참고 자료 모음  (0) 2020.07.10
webRTC 용어 정리  (0) 2020.07.10
WebRTC - other parts  (0) 2020.07.04
WebRTC - Media capture and constraints  (0) 2020.07.02
WebRTC - Media devices  (0) 2020.07.02