본문 바로가기
Develop/Zoom만들기

Zoom만들기 13. WebRTC

by 보보트레인 2023. 9. 5.

WebRTC는 web real-time communication을 뜻한다.

WebRTC는 네가지 스텝을 통해 동작한다.

  1. Signalling
  2. Connecting
  3. Securing
  4. Communicationing

그리고 peer-to-peer로 데이터를 교환할 수 있게 해준다.

1. 그럼 peer-to-peer이 뭔데?

인터넷에 연결된 다수의 개별 사용자들이 중개 기관을 거치지 않고 데이터를 직접 주고받는다는 뜻이다.

 

※ socket.io로 만든 채팅 앱은 peer-to-peer가 아님. 왜냐하면 하나의 서버에 웹소켓이 여러개 이어져 있어서 서버가 중개 기관 역할을 했기 때문...

 

<장점>

  • 언제나 서버를 이용할 필요도 없다.
  • 실시간 소통 속도도 빠르다.
  • 서버를 유지하는데 드는 여러가지 비용도 줄일 수 있다.

다만 그렇다고 해서 서버가 필요없다는 소리는 아니다!

시그널링( signaling ) 을 진행해야 비로소 사용자 간에 직접 소통을 할 수 있는데, 이를 위해서는 서버가 꼭 필요하다.

시그널링은 쉽게말해 사용자들을 서로 연결하는 작업이다.

 

 


2. 시그널링  step

사용자간의 연결 = 브라우저 간의 연결

브라우저를 인식하기 위해서는 ip주소와 포트번호를 공유해야함.

 

시그널링은 3가지 스텝으로 동작한다.

  1. offer
  2. answer
  3. candidate

offer를 통해 상대방에게 소통을 제안하면, 상대방이 answer를 보내면서 서로 소통할 수 있음.

그러고 나면 candidate라는 걸 이용해 데이터를 주고받는 방식이다.

 

2-1) offer 작업

offer는 간은 서버에 접속해 있는 다른 사용자에게 서버를 거치지 말고 p2p를 제안하는 것임.

여기서 사용자란 웹소켓을 뜻하는데 제안을 보내는 쪽에서 offer객체를 만들어 다른 웹소켓에 보냄.

※ offer 객체 = 연결객체

 

그럼 연결객체를 만들어보자. ( app.js )

let myPeerConnection;
 
async function startMedia(){
    welcome.hidden = true;
    call.hidden = false;
    await getMedia();
    makeConnection();
}
 
//RTC Code
function makeConnection() {
    myPeerConnection = new RTCPeerConnection();
}

makeConnection 함수를 추가했고, 이를 startMedia() 미디어를 가져오는 함수 내부에서 호출함.

서버를 거치지 않는 직접 연결을 위해 startMedia 함수를 비동기 함수로 만들었음. (async & await)

makeConnection 함수에서는 new RTCPeerConnection(); 코드로 peer 간의 연결에 사용할 객체를 생성했음. 

→ 사용자 모두 연결 객체를 가질 수 있도록 함.

 

2-2) 트랙을 연결객체에 담기 ( 영상과 소리를 포함한다는 소리 ! ) 

myStream.getTracks() 사용하여 미디어가 포함된 배열을 받음.

//RTC Code
function makeConnection() {
    myPeerConnection = new RTCPeerConnection();
    myStream.getTracks()
    .forEach(track => myPeerConnection.addTrack(track, myStream));
}

2-3) offer 객체 생성

누군가 접속하면 welcome이벤트 발생하고 이걸 처리하는 브라우저가 offer를 보내는 주체가 됨을 이해해야함.

 

welcome 이벤트 핸들러 함수에서 offer를 생성하자.

//Socket Code
socket.on("welcome", async () => {
    const offer = await myPeerConnection.createOffer();
    console.log(offer);
});

myPeerConnection 의 createOffer 메서드를 호출해서 offer객체를 생성.

비동기로 처리해야 되서 핸들러 함수에서도 async 추가됨.

→ 기존의 someone joined! 메시지 대신 offer 객체를 확인하는 걸로 대체됨.

 

<offer객체 확인>

