webRTC로 화상통화 만들어보기!

Ryan Ham·2024년 12월 10일
1

Unreal Engine

목록 보기
31/31
post-thumbnail

webRTC

webSocket과 SocketIO를 거쳐 이제 대망의 webRTC를 다뤄보자. 다시 한번 recap 하자면 webSocket은 HTTP와 비슷하게 server-client 모델을 가지고 있지만, 클라이언트에서 서버로 단방향 request가 아닌 서로에게 request, response가 가능한 구조이다. webRTC는 webSocket과 같이 RTC(Real Time Communication)이 가능하지만, TCP가 아닌 UDP 베이스며 클라이언트끼리 한번 채널이 생성되고 나면 서버를 통하지 않고 P2P로 직접 소통이 가능하다.

webRTC는 javascript에서 네이티브로 지원해주고 바로 모던 웹 브라우저에서 사용 가능하기 때문에 별도의 설치는 필요하지 않다.

Stream에서 Track 가져오기

Stream과 Track에 대해 잠시 알아보자. Track은 오디오 트랙, 미디어 트랙, 자막 트랙과 같이 단일 데이터 컴포넌트라 하면 Stream은 이 Track들의 집합체이다. 보통 webRTC에서는 navigator.mediaDevices.getUserMedia()로 Media Stream을 가져오고 이 Stream으로부터 Stream.getVideoTracks(), Stream.getAudioTracks()로 개별 트랙에 접근할 수 있다.

Signaling Server란?

webRTC의 가장 도드라진 점은 client들끼리 server의 개입없이 P2P로 통신할 수 있다는 점이다. 하지만, client끼리의 초기 채널을 뚫기 위해서는 몇몇 서버가 개입될 필요가 있는데 그중 가장 핵심적인 서버가 바로 이 signaling server이다.

Signaling server로 초기 채널을 만들기 위해서는 크게 2번의 작업이 필요하다. 1번째 작업은 client들끼리 한번의 핑퐁 과정을 거처 각각의 local, remote description을 생성하는 것이고, 2번째 작업은 피어들이 서로 소통할 수 있는 수단인 ICE candidate들을 모다 다시 한번 핑퐁의 과정을 거친다. 이를 그림과 코드로 자세히 알아보자.

첫 번째 핑퐁(Local/Remote description 생성)

//app.js
...

// Step 1. 자신이 만든 방에 누군가 들어오면 offer를 생성해서 
// 자신의 local description을 만들고, 들어온 사람에게 offer를 전송
socket.on("welcome", async ()=>{
    const offer = await myPeerConnection.createOffer()
    myPeerConnection.setLocalDescription(offer)
    socket.emit("offer", offer, roomName)
    console.log(offer)
})

// Step 2. 방에 입장한 사람은 Step 1에서 전달된 offer를 통해 자신의 remote description을 만든다.
// 그 후, answer를 만들고 이를 통해 자신의 local description을 만든다.
// 만든 answer은 방의 생성자에게 다시 전달
socket.on("offer", async(offer) => {
    myPeerConnection.setRemoteDescription(offer)
    const answer = await myPeerConnection.createAnswer()
    myPeerConnection.setLocalDescription(answer)
    socket.emit("answer", answer, roomName)
})

// Step 3. 방의 생성자는 Step 2에서 전달된 answer를 통해 자신의 remote description을 만든다.
// 이로써 방의 생성자와 참가자 모두에게 각각 local, remote description이 1개씩 생성되었다. 
socket.on("answer", answer=>{
    myPeerConnection.setRemoteDescription(answer)
})

두 번째 핑퐁(ICE candidates 주고 받기)

//app.js

// Step 1. 각각의 클라이언트에서 방에 입장하게 되면 RTC 커넥션 객체를 생성하게 되는데,
// 여기서 "icecandidate"라는 이벤트를 듣는 이벤트 리스너와 handleIce라는 콜백 함수를 만든다. 
myPeerConnection = new RTCPeerConnection()
myPeerConnection.addEventListener("icecandidate", handleIce)

function handleIce(data){
    socket.emit("ice", data.candidate, roomName)
}

// Step 2. emit된 "ice" 이벤트를 받으면 넘어온 ice 데이터를 addIceCandidate 함수에 넣어서 실행 
socket.on("ice", ice =>{
    myPeerConnection.addIceCandidate(ice)
})

STUN Server란?

NAT의 등장!

