본문 바로가기
Develop Study/Architecture 2025. 8. 12.

(25.08.11) WebRTC기반의 P2P 통화 연결

최근 취업을 하게 되어,

회사의 솔루션 및 서비스를 분석하고자 한다.

 

항상 Backend 단에서의 API 및 WebSocket 기반의 내 메시지의 교환을 구성했지만, 이와 다르게

 

SDK 를 기준으로 시그널링만의 역할만 서버가 담당하고, 스트리밍을 UDP로 미디어를 스트리밍 = 영상통화를 할 수 있도록 하는 서비스를 샘플 코드를 기반으로 분석하고자 했다.


https://docs.knowledgetalk.co.kr/sample/p2p

 

P2P통화 연결 | Knowledgepoint

SAMPLEP2P통화 연결 설명 중앙 미디어 서버없이 종단 간 직접 연결하여, 연결을 하고 싶은 사용자에게 발신자의 영상을 보낼 수 있습니다. 단, NAT/방화벽 환경의 사용자가 외부망과의 통신을 위해

docs.knowledgetalk.co.kr

WebRTC, Web Real-Time Communication

  • 웹 브라우저와 모바일 앱에서 플러그인 없이 실시간으로 오디오, 비디오, 데이터 통신을 할 수 있게 해주는 기술 표준
    • 웹 애플리케이션과 웹사이트가 중간 서버 없이 브라우저 간에 실시간 통신
  • 표준화된 API 를 통해 브라우저-브라우저 간 직접적인 미디어 스트림 및 데이터 전송
  • 기본적으로 실시간 스트림을 위한 UDP 프로토콜을 주로 사용(TCP 사용가능)
    • RTP/RTCP(Real-time Transport Protocol) 같은 프로토콜을 사용해 미디어 스트림 전송을 추가로 관리하면서 데이터 손실을 방어
    • STUN/TURN 서버를 통해 NAT 환경을 피해 UDP 패킷을 주고 받음

특징

  • 미디어 캡처 및 전송: 웹캠, 마이크 등 미디어를 상대방에게 전송
  • 실시간 스트리밍: 지연시간이 매우 짧은 실시간 영상/음성 교환 가능
  • → P2P, SFU 방식의 연결의 기반
  • 데이터 채널: 영상, 음성뿐 아니라 텍스트, 파일 등 데이터 전송도 가능

P2P , Peer-to-Peer

  • 중앙 서버를 거치지 않고, 사용자(피어)끼리 두 명 이상이 실시간으로 직접 연결해 데이터를 주고받는 방식
  • 단, 무조건 중앙서버를 거치는 것이 아니라, 신호 교환(Signaling)시, 중간 서버가 이를 중계
    • NAT 방화벽 환경에 의한 통신 제한 또는 공인 IP 공유가 어려울 경우

P2P의 장단점

  • 장점
    • 서버 비용 절감 (서버가 미디어 데이터 전달 안 함)
    • 지연 시간 최소화
    • 분산 구조로 특정 서버 장애 영향 적음
  • 단점
    • 많은 참여자일 경우 연결 관리와 네트워크 부하 증가
    • NAT, 방화벽 문제로 연결 실패 가능성
    • 클라이언트 성능에 의존 (연결 유지에 리소스 필요)
    • → 따라서 1:1 영상통화 또는 파일 공유에 필요함

Knowledgetalk SDK의 P2P 통화 분석

Knowledgetalk 서버 API 통신 서버 중계를 통한 P2P 연결 준비

.... // Knowledgetalk SDK 객체에 식별코드, 인증키를 통한 연결 knowledgetalk.init(cpCode, authKey).then(result => { // 서버 연결 성공시 서버에서 200 코드 Response // 그렇지 않을 경우 alert 처리 if(result.code !== '200') { alert('init failed!'); return; } ... })
  • Knowledgetalk 서버에서 P2P 통화를 사용하기 위해서 부여된 식별코드, 인증키로 인증을 API 를 통해서 진행
    • init()
      • 일반적이 REST API 가 아닌 SDK 제공의 API 이기 때문에 Java 매서드처럼 Params를 입력해서 요청하면서 API 의 비동기 처리ㅣ를 하게됨
      • Javascript 기준 **Promise**를 반환하고, .then() 또는 **await**로 결과를 반환 → knowledgetalk.init(cpCode, authKey).then(...) 의 형태
    • **result.code === '200'**에서 받는 **200**은 HTTP 상태 코드(HTTP status code)가 아니라, 서버가 API 응답 본문(JSON 데이터) 안에 포함시켜 보내는 응답 메시지 내 필드 값
  • API 이기 때문에, 이는 WebRTC 영역이 아님 주의

추가 SDK API처리

				// 방 생성 EventListener
        createRoomBtn.addEventListener('click', async () => {
            // 방 생성 요청
            let roomData = await knowledgetalk.createRoom();

            // 방생성 오류 처리
            if(roomData.code !== '200'){
                alert('createRoom failed!');
                return;
            }

            ...
        })

        // 방 입장 EventListener
        joinRoomBtn.addEventListener('click', async () => {
            let roomId = roomIdInput.value;

            // 방 아이디를 통해 방 입장 요청 -> 성공시 roomData Response
            let roomData = await knowledgetalk.joinRoom(roomId);

            // 입장 요청 오류 처리
            if(roomData.code !== '200'){
                alert('joinRoom failed!');
                return;
            }

            ...
        })
  • SDK 를 통한 서버에서 방 연결을 위한 로직 처리가 진행
  • createRoom() joinRoom() 을 통해서 각각 방생성/생성된 방 정보, 특정방상태/방멤버 를 Response 받아오는 호출