offer를 보내주는 주체인 첫번째 브라우저에 offer 객체가 잘 출력됨을 콘솔에서 확인가능하다.

2-4) offer 보내기

offer를 전송하기 위해 자신이 가지고 있는 myPeerConnection에 offer를 포함시켜야함.

//Socket Code
socket.on("welcome", async () => {
    const offer = await myPeerConnection.createOffer();
    myPeerConnection.seltLocalDescription(offer);
    socket.emit("offer", offer, roomName);
});

 setLocalDescription은 연결의 속성을 지정하려고 하는것. 

예를 들어 미디어 형식 같은 것이 연결의 속성이라고 할 수 있음.

→ myPeerConnection.setLocalDescription(Offer); 코드는 결국 offer객체를 보내는 주체(브라우저)가

자신의 연결 객체에 '나는 이 offer를 보낼 거야'라는 걸 명시한 것임을 이해하자. 

 

이후 socket.emit으로 offer라는 이벤트를 발생시킴

wsServer.on("connection", (socket) => {
    socket.on("join_room", (roomName, done) => {
        socket.join(roomName);
        done();
        socket.to(roomName).emit("welcome");
    });
    socket.on("offer", (offer, roomName) => {
        socket.to(roomName).emit("offer", offer);
    });
});

보내는 쪽에서 서버에 offer와 roomName을 보내면, 해당 채팅룸에 접속하는 모든 사용자에게는 offer이벤트와 함께 offer 객체가 전달됨.

 

2-5) offer 받기

offer를 받는 쪽에서 offer 이벤트에 대한 핸들러 함수를 추가해줘야함.

socket.on("offer", offer => {
    console.log(offer);
})

offer이벤트가 발생하면 전달된 offer를 콘솔에 출력하는 이벤트 함수를 만들어서 확인해보자.

※ 이 함수는 반드시 offer를 받는 쪽에서만 실행될 것.

 

<결과 확인>

offer보내는 첫번째 브라우저 콘솔창.
offer를 받는 두번째 브라우저에서 오퍼객체가 잘 출력된다.

2-6) 받은 offer에 대한 answer 생성하기

//Socket Code
socket.on("welcome", async () => {
    //offer 보내는쪽
    const offer = await myPeerConnection.createOffer();
    myPeerConnection.setLocalDescription(offer);
    console.log("sent the offer");
    socket.emit("offer", offer, roomName);
});

//offer 받는 쪽.
socket.on("offer", async(offer) => {
    myPeerConnection.setRemoteDescription(offer);
    const answer = await myPeerConnection.createAnswer();
    console.log(answer);
    myPeerConnection.setLocalDescription(answer);
})

자신이 만든 answer 객체를 자신이 가지고 있는 myPeerConnection에 포함시켜야함.

 

2-7) answer 보내기

//offer 받는 쪽.
socket.on("offer", async(offer) => {
    myPeerConnection.setRemoteDescription(offer);
    const answer = await myPeerConnection.createAnswer();
    myPeerConnection.setLocalDescription(answer);
    socket.emit("answer", answer, roomName)
})

answer 이벤트를 발생시키면서 answer 객체를 보내고 채팅룸 이름도 함께 보낼 거야. 이를 처리하는 코드를 서버에 추가해야함.

 

server.js의 코드를 다음과 같이 수정

wsServer.on("connection", (socket) => {
    socket.on("join_room", (roomName,) => {
        socket.join(roomName);
        socket.to(roomName).emit("welcome");
    });
    socket.on("offer", (offer, roomName) => {
        socket.to(roomName).emit("offer", offer);
    });
    socket.on("answer", (answer, roomName) => {
        socket.to(roomName).emit("answer", answer);
    });
});

offer를 보내는 브라우저가 이번에는 answer를 받는 브라우저가 되었음. 

이벤트 핸들러는 역시 app.js에 추가해야함

//answer 받는 쪽.
socket.on("answer", answer => {
    myPeerConnection.setRemoteDescription(answer);
})

 

구조 정리 : 브라우저 1이 브라우저 2에게 offer를 보내고, offer를 받은 브라우저 2가 브라우저 1에게 answer를 보낸다.

반응형