[Project 나만의 무기] - 회고 02.21~27 D-19~13 (3주차-SFU구현)

novxerim·2022년 2월 26일
3

SW-Jungle

목록 보기
57/59
post-thumbnail
post-custom-banner

📖 02.21 월요일

1. 공부 진행 상황

  1. 공부 진행 상황
    • 일정 회의 결과 SFU로 변경해보는 것이 좋겠다고 판단. 공부 시작
    • Algorithm X

2. 기술적 고민

  1. 기술적 고민

    [발견한 버그]

    • 3D관 입장시 흰화면
    • 방 이동시 뒤로가기로 하면 새로고침 한 번 더 해야 방 입장 됨
    • 캠 없는 유저도 문제없이 되는지?
    • 5명 접속시 2:3 -> 4:1 그룹 이동시 에러

    ⇒ 위 문제들 local말고 직접 우리끼리도 링크들어가서 다시 테스트 해보기


    • 화면 공유 기능 추가 됨.
    • 다대다 연결이 잘 안되던게 혹시 STUN 문제일 수도 있을까? 지금은 임시 구글 STUN을 사용중이라.. STUN에 대해서 공부해보자. → 딱히 관련 없는 것 같다.
      • SFU STUN/TURN ICE는 피어(Peer)들과 연결하기 위한 최적의 경로를 찾으려 합니다. 동시에 가능한 모든 것을 시도하며 동작하는 가장 효율적인 선택을 골라냅니다. ICE는 첫번째로 다음과 같이 디바이스 운영체제로와 네트워크 카드로부터 획득한 호스트 주소를 사용하여 연결을 생성하려 합니다. 만약 실패한다면 (디바이스가 NAT 뒤에 있을 것입니다.) ICE는 STUN 서버를 이용하여 외부 주소를 획득하고 그것이 실패한다면 TURN 중계 서버를 통해 트래픽을 라우팅합니다. 다시 말해서
    • 한계를 어떻게 실험해봤는지! 아키텍쳐의 한계를 알기, 아키텍쳐 관계도 그리기
      임계값들을 잘 알아야 설계하는데 도움이 된다.
    • crud api 명세서
    • 여기서 async-await을 왜 사용했는지?
    • 왜 pm2, npm, yarn을 사용했는지?
    • 로드밸런스?

    [실험]

    • 로컬 1인테스트인 것 감안
    • 5명, 5명 2그룹이 되었을 때 부터 느려짐

    • 두 번 째 이미지 - 영상별 손 모양 참고

3. 성찰

  1. 성찰
    • 죽이되든 밥이되든 엉덩이 붙이고 앉아 공부하자. 버티자
    • 프로젝트는, 협업은, 서로 보고 배우며 해나가는 것이다.
    • 예외상황을 먼저 생각하는 습관을 들이자
    • 뭘 할지 계속해서 생각하자

📖 02.22 화요일

1. 공부 진행 상황

  1. 공부 진행 상황
    • 포스터세션 해야 함
    • Algorithm X

2. 기술적 고민

  1. 기술적 고민
    • Mesh→SFU
      • 팀원들이 mediasoup 설치가 안되는 현상 발생으로 지체 됨.
    • 일반 webrtc를 사용하면 모든 피어가 데이터를 다른 모든 피어와 별도로 보내고 받아야합니다. 화상 채팅을하는 동료가 10 명이라고 가정 해 보겠습니다. 그런 다음 모든 피어는 비디오를 동시에 9 번 전송하고 9 번도 받아야합니다. 모든 피어는 일반적으로 가지고 있지 않은 많은 양의 업로드 대역폭을 사용합니다. SFU는 모든 피어가 하나의 스트림 만 미디어 서버에 보내고 해당 서버가 다른 피어에 대한 모든 라우팅을 수행하도록함으로써이 문제를 해결합니다. 이렇게하면 모든 피어가 스트림 1 개만 보내고 9 개를받습니다. 다운로드 최대 다운로드 대역폭은 일반적으로 업로드 대역폭보다 높습니다. 피어의 사용 가능한 대역폭에 따라 품질을 자동으로 전환하는 simulcast라는 것도 있습니다. 나는 이것을 mediasoup 으로 달성 할 수 있었다 .
    • DB쪽을 더 시험, 공부해보고 싶은데 우리 프로젝트에서 DB를 더 사용해야하는 기능이 있을까? → 우리 프로젝트에서는 유저 정보 / 룸 정보 말고는 딱히 없다.

3. 성찰

  1. 성찰
    • 내가 우리팀 백엔드 아키텍쳐를 짜보자! → 리드미에 작성
    • 코로나 터짐

📖 02.23 수요일

