[1] WebRTC SFU/N:M 채팅(for Beginner) : Logic 정리

BigChoi·2022년 3월 4일
0

WebRTC

목록 보기
1/2
post-thumbnail

뜨거운 감자 Metavarse

코로나 이후 메타버스가 뜨거운 감자로 떠오르며, webrtc와 websocket 기술에 대한 수요가 눈의 띄게 증가한 것 같습니다.
이번에 사내에서도 음성채팅에 대한 수요가 있었고,
WebRTC 원리를 이해하기 위해 mediasoup, kurento, jitsi 가 아닌 express만을 사용하여 SFU 서버를 구축을 스터디한 내용을 정리한 글입니다.

N:M 채팅을 하기 위한 방법으로는
MESH - MCU - SFU가 있으며, 사진은 참고하면 어떻게 데이터 통신이 이루어지는지 한 눈에 확인할 수 있습니다.

SFU 서버 개발

서버 사양 및 사용할 npm

https express, cors, socket.io, wrtc

wrtc는 express 서버단에서 peerconnection을 생성할 모듈입니다.

자세한 사용방법에 대해서는 개발을 모두 진행한 뒤에 정리하도록 하겠습니다.

개발방법:

클라이언트 :

  1. 클라이언트가 접속시 RTCPeerConnection 객체를 생성합니다.
    내가 가지고 있는 미디어 스트림 addTrack 메소드의 전달인자로 등록한다.
    이벤트 리스너를 등록합니다.(icecandidate, connectionstatechange)
    ※ 연결이 잘 수립되는지 확인하기 위해 connectionstatechange 이벤트 리스너도 등록해주면 디버깅을 하실 때 도움이 됩니다.(서버 1 이동)
    createOffer 메소드를 통해 offer 생성 후 setLocalDescription 메소드에 offer를 전달인자로 등록 후 생성한 offer를 socket.io를 통해 전달합니다.
    ✍ 여기서 중요한 점! addTrack 이벤트는 등록하지 않습니다. 클라이언트가 접속을 하자마자 RTCPeerConnection를 수립하는 이유는 나의 스트림을 서버에 전달하기 위한 용도의 연결이기 때문에 서버와의 연결로부터 미디어 스트림을 받을 필요도 없고 이벤트 캐치조차 되지 않기 때문입니다!
  2. 서버에서 answer를 받으면 클라이언트는 받은 answer를 처음에 생성한 setRemoteDescription 메소드의 전달인자로 등록해주어야 합니다. 모두 일련의 과정이 모두 마무리가 되면 서버와 클라이언트 단에 등록한 icecandidate 이벤트가 실행됩니다. 해당 이벤트가 발생되면 candidate가 이벤트 객체에 들어있는데, candidate를 socket.io를 통해 다시 한 번 주고받게 되고 각 커넥션 addIceCandidate메소드에 전달받은 candidate를 전달인자로 등록해주고 나면 완벽한 연결이 이루어지게 됩니다.
  3. 기존에 있던 유저 혹은 새로 들어온 유저의 데이터를 서버로부터 받으면 위의 과정을 다시 한번 진행하게 되는데, 여기서 앞선 것과 다른 점은 addTrack 이벤트 리스너를 등록해줘야 합니다. (클라 4 이동)
    왜냐하면 모든 클라이언트에서 보냈던 미디어 스트림 정보들을 넣어서 보내줄 거기 때문에, 클라이언트에서는 다른 유저들의 미디어 스트림을 받아서 처리해야되기 때문입니다!
  4. 서버로 offer를 보낼 때 나의 소켓id와 미디어 스트림을 받을 (내가 아닌 다른 유저)의 소켓id를 같이 서버로 보낸다.(서버 4 이동)
  5. 나간 유저의 소켓 아이디를 받으면 peerconnection 객체를 조회 후 close 메소드를 호출하면 클라와 서버 모두 연결이 해제됩니다!

서버 :

  1. 클라이언트에서 offer를 보내면 서버에서도 peerconnection 객체를 생성합니다.
    객체를 생성 후 클라이언트에서 이벤트 리스너를 등록했던 것과 동일하게 작업을 해줘야 합니다.
    다만, 중요한 것은 클라이언트에서는 빠진 addTrack 이벤트도 등록을 해주어야 합니다.
    클라이언트로부터 받은 미디어스트림을 다른 유저에게 전달하기 위해서는 꼭 필요합니다!

    이벤트 등록이 끝나고 클라이언트로부터 받은 offer를 setRemoteDescription 메소드의 전달인자로 등록해줘야합니다.
  2. setRemoteDescription를 설정해주었다면 createAnswer 메소드를 통해 answer를 생성 후 setLocalDescription메소드의 전달인자로 등록 후 생성한 answer를 socket.io를 통해 클라이언트로 전달합니다.(클라 2 이동)
  3. 클라이언트와 서버간의 p2p 연결이 끝이나면 내가 아닌 다른 유저들의 데이터를 받아합니다.
    socket.io로 접속한 클라이언트들의 접속 정보를 서버에서 가지고 있다가 먼저 접속해있던 유저에게는 새로운 유저가 접속했다는 정보를, 이제 접속을 한 유저에게는 기존에 먼저 접속해있던 유저의 정보를 socket.io를 통해 보내줍니다.(클라 3 이동)
  4. socket.io 이벤트로 들어온 offer, mysocketid, otheruserid를 받으면 앞서 peerconnection 객체를 생성했던 과정을 다시 하게 됩니다. 서버에서는 addTrack 메소드를 호출해야하는데, 여기에 otheruserid로 받은 다른 유저의 미디어 스트림을 찾아서 클라이언트로 보내줄 스트림을 등록해줘야합니다. 그래야 클라이언트에서 addTrack 이벤트 리스너 캐치가 되기 때문입니다.
  5. 유저가 나갔을 때는 비교적 간단합니다. 나간 유저의 소켓 아이디를 통해 저장해두었던 peerconnection 객체를 찾아서 close 메소드 호출을 해주고, 클라이언트에 연결되는 peerconnection 또한 찾아서 close, 클라이언트에게 나간 유저의 소켓 아이디를 전달합니다. (클라 5 이동)

느낀점

이번 WebRTC 스터디를 통해 느낀점을 한 단어로 표현하자면 '바보'입니다.
여러 자료들을 참고하면서도 '도대체 어떻게 흘러가는 거지..? 나는 바보인가...?'라며 자책했고.. 전반적인 흐름을 이해한 뒤에도 '나는 바보였나..? 이걸 왜 이해 못 했지..?'라며 자책했습니다..😭😭
그래도 실제로 돌아가는 것을 확인해보니 신기했고, 실제 서비스를 하는 회사의 미디어서버는 어느 정도의 스펙을 가지고 있어야할지.. 경이로움과 궁금증을 모두 갖게 되었습니다.
nodejs는 단인 스레드이기 때문에 스터디한 내용을 절대 현업에서 써먹을 수 없겠지만 그래도 적어도 어떤 로직으로 흘러가는구나를 이해한 스터디여서 만족스럽습니다.

다음 글에서는 socket.io와 pm2를 통해 클러스터링하고, openssl의 무료인증서를 통해 실제 서버에 https 프로토콜로 테스트한 내용을 정리하고, 로직 정리가 끝나면 코드 작성해보도록 하겠습니다!

참고자료

https://millo-l.github.io/WebRTC-%EC%9D%B4%EB%A1%A0-%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0/

https://nomadcoders.co/noom

https://developer.mozilla.org/ko/docs/Web/API/WebRTC_API

https://webrtc.org/

profile
천천히 한 걸음씩

0개의 댓글