분명 webRTC는 서버를 쓰지 않는다고 했는데, 또 다른 서버인 STUN server가 또 등장했다! 이건 또 뭘까? 우리가 local 환경에서 사실 이 서버는 필요가 없지만 온라인으로 나가게 된다면 우리의 local 단말기들은 NAT라는 장치를 거쳐 나가게 된다. 여기서 이 STUN 서버의 역할은 클라이언트로 하여금 자신의 public IP 주소를 찾을 수 있게 도와준다.

그럼 클라이언트는 자신의 public IP주소를 모른다는 것일까? 결론부터 말하자면 맞다! NAT안에 있는 클라이언트들은 자신의 private IP(192.168.x.x나 10.x.x.x)를 사용하게 되고, 이것이 NAT을 통해 publicIP로 변환되는 과정은 클라이언트가 알지 못한다.

이를 우리의 프로젝트로 가져온다면, 클라이언트는 STUN 서버에게 “나의 public IP 주소와 포트번호를 알려줘”라고 요청을 한다. 이 정보로, webRTC를 통신할 수 있는 채널을 만든다.

다행히도 구글에서 무료로 제공하고 있는 STUN 서버들이 있다(STUN 서버 링크). 이를 활용해서 코드에 추가해보자.

TURN server

방화벽의 등장

우리의 보안망은 튼튼해서 NAT에 더불에 방화벽까지 존재할 수 있다. 방화벽은 클라이언트끼리 직접적인 webRTC 소통을 막을 수 있는데, 이를 해결하기 위해서는 방화벽을 해지하거나 아니면 중간에 TURN 서버 경유하여서 통신할 수 있다.

STUN 서버까지는 클라이언트에게 자신의 publicIP와 port를 알려준다음 P2P 통신을 가능하게 하지만, TURN 서버가 들어오는 순간서부터는 모든 정보가 TURN 서버를 거쳐 도달하기 때문에 사실상 P2P 통신이라 부르기 힘들다.

데모!

Ngrox로 공개 url을 얻을 수 있다.

지금 프로젝트에서는 express로 localhost에서 호스팅하고 있기 때문에 이를 외부에서 접근하려면 추가적인 작업을 해야한다! Localhost로 서빙을 해도 이를 외부 url로 만들어 주는 localtunnel이라는 플러그인이 있는데 현재 관리가 잘 되고 있지 않아 잘 작동하지 않았다. 그래서 찾아보다가 ngrox 플러그인을 찾게 되었고 성공적으로 잘 작동했다!

처음에 사이트에 가입하고 auth token을 받아 관련 command를 terminal 돌려주는 작업이 필요하다. 그후, terminal 창을 2개띄워서 하나는 server.js를 돌리는 terminal, 다른 하나는 server.js에서 설정한 포트번호로 ngrox command를 돌리면 된다!

랜딩 페이지에서 방 생성 화면

노트북 <-> 핸드폰과 실시간 화상 통화

나는 노트북에서 "Ryan"이란 이름을 방을 생성하였고, 핸드폰으로 접속해 같은 방 이름을 치고 들어왔다. 왼쪽은 노트북 화면이고 오른쪽은 핸드폰 화면이다. 각측에서 오디오 stream과 비디오 stream을 토글시킬 수 있는 버튼들인 "Mute"와 "Turn Camera Off"이 각각 존재하고 그 밑에는 streaming하는 디바이스의 어떤 카메라를 사용할 수 있는지 선택할 수 있는 버튼이 있다. 모바일 카메라와 들어오니 해상도가 달라서 오른쪽 UI가 약간 깨져 버튼이 밑으로 밀려버렸다.

이번 실습에서는 Video와 Audio에 대해서만 실시간 stream을 보냈지만, webRTC로는 채팅, 게임 패킷, 파일 등 무긍무진하게 P2P로 보낼 수 있다. 웹 통신은 대부분 server-client 모델을 가지고 있는 것을 생각하면 webRTC는 client끼리 바로 통신이 가능하다는 엄청난 강점이 있고, 프로젝트의 방향성에 맞다면 webRTC을 적극 사용해보면 좋을 것이다.


코드

https://github.com/jerryhtw/webRTC_test/tree/webRTC


Reference

https://support.medialooks.com/hc/en-us/articles/360000213312-%D0%95nvironment-signaling-STUN-and-TURN-servers

노마드 코더
https://www.youtube.com/@nomadcoders

profile
🏦KAIST EE | 🏦SNU AI(빅데이터 핀테크 전문가 과정) | 📙CryptoHipsters 저자

0개의 댓글