1. 공부 진행 상황

  1. 공부 진행 상황
    • SFU 구현 공부...ing
    • Algorithm X

2. 기술적 고민

  1. 기술적 고민
    • SFU 구현하기
      • SFU+Mesh 둘 다 적용
      • 접속시 기본 연결 방법은 SFU로, 그 이후는 Mesh접속으로.
      • 원래는 기본 접속시 Mesh로 연결하게 하고, 접속 인원 수가 많아질 경우 SFU로 접속되게 하는 것이 이상적이나, 우리는 SFU가 잘 작동이 된다면 처음부터 SFU방식으로 접속되는 것이 유저 입장에서 가장 빠르고 좋은 케이스라고 생각되어 SFU를 기본으로 사용해보기로 함
    • DTLS 데이터그램 전송 계층 보안 프로토콜 https://brunch.co.kr/@sangjinkang/33
      - transport.connect의 파라미터로 들어오는 DTLS 파라미터. 보안 관련때문에 있나 본데 이게 왜 SFU에 있을까? 서버를 통해야하니까 서버는 보안이 기본이라 있는걸까?? 안전한 데이터 전달을 위해서?
      - 클라이언트-서버간의 통신이니까 그 과정에서 누군가가 중간에 탈취해가는 것을 막기 위해서 보안을 하는 걸까?
      - 그걸 중간에 탈취해갈 이유가 뭐가 있나? 어쨌든 개인정보니까..
      - DTLS는 왜 필요한가? DTLS를 통해, 데이터그램을 주고받는 양쪽 엔드 포인트의 중간 네트워크에서, 전송 중인 데이터를 훔쳐보거나 위조하는 등의 악성 행위를 막을 수가 있다.
      - 유튜브 - RTMPS 스트리밍 동영상 프로토콜을 통해 스트림을 암호화하고,
      넷플릭스 - OpenVPN 보안 프로토콜을 지원한다고 한다.
      - 보안 프로토콜의 종류 → SSL/TLS가 대표적. 시간 나면 DTLS/TLS의 차이점도.
      - 보안 프로토콜에는 여러 종류가 있지만, WebRTC같이 미디어 전송을 중심으로 할 때는 주로 DTLS라는 프로토콜을 사용하는 것 같다.
      - 보안까지는 이정도만 공부하고 넘어가고 얼른 SFU구현 완성에 집중하자!
        


3. 성찰

  1. 성찰
    - 도저히 SFU코드를 어떻게 합쳐야 할지 모르겠어서 타인이 작성한 코드도 여러 개 읽어봤고, 유튜브 영상도 찾아봤다. 그리고 결국 클론해온 코드 중 하나를 가지고, 파일을 하나 만들어 특정 함수를 넣고 빼보고 해보면서 특정 함수가 어떤 기능을 하는지 알아가는 방식을 사용했다.
    - 파트4 영상보고, 실행해보고(npm start)
    - 기능별로 삭제해보며 뭐가 뭔지 localhost: 들어가서 파악해보기!

📖 02.24 목요일