P2P 영상통화 연결

p2pBtn.addEventListener('click', async () => {
            // 전송할 상대방 id 선언
            let targetId = prompt('상대방 id 입력');

            // localStream 객체를 생성
            // navigator를 통해 허용된 사용자의 입력장치(비디오, 오디오) 를 함께 활용하기 위해 가져오기
            let localStream = await navigator.mediaDevices.getUserMedia({video: true, audio: false});

            // 요청한 사람 = 스트림을 시작한 사람의 VideoBox 생성 함수실행
            createVideoBox(knowledgetalk.getUserId());

            // 카메라 영상 스트림을 화면에 출력하기 위해 연결 (srcObject = localStream)
            document.getElementById('multiVideo-' + knowledgetalk.getUserId()).srcObject = localStream;

            // 다른 targetId 인 상대방 user 과 webRTC 연결설정과 publish
            await knowledgetalk.publishP2P(targetId, 'cam', localStream);
        })
  • navigator.mediaDevices.getUserMedia({video: true, audio: false})
    • 사용자의 카메라(비디오)와 마이크(오디오) 권한을 요청해서, 실제로 미디어 스트림을 받아오는 Web API
    • WebRTC 통신에서 미디어 스트림(영상, 음성)을 주고받으려면 먼저 로컬 스트림을 확보해야하므로
      • localStream 객체는 WebRTC가 상대방과 주고받을 영상 데이터의 원본 소스
  • knowledgetalk.publishP2P(targetId, 'cam', localStream)
    • WebRTC P2P 연결을 실제로 생성
    • 내부적으로는 WebRTC의 PeerConnection 객체 생성(브라우저) → 신호 교환(signaling) 과정을 통한 연결 설정
      • signaling은 Peer간  ‘누가 있다’, ‘어떤 미디어를 주고받을지’, ‘네트워크 정보(ICE candidate)’ 등의 정보를 주고 받는 WebSocket형태를 띄고 있음 ex) offer, answer 등등
      • 이후에 미디어 스트리밍이 WebRTC, UDP 기반의 P2P 통신이 진행이 되는 것 → 브라우저간 직접

전체 Knowledgetalk SDK를 활용한 시그널링(Signaling) FLOW

(출처 :  https://docs.knowledgetalk.co.kr/sample/p2p  )

 

User Flow

HOST

  1. "CreateRoom" 버튼 누르기: 먼저, 방을 만들기 위해 CreateRoom 버튼을 누릅니다.
  2. roomId 확인: CreateRoom 버튼을 누르면, "room id" 입력란에 방 ID가 자동으로 생성
  3. "JoinRoom" 버튼 누르기: 생성된 방에 입장하기 위해 JoinRoom 버튼을 클릭
  4. 멤버에게 방 ID 공유: 방 ID를 친구에게 알려주기
  5. 4-1. 멤버가 해당 roomId로 JoinRoom 클릭해 방에 들어오기
  6. "p2p" 버튼 누르기: 친구가 방에 들어오면, p2p 버튼 클릭
  7. 멤버의 ID 입력: "상대방 id 입력" 프롬프트 창이 나타나면, 멤버의 ID를 입력하고 확인

MEMBER

  1. "room id" 입력: 방을 만든 친구에게서 받은 방 ID를 "room id" 입력란에 직접 입력
  2. "JoinRoom" 버튼 누르기: 입력한 방 ID로 방에 입장하기 위해 JoinRoom 버튼 클릭
  3. "p2p" 버튼 누르기: 친구의 영상이 보이면, p2p 버튼 클릭
  4. 호스트의 ID 입력: "상대방 id 입력" 프롬프트 창이 나타나면, 호스트의 ID를 입력하고 p2p 클릭

STUN/TURN을 고려하기

  • STUN 성공 시 → 브라우저끼리 직접 연결 (UDP)
  • STUN 실패 시 → TURN 서버 중계로 연결

STUN (Session Traversal Utilities for NAT)

  • 클라이언트가 자신의 공인 IP:포트 = 주소만 확인할 수 있도록 하는 역할
  • NAT 환경에서 P2P 직접 연결을 시도할 때 필수
    • NAT 뒤에 클라이언트의 주소를 확인할 수도 있음
    • 방화벽이 있을 경우 실패할 수 있음
  • 위의 시그널링에 사용하는 중계 서버 외에 P2P로 미디어를 흘려야할 때 또 서로의 IP 주소를 알아야하기 때문에 STUN 을 사용할 수 밖에 없음

TURN (Traversal Using Relays around NAT)

  • 직접 P2P 연결이 불가능할 때 미디어(음성/영상)를 중계(relay)하는 역할
  • 클라이언트는 TURN 서버에 미디어 스트림을 보내고, TURN 서버가 그 미디어를 상대 피어에게 전달(중계)하는 방식
  • UDP 외 TCP/TLS도 지원하기 때문에 UDP 없는 환경에서도 가능
  • 외부 서버이기 때문에 비용이 발생

STUN/TURN 이 반드시 필요하진 않지만, 브라우저 (클라이언트 단) 에서 확인할 수 없기 때문에, 후에

실제 SSL 인증서를 설치하고,  PeerConnection 에서 STUN 또는 TURN 을 경유하는지 보고, 다이어그램으로 한번더 정리해보고자 한다.