1. 공부 진행 상황

  1. 공부 진행 상황
    • 영상화면 - 접속시 자기 화면은 우선으로 나오게 설정
    • 민수 - 공유기능 그림 같이 보기.. 마우스 포인터
    • 에러 수정
      • 구글 로그인시 유저데이터 안불러와짐. Anonymous로 뜨는 현상
        • 해당 코드
          // Overworld.js 539~541 line
          //Draw Game Objects
                Object.values(charMap)
                  .sort((a, b) => {
                    return a.y - b.y;
                  })
                  .forEach((object) => {
                    object.sprite.draw(ctx, cameraPerson);
          
                    **const objectNicknameContainer = document.querySelector(
                      `.${object.id}`
                    );**
    • Algorithm X

2. 기술적 고민

  1. 기술적 고민
    • SFU 구현하기
      • 코드 합치기
      • 일단 돌아가게만 만들자는 마음으로 합치고, 뜨는 에러를 수정해나가는 식으로 진행
      • 진척이 되고있다!! 얼른 끝내자ㅜㅜ
      • 1차 테스트) 코드 합치고 캐릭터 오류 수정 (server 코드를 다시 io로 수정)
        • 본인 화면 이외 상대방 화면은 뜨지 않음, 따로 발생한 에러도 없음

3. 성찰

  1. 성찰
    • 코로롱으로 몸상태가 들쑥날쑥이었지만 책상 앞에 앉아 오늘도 잘 해나갔다.
    • 알고리즘 풀어야 하는데..!! ㅜㅜㅜ 내일은 무조건 개인 시간도 내서 풀자.

📖 02.25 금요일

1. 공부 진행 상황

  1. 공부 진행 상황

    • Mediasoup code Logic 참고사이트
      • (Send)
      1. 빌더를 이용해 PeerConnectionFactory를 만들어준다. 여기서 비디오와 오디오 트랙을 생성함
      2. 통신을 위한 소켓을 만들어준다. 이때 소켓은 미디어를 보내주는 소켓과 받아오는 소켓 둘다 만들어준다. 그리고 이 소켓으로 미디어서버와 connect한다.
      3. Mediasoup의 device 클래스를 load한다.
      4. device를 이용해 SendTransport를 만든다.
      5. PeerConnectionFactory로 통해 만들어진 트랙을 앞서만든 sendTransport에 produce해준다.
      • (Receive)

        기본적으로 Send와 동일하나 몇가지 차이점이 있다.
        SendTransport 대신 RecvTransport를 사용한다.
        produce를 한후 consume을 해야한다. RecvTransport를 통해 consume이 가능하다.
        consume을 하게되면 미디어 데이터를 받아오게된다.

    코드 흐름 정리

    1. getRtpCapabilities 콜백 실행

    2. createDevice 실행

    3. createSendTransport 실행

    4. createSendTransport에서 createWebRtcTransport 콜백 실행

    5. connectSendTransport 실행

      producer = await producerTransport.produce(params)

      이 함수를 통해 미디어 스트림을 다른 유저에게 전송

    6. producerTransport의 connect 이벤트 실행

    7. producerTransport의 produce 이벤트 실행

    8. getProducers 실행

    9. getProducers 콜백 실행

      자신을 제외한 모든 유저들의 producer.id가 들어있는 배열을 파라미터로 받음.

      이 안에 forEach 로 signalNewConsumerTransport를 여러번 호출함.

      socket.emit('getProducers', producerIds => {
        console.log("getProducers 콜백 실행")
      
        console.log(producerIds)
        // for each of the producer create a consumer
        // producerIds.forEach(id => signalNewConsumerTransport(id))
        producerIds.forEach(signalNewConsumerTransport)
      })
    10. signalNewConsumerTransport 실행

    11. signalNewConsumerTransport에서 createWebRtcTransport 콜백 실행

    12. connectRecvTransport 실행 (device.connectRecvTransport(params) 아님!)

    13. consumerTransport의 connect 이벤트 실행

      이 이벤트 실행 타이밍이 특이하다.

      connectRecvTransport에서 이벤트를 등록하고 다음 함수에서 이 이벤트가 불렸을 때 작동하는 것으로 보인다.

    14. &&&&&connect&&&&& 얘는 connect 내부의 콘솔 로그

    15. 그리고 마지막에 paintpeerstream 실행

    • Algorithm 완전탐색(하)마무리

2. 기술적 고민

  1. 기술적 고민

    • SFU 구현하기
      • 2차 테스트)
        • 공부하던 코드에서 상대방 화면이 뜨지 않던 에러 fix
          // app.js
          
          const createWebRtcTransport = async (router) => {
            return new Promise(async (resolve, reject) => {
              try {
                // https://mediasoup.org/documentation/v3/mediasoup/api/#WebRtcTransportOptions
                const webRtcTransport_options = {
                  listenIps: [
                    {
                      **ip: '172.20.10.2', // replace with relevant IP address
                      // announcedIp: '10.0.0.115',**
                    }
                  ],
                  enableUdp: true,
                  enableTcp: true,
                  preferUdp: true,
                }
          • mediasoup 서버를 실행하는 컴퓨터의 IP주소로 변경해줘야 했음
          • listenIps 부분을 수정. 나중에 우리 aws IP 주소로 변경하면 될 듯
          • sfu가 서버를 통해 실행시키는게 핵심인데 거기에 해당되는 문제였던거였음
        • 접속 인원 수를 늘리려면
          const createWorker = async () => {
            worker = await mediasoup.createWorker({
              rtcMinPort: 2000,
              rtcMaxPort: 2020, // 4명까지 됨. 인원 증가시 이 부분을 수정
            }) // 2100 으로 하면 100개의 전송 수를 받을 수 있게 됨
        • 두 캐릭터가 만났을 때 뜨는 콘솔창 메시지 에러 해결
          // overworld.js 수정
          
          // import mediasoupClient from "mediasoup-client";
          const mediasoupClient = require('mediasoup-client')
      • 3차 테스트)
        • connect완료
        • user2에게 user1의 정보가 추가 되지 않음. (div class추가 안됨)
        • user3은 user1, 2의 비디오가 투명상태로 뜨고, user2는 여전히 아무것도X
        • stream정보가 전달되지 않음
        • consumer 관련 함수가 실행이 안되는 듯 하다
        • 우리 코드는 roomName, groupName 두 가지로 구분을 하고있는데, SFU에서는 roomName만 사용중이니 이 부분에서 꼬인 부분이 있을 것 같다.
        • 참고한 코드에서는 roomName을 url을 파싱해서 사용하고 있었고, 우리 코드는 groupObjArr를 따로 만들어서 거기에서 roomName을 가져오고 있었다.

    문제 해결 과정

    문제점

    두 명이서 만나면 한 쪽은 connect 후 getProducers()를 실행하고, 한 쪽은 아예 connect도 실행되지 않는다.

    user2가 이미 접속해있는 user1을 만났을 때 아무런 실행을 하지 않는 것이 문제.

    고민과정

    우선 워커는 서버를 실행시키자 마자 생성 된다.

    그림에 쓰여진 원인 2개와 추가로 생각해볼 것 몇개 더

    • user1이 입장하자마자 sendtransport가 잘 실행되는지
      아니면 user2의 receiver transport가 있어야 실행되는지 → 정상 작동 확인

    • 원인추측 1 : 라우터가 2개(?)다.
      - 꼭 2개여야 하는지? 그리고 2개여서 최초 user1과 user2간의 전달이 안되고 있는건지?
      - 우선 우리 코드에서 라우터는 createRoom 함수에서 딱 1개씩 만들어지고 있다.
      - 그러면 CreateRoom은 언제 호출되냐?
      - 프론트 socket.on("accept_join", 안의 socket.emit('getRtpCapabilities' 에서
      socket.on('getRtpCapabilities' 로 받고 실행된다.
      - 그러면 유저 accept_join시 마다 라우터가 생성되겠네? 콘솔로그로 확인해보자.
      → 그렇다. 유저 1명이 추가 입장 후 기존의 유저를 만날 때 최초 1회 라우터가 생성된다. 즉, 유저 명수 = 라우터 갯수
      이게 문제가 되려나?

                
    • 원인추측 2 : user2가 거쳐가는 라우터에서 Consumer를 못 보내는(?)건지
      - 서버에서 Producertransport.produce(params) 를 호출할 때, params의 파라미터 안에 든 video track을 track = getVideotrack 으로 가져오는데, 이 때 트랙이 제대로 추가가 안된 것은 아닐지?
      - 빠진 코드 발견

            // server informs the client of a new producer just joined
              socket.on('new-producer', ({ producerId }) => signalNewConsumerTransport(producerId))
  • 4차 테스트)
    - 화상채팅 인원 추가될 때마다 클래스 추가까지 작동 확인 → 내일은 rtcMaxPort 올려서 connection 제한 갯수 올린 후 테스트인원 늘려보기 stream 제대로 가져오는지 확인, getVideoTrack 다시 확인해보기

3. 성찰

  1. 성찰
    • 아키텍쳐를 그려보는 것이 굉장히 중요한 것 같다.

📖 02.26 토요일

1. 공부 진행 상황

  1. 공부 진행 상황
    • 회의 - 친구기능 보류, 그림 같이 보기 기능 → 방에 들어와있는 사람 중에 골라서 푸쉬 알람 보내고, 수락하면 그림 공유 가능하게 하기로 함.
    • 드디어 에러 해결!!!!!!!!!!! SFU 마무리함!
    • 성능 테스트하기
    • 앞으로 할 일 순서 disconnect ⇒ merge 전 mesh 성능 비교(서버 열어서 실제 유저들로) → merge
      → aws test ⇒ SFU 성능 비교
    • 우리 백엔드 아키텍쳐를 그려보자
      • 멀다 수정필요 → 클라이언트1 만 전송받는게 아니라, 같은 groupName 에게 전송

    • Algorithm 이진탐색(하)

2. 기술적 고민 / 성능테스트

  1. 기술적 고민
    • SFU 구현하기 (완료)

      - 해결과정

      • 첫번째로 입장한 user1의 콘솔창

- 두번째로 입장한 user2의 콘솔창

  • 문제점1
    유저 accept_join시 마다 라우터가 생성되나?
    그렇다. 유저 1명이 추가 입장 후 기존의 유저를 만날 때 최초 1회 라우터가 생성된다. 즉, 유저 명수 = 라우터 갯수 → 라우터는 최초 1회 생성 후, createRoom 함수가 호출 되고, 함수 내에 if else문에서 else문에 걸려 createRouter 호출 시에 추가로 생성된다.
    - createRoom
        ```jsx
        // app.js
        
        const createRoom = async (roomName, socketId) => {
            let router1
            let peers = []
            if (rooms[roomName]) { // let rooms = { roomName1: { Router, rooms: [ sicketId1, ... ] }, ...}
              router1 = rooms[roomName].router
              peers = rooms[roomName].peers || []
            } else {
              router1 = await worker.createRouter({ mediaCodecs, })
              console.log('~~~Router1 생성 완료다냥~~~');
            }
        ```
        
  • 문제점 2
    user1 : ProducerIds를 비디오 태그 정보로 추가하고 있음. stream 정보를 추가했어야 하는거 아닌가?
    user2 : getProducers를 안하고 있음.
    → getProducers 관련 코드를 보니 새로 들어온 사람이 기존에 들어와있는 사람 수를 세서 1명이 넘을 떄 실행을 해야하는데, 문제는 첫번째로 들어온 사람이 실행을 하고있다.
if (producersExist) getProducers() 
// producersExist -> producer.len>1 ? true : false
  • 해결
    • 해결 ⇒ transport-recv-connect 코드 추가
      처음부터 끝까지 코드를 쭉 보면서 SFU에 있어야 할 기능을 하나 하나 실행해보고 과정을 따라가보다보니, 빠진 기능이 있다는 걸 알게 돼서 추가로 구현해주었다.
// see client's socket.emit('transport-recv-connect', ...)
  socket.on('transport-recv-connect', async ({ dtlsParameters, serverConsumerTransportId }) => {
    console.log(`DTLS PARAMS: ${dtlsParameters}`)
    const consumerTransport = transports.find(transportData => (
      transportData.consumer && transportData.transport.id == serverConsumerTransportId
    )).transport
    await consumerTransport.connect({ dtlsParameters })
  })
  • 5차 테스트) 정상 작동!


    • 성능 비교 테스트

      • 성능 비교 방법

        1. 사이트 트래픽 확인할 수 있는 사이트
          https://m.blog.naver.com/wkosin/221794980782
        2. 크롬으로 트래픽 체크
          https://imweb.me/faq?mode=view&category=30&category2=43&idx=15603
        3. NHN webrtc 성능테스트 과정 (굿)
          https://rlqof7ogm.toastcdn.net/references/2021_session_02.pdf
      • 성능테스트 회의 내용

        [참고사항]

        1. 1기 네이버 선배님들이 조언해줬던 것처럼 사진/동영상으로 남기기

        2. 클라이언트 cpu 사용량, 서버 cpu 사용량, 트래픽 / 부하
          - 한 방에 몇 명까지?
          - 3개 방 ⇒ 10명 x 3개 방 = 30명 동시 접속, 동시 화상 통화 가능한지?
          - Mesh는 클라이언트니까 상관없음
          - SFU일때만 고려하기

          [성능 테스트]

        • 한 서버당 몇개의 그룹을 지원하는지? (EC2 기준)

        • 1그룹당 10명 기준. 몇 그룹까지 한 서버로 처리할 수 있는지?

        • 우리가 체크할 것

          • 동영상
          • 수치 → 뭘로?
        • cpu 사용량? : 인원이 n명일 때 cpu를 얼마나 사용하는지?
          - 트래픽
          - 서버는 부하. 비용적인 부분은 챌린징이나 선택이유 이야기 시, 업로드/다운로드 속도
          - 인원수 증가 → 트래픽 증가 → 서버 여러개 사용 → 비용 증가
          - 클라이언트는 업로드/다운로드 속도 부분, CPU사용률


        • 테스트 어떻게할지 정확하게 루틴(?) 정하고, 뭘로 테스트할지 정하자!

        • SFU+FE 머지해서 브랜치 따로 만들어놓고,

        • 테스트할 사람들 있을 때!

        • 현재 돌아가는 코드 브랜치명

          mesh방식 : msworks -

          sfu방식 : SFU

          • 시간 되면 서버 추가 챌린징
            • 서버3개 aws기준 서버 사람 아무도 없어야 돈을 안낸다

              그럼 한 서버에 가능한한 많은 사람을 넣어야..

              비용 최소화하려면 모든 서버끼리는 룸과 관계없이 서로 통신할 수 있게 되어야함

              왜냐면 그룹1, 2가 있을 때 여기 사람들이 순서대로 그룹 1에 1명, 그룹 2에 1명 있고 순서대로 들어올 때 서버에 집어넣으려면 무조건 한 서버에 들어온 순서대로 넣어야 뒤의 서버들을 안쓰게되니까 이런식으로 비용 최소화하려면 스케쥴링을 따로 해줘야한다

              서버끼리의 통신 필요. 같은 서버인 것 처럼 보이게 하는게 어려움

          [고려사항]

          • 트래픽/부하 : 각 클라이언트의 카메라 device 해상도가 영향이 있을지?
          • 한 그룹이 잘 된다는 기준은 어떻게? 화상이 연결되기만 하면? 혹은 렉이 얼마나 걸리는지 기준을 정할건지? 부하테스트 기준?

          Test 1 - 그룹영상통화 : 한 그룹내 최대 몇 명

          방법 (각각 SFU, Mesh)

          • 영상통화 한 그룹당 최대 가능 사용자 수를 1부터 10까지 늘려가면서 성능 테스트
            • CPU 사용량 (내부 모니터링 프로그램)
            • 영상 받아오는 시간 (실시간으로 받아오는데 어떻게 테스트하지) - 평가 안될듯
          • 각각 서버 CPU 1개, 클라이언트 1명 각 CPU 부하 테스트(아니면 1→2→,,,→10명 각 평균 구하기?)

          그래프로 그려서 1→ 10명, y축 평균 CPU사용량

          Test 2 - 서버 당 가능한 영상통화 총 그룹 수

          방법 (각각 SFU, Mesh)

          • 한 서버로 3, 4명 단위로 영상통화 그룹 수를 늘려간다
          • 서버 CPU의 사용량이 90%기준이 되는 최대 그룹 수를 체크한다.

          예상

          • Mesh : 서버의 CPU사용량이 다소 적다, 클라이언트는 4명이면 괜춘, 10명이면 부담
            • 화상통화단위가 4명 이상이면 SFU, 4명 미만이라면 최대한 Mesh 방식 적합
              • 단점 : 전환 과정에서 영상 끊김이 있다. 테스트해본다.
          • SFU : 서버의 CPU사용량이 크다. 그룹통화 사용자 수를 늘릴 수 있다. 한 서버에서 처리하는 이용자 수가 많아질수록 속도 저하가 예상된다.
            • 서버 CPU사용량 기준으로 서버 분할 방식 구현. (서버간 통신)

          Test 사용 프로그램

          • PM2.io

          • 맥 터미널 활성상태보기

          • 사용자 수 10만 테스트 (auxillery)

          • 예시 - 아무거나 붙인거임

            1. Mac CPU 부하 테스트terminal -> yes 입력 후 Enter -> terminal 창에 y가 무한 출력(출력이 보기 싫다면 yes >/dev/null)terminal을 여러개 켜서 실행하면 금방 CPU팬 소리가 남.
            2. Mac RAM 부하 테스트memtest 패키지 설치 후 진행.
            3. Mac 하드디스크 부하 테스트Blackmagic Disk Speed Test 앱을 설치 후 진행.

          제한 사항

          • 최대한 맥북 기준
        • 참고 이미지

3. 성찰

  1. 성찰
    • 끝까지 잡고 해볼 수 있는 방법을 전부 시도해보니 결국 해결 되는구나! ㅎㅎㅎ 뿌듯하다

📖 02.27 일요일

1. 공부 진행 상황

  1. 공부 진행 상황
    • Mesh→SFU 전환은 보류
    • 우선 aws배포가 잘 안돼서, 이것부터 해결하기. 하루종일 걸리는 중.
    • Algorithm 이분탐색(하), (중)

2. 기술적 고민

  1. 기술적 고민

    • Mesh→SFU 전환하기 1
      • 4번째 사람이 방에 입장했을 때, 모든 사람의 Mesh연결을 끊고 SFU로 전환하기
        • 기존 mesh connect를 끊고 sfu방식을 불러오는 과정을 코드로 구현해봐야함.
        • 첫번째 시도
          1. 서버 / joinGroup안에서 accept_join을 부르며 유저의 입장이 시작되었었는데, 이 기존에 있던 코드 내용 중 10명이 되면 더 이상 참가를 못하게 막는 maximum제한 코드가 있는 것을 발견.
            1. 이 코드에 있던 socket.emit("reject_join") 의 호출을 받고 있는 함수가 없음을 발견했고, 이 부분에서 max인원이 되었을 때 입장을 거절하는 게 아니라 SFU를 호출하는 방법으로 바꾸면 어떨까? 하는 아이디어.
            2. maximum을 10→4로 줄이고, “reject_join”대신 “change_SFU”를 쓰고 프론트쪽에 있던 “accept_join”쪽 함수를 수정해보았다.
            • 코드
              // overworld.js
              
              socket.on("accept_join", async (groupName) => {
                  await initCall();
                  switch (connect) {
                    case "mesh":
                      const length = userObjArr.length;
                      if (length === 1) {
                        return;
                      }
              
                      for (let i = 0; i < length - 1; ++i) {
                        const newPC = await createConnection(
                          userObjArr[i].socketId,
                          userObjArr[i].nickname
                        );
                        const offer = await newPC.createOffer();
                        await newPC.setLocalDescription(offer);
                        socket.emit(
                          "offer",
                          offer,
                          userObjArr[i].socketId,
                          userObjArr[i].nickname
                        );
                      }
                    case "SFU":
                      socket.on("change_SFU", function(data) {
                      socket.emit('getRtpCapabilities', groupName, (data) => {
                      console.log(`Router RTP Capabilities... ${data.rtpCapabilities}`)
                      // we assign to local variable and will be used when
                      // loading the client Device (see createDevice above)
                      rtpCapabilities = data.rtpCapabilities
                  
                      // once we have rtpCapabilities from the Router, create Device
                      createDevice()
                      })
                    })
                  }
                });
          2. switch 문을 사용해 mesh/SFU가 호출되는 케이스를 나눴는데, 나누고보니 이러면 기존에 입장해있던 1~3번째의 유저들의 connection을 끊고 SFU방식으로 다시 connect하게 하는 코드는 누락되어있다는 것을 알게 되었다.
        • 두번째 시도
          1. 기존 “accept_join” 코드를 유지한 채, socket.on("change_SFU") 함수를 따로 만들어서 이 안에 1~3번째 유저의 connection 변경사항을 조건식으로 포함해 구현해보기로 했다.
            • 코드
              // overworld.js
              
              socket.on("accept_join", async (userObjArr) => {
                  try {
                    // Mesh코드~  
                    const length = userObjArr.length;
                    if (length === 1) {
                      return;
                    }
              
                    for (let i = 0; i < length - 1; ++i) {
                      const newPC = await createConnection(
                        userObjArr[i].socketId,
                        userObjArr[i].nickname
                      );
                      const offer = await newPC.createOffer();
                      await newPC.setLocalDescription(offer);
                      socket.emit(
                        "offer",
                        offer,
                        userObjArr[i].socketId,
                        userObjArr[i].nickname
                      );
                    }
                  } catch (err) {
                    console.error(err);
                  }
                });
                
                socket.on("change_SFU", async (userObjArr, groupName) => {
                  try {
                    // SFU
                    await initCall();
              
                    // connection 변경
                    if (userObjArr.length >= 4) {
                      console.log("4명 이상입니다.")
                      socket.emit("disconnect")
                      console.log("disconnect. 현재 명수: ", userObjArr.length)
                      // socket.emit("change_SFU", groupName)
                    }
              
                    console.log("SFU connect를 시작합니다.")
                    socket.emit('getRtpCapabilities', groupName, (data) => {
                      console.log(`Router RTP Capabilities... ${data.rtpCapabilities}`)
                      // we assign to local variable and will be used when
                      // loading the client Device (see createDevice above)
                      rtpCapabilities = data.rtpCapabilities
              
                      // once we have rtpCapabilities from the Router, create Device
                      createDevice()
                    });
                    
                  } catch (err) {
                    console.error(err);
                  }
                });
            • user가 4명 이상일 떄 disconnect하는 조건을 걸면 안된다.. 그러면 한 명이 disconnect되는 순간 5번째 사람은 다시 1~3명 안에 들어오게 되니 말짱 도루묵이다.
          2. userObjArr.length가 아니라, 같은 groupName 안에 속한 인원 수를 해야 한다
            • 코드
              socket.on("accept_join", async (userObjArr) => {
                  try {
              
                    if (userObjArr.length < 4) {
                    // Mesh코드~  
                      const length = userObjArr.length;
                      if (length === 1) {
                        return;
                      }
              
                      for (let i = 0; i < length - 1; ++i) {
                        const newPC = await createConnection(
                          userObjArr[i].socketId,
                          userObjArr[i].nickname
                        );
                        const offer = await newPC.createOffer();
                        await newPC.setLocalDescription(offer);
                        socket.emit(
                          "offer",
                          offer,
                          userObjArr[i].socketId,
                          userObjArr[i].nickname
                        );
                      }
                    } else {
                      socket.emit("change_SFU". groupName)
                    }
                  } catch (err) {
                    console.error(err);
                  }
                });
                
                socket.on("change_SFU", async (userObjArr, groupName) => {
                  try {
                    // SFU
                    await initCall();
              
                    // connection 변경
                    if (userObjArr.length >= 4) {
                      console.log("4명 이상입니다.")
                      socket.to(userObjArr.socketId).emit("disconnect")
                      console.log("disconnect. 현재 명수: ", userObjArr.length)
                      // socket.emit("change_SFU", groupName)
                    }
              
                    console.log("SFU connect를 시작합니다.")
                    socket.emit('getRtpCapabilities', groupName, (data) => {
                      console.log(`Router RTP Capabilities... ${data.rtpCapabilities}`)
                      // we assign to local variable and will be used when
                      // loading the client Device (see createDevice above)
                      rtpCapabilities = data.rtpCapabilities
              
                      // once we have rtpCapabilities from the Router, create Device
                      createDevice()
                    });
                    
                  } catch (err) {
                    console.error(err);
                  }
                });
            • FE 터미널 메시지
              host, guest의 그룹 번호가 같습니다.
              이것이 caller의 닉네임이다 coco
              1 1
              host, guest의 그룹 번호가 같습니다.
              이것이 caller의 닉네임이다 coco
              1 1
              host, guest의 그룹 번호가 같습니다.
              이것이 caller의 닉네임이다 coco
              1 1
              host, guest의 그룹 번호가 같습니다.
              이것이 caller의 닉네임이다 coco
              1 1
              host, guest의 그룹 번호가 같습니다.
              이것이 caller의 닉네임이다 coco
              1 1
              • 흠.. 그룹을 나가게 하는 것도 해야하나?
          3. 수정
            • 코드
              socket.on("accept_join", async (userObjArr) => {
                  try {
              
                    if (userObjArr.length < 4) {
                    // Mesh코드~  
                      const length = userObjArr.length;
                      if (length === 1) {
                        return;
                      }
              
                      for (let i = 0; i < length - 1; ++i) {
                        const newPC = await createConnection(
                          userObjArr[i].socketId,
                          userObjArr[i].nickname
                        );
                        const offer = await newPC.createOffer();
                        await newPC.setLocalDescription(offer);
                        socket.emit(
                          "offer",
                          offer,
                          userObjArr[i].socketId,
                          userObjArr[i].nickname
                        );
                      }
                    } else {
                      socket.emit("change_SFU". groupName)
                    }
                  } catch (err) {
                    console.error(err);
                  }
                });
                
                socket.on("change_SFU", async (userObjArr, groupName) => {
                  try {
                    // SFU
                    await initCall();
              
                    // connection 변경
                    if (userObjArr.length >= 4) {
                      console.log("4명 이상입니다.")
                      socket.to(userObjArr.socketId).emit("disconnect")
                      console.log("disconnect. 현재 명수: ", userObjArr.length)
                      // socket.emit("change_SFU", groupName)
                    }
              
                    console.log("SFU connect를 시작합니다.")
                    socket.emit('getRtpCapabilities', groupName, (data) => {
                      console.log(`Router RTP Capabilities... ${data.rtpCapabilities}`)
                      // we assign to local variable and will be used when
                      // loading the client Device (see createDevice above)
                      rtpCapabilities = data.rtpCapabilities
              
                      // once we have rtpCapabilities from the Router, create Device
                      createDevice()
                    });
                    
                  } catch (err) {
                    console.error(err);
                  }
                });
        • 의문점, 중간 회의내용 → disconnect 는 내장되어있는 기능(?)인 것 같다.
          • ICE connect를 끊어주는 방법이 괜찮을 것 같다
          • 혹은 leave 사용하거나, 태그를 삭제하는 방법도 있다
        • 회의 결과, disconnect 과정에서 다른 기능과 관련해서 pcObjarr쪽도 삭제해줘야할 것이 있다는 것을 알게 되었다.

    • AWS배포
      • AWS는 HTTPS ~ HTTP 연결 안됨 (socket부분)
        헤로쿠는 socket 문제가 없지만, mediasoup이 안깔림
        => 일단 헤로쿠에 mediasoup을 깔아보는걸로 얘기됨
      • 헤로쿠 홈페이지 - 터미널처럼 헤로쿠 CLI있는데 거기에 있는 다큐멘테이션 읽음 파이썬 버전문제? 헤로쿠 파이썬 버전을 어떻게 바꿀지? 헤로쿠가 결국 mediasoup 설치가 안됨 (미디어숲설치시 필요한게 gcc, python)
      • aws에 우리 SFU코드로 배포시 listenIps에서 문제 발생 public Ip를 넣으면 커넥션조차 안되고, 프라이빗 Ip를 넣으면 커넥션은 되는데 스트림이 안뜬다. 가능한 UDP TCP 포트 다 열어봄. rtcMinPort rtcMaxPort 크기만큼 포트 열어주라는 글들이 몇개 있어서 그대로 해봤는데 잘 안됨

3. 성찰

  1. 성찰
    • 여러 코드 작성과 그에 따른 공부를 진행하면서 이전에 작성한 코드에 대해서 자세히 기억이 안난다는 것을 알았다. 내가 쓴 코드를, 우리 프로젝트의 코드는 무조건 기억해야 한다. 특정 기능에 대한 코드 내용이 기억이 안난다면 자세히 보고 가자.
    • 검색은 구글링→스택오버플로우 순으로 해보자!
profile
블로그 이전했습니다. https://yerimi11.tistory.com/
post-custom-banner

0개의 댓글