이미지 로딩 중...
AI Generated
2025. 11. 20. · 2 Views
WebRTC RTCPeerConnection 생성과 관리 완벽 가이드
WebRTC의 핵심인 RTCPeerConnection을 생성하고 관리하는 방법을 배웁니다. ICE 서버 설정부터 미디어 트랙 추가, 이벤트 관리, 연결 상태 모니터링까지 실무에서 바로 사용할 수 있는 완전한 가이드입니다.
목차
1. RTCPeerConnection_생성
시작하며
여러분이 화상 회의 앱이나 실시간 스트리밍 서비스를 만들 때 이런 상황을 겪어본 적 있나요? "어떻게 하면 브라우저끼리 직접 연결해서 영상과 음성을 주고받을 수 있을까?" 서버를 거치면 비용도 많이 들고 지연도 생기는데 말이죠.
이런 문제는 실제 개발 현장에서 자주 발생합니다. 특히 실시간 커뮤니케이션이 필요한 서비스에서는 서버 부하와 네트워크 지연이 큰 걸림돌이 됩니다.
서버를 통해 모든 미디어 데이터를 중계하면 비용도 엄청나게 증가하죠. 바로 이럴 때 필요한 것이 RTCPeerConnection입니다.
이것은 브라우저끼리 직접 연결하여 영상, 음성, 데이터를 실시간으로 주고받을 수 있게 해주는 WebRTC의 핵심 객체입니다.
개요
간단히 말해서, RTCPeerConnection은 두 브라우저 사이에 직접적인 통신 채널을 만들어주는 JavaScript 객체입니다. 이 객체가 필요한 이유는 실시간 통신의 효율성 때문입니다.
예를 들어, 화상 통화를 하는 경우 서버를 거치지 않고 직접 연결하면 지연이 크게 줄어들고, 서버 비용도 절감할 수 있습니다. 특히 고화질 영상이나 게임처럼 빠른 반응이 필요한 경우에 매우 유용합니다.
기존에는 플러그인이나 별도의 소프트웨어를 설치해야 했다면, 이제는 브라우저만 있으면 바로 P2P 통신을 시작할 수 있습니다. RTCPeerConnection의 핵심 특징은 첫째, NAT 및 방화벽 통과 기능, 둘째, 미디어 스트림 자동 암호화, 셋째, 네트워크 상태 자동 적응입니다.
이러한 특징들이 개발자가 복잡한 네트워크 로직을 직접 구현하지 않아도 안정적인 P2P 연결을 만들 수 있게 해줍니다.
코드 예제
// RTCPeerConnection 기본 설정 객체 생성
const configuration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }
]
};
// RTCPeerConnection 인스턴스 생성
const peerConnection = new RTCPeerConnection(configuration);
// 연결 상태 확인
console.log('PeerConnection 생성 완료:', peerConnection.connectionState);
// 시그널링 상태 확인
console.log('시그널링 상태:', peerConnection.signalingState);
설명
이것이 하는 일: RTCPeerConnection 객체를 생성하면 두 브라우저 사이에 P2P 연결을 맺을 준비가 완료됩니다. 마치 전화기를 집어들고 상대방 번호를 누르기 전 상태라고 생각하면 됩니다.
첫 번째로, configuration 객체를 만드는 부분은 연결 설정을 담당합니다. 여기서 iceServers는 NAT와 방화벽 뒤에 있는 브라우저들이 서로를 찾을 수 있도록 도와주는 STUN 서버 주소를 지정합니다.
이렇게 하는 이유는 대부분의 사용자가 공유기나 방화벽 뒤에 있어서 직접 IP 주소를 알 수 없기 때문입니다. 그 다음으로, new RTCPeerConnection(configuration)이 실행되면서 실제 연결 객체가 생성됩니다.
내부에서는 ICE 에이전트가 초기화되고, 미디어 스트림을 처리할 준비를 하며, 보안 연결을 위한 DTLS 설정이 준비됩니다. 마지막으로, connectionState와 signalingState를 확인하여 연결 객체가 제대로 생성되었는지 확인합니다.
connectionState는 실제 미디어 데이터 연결 상태를, signalingState는 협상 진행 상태를 나타냅니다. 여러분이 이 코드를 사용하면 웹 브라우저에서 바로 P2P 연결을 시작할 수 있습니다.
플러그인이나 추가 설치 없이 JavaScript만으로 화상 통화, 파일 공유, 실시간 게임 같은 기능을 구현할 수 있게 됩니다.
실전 팁
💡 RTCPeerConnection은 사용이 끝나면 반드시 close() 메서드로 정리해야 합니다. 그렇지 않으면 메모리 누수와 불필요한 네트워크 연결이 유지됩니다.
💡 configuration 객체는 선택사항이지만, iceServers를 설정하지 않으면 같은 로컬 네트워크에서만 연결이 가능합니다. 실제 서비스에서는 반드시 STUN/TURN 서버를 설정하세요.
💡 하나의 페이지에서 여러 개의 RTCPeerConnection을 생성할 수 있습니다. 다자간 화상 회의에서는 참가자마다 별도의 연결 객체를 만들어 관리합니다.
💡 브라우저 호환성을 위해 adapter.js 라이브러리를 사용하는 것을 권장합니다. 이 라이브러리가 브라우저별 차이를 자동으로 처리해줍니다.
💡 개발 중에는 chrome://webrtc-internals (Chrome) 또는 about:webrtc (Firefox)에서 연결 상태를 실시간으로 모니터링할 수 있습니다.
2. ICE_서버_설정
시작하며
여러분이 P2P 연결을 만들었는데 같은 네트워크에서는 잘 되다가 다른 네트워크에 있는 친구와 연결하려니 안 되는 경험 있으신가요? "왜 우리집 컴퓨터와 회사 컴퓨터는 연결이 안 될까?" 하는 의문이 생깁니다.
이런 문제는 NAT(Network Address Translation)와 방화벽 때문에 발생합니다. 대부분의 기기들은 공유기 뒤에 숨어있어서 외부에서 직접 접근할 수 없는 사설 IP 주소를 사용합니다.
마치 아파트 안의 각 호수처럼, 외부에서는 아파트 주소만 보이고 몇 호인지는 알 수 없는 것과 같습니다. 바로 이럴 때 필요한 것이 ICE 서버 설정입니다.
STUN과 TURN 서버를 통해 NAT 뒤의 기기들이 서로를 찾고 연결할 수 있도록 도와줍니다.
개요
간단히 말해서, ICE(Interactive Connectivity Establishment) 서버는 NAT와 방화벽을 통과해서 P2P 연결을 성공시키기 위한 중개 서버입니다. 이 설정이 필요한 이유는 인터넷 환경의 현실적인 제약 때문입니다.
예를 들어, 카페에서 노트북으로 집에 있는 데스크톱과 화상 통화를 하려면, 두 기기 모두 공유기 뒤에 있어 서로의 실제 위치를 알 수 없습니다. ICE 서버가 이 문제를 해결해줍니다.
기존에는 복잡한 네트워크 설정과 포트 포워딩이 필요했다면, 이제는 ICE 서버 주소만 설정하면 자동으로 최적의 연결 경로를 찾아줍니다. ICE의 핵심 구성요소는 첫째, STUN 서버(공인 IP 주소 확인), 둘째, TURN 서버(직접 연결 실패 시 중계), 셋째, ICE 후보 수집 및 선택 알고리즘입니다.
이러한 요소들이 함께 작동하여 95% 이상의 연결 성공률을 보장합니다.
코드 예제
// 실전에서 사용하는 완전한 ICE 서버 설정
const configuration = {
iceServers: [
// Google의 무료 STUN 서버
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
// TURN 서버 설정 (인증 포함)
{
urls: 'turn:turn.example.com:3478',
username: 'user123',
credential: 'pass456'
}
],
// ICE 후보 수집 정책
iceTransportPolicy: 'all', // 'all' 또는 'relay'
// ICE 후보 풀 크기 (Chrome)
iceCandidatePoolSize: 10
};
const peerConnection = new RTCPeerConnection(configuration);
설명
이것이 하는 일: ICE 서버 설정은 다양한 네트워크 환경에서도 안정적인 P2P 연결을 보장하는 핵심 설정입니다. 마치 여러 개의 다리를 준비해서 어떤 길이 막혀도 목적지에 도달할 수 있게 하는 것과 같습니다.
첫 번째로, STUN 서버 설정 부분은 자신의 공인 IP 주소와 포트를 알아내는 역할을 합니다. 대부분의 경우 이것만으로도 연결이 성공합니다.
Google이 제공하는 무료 STUN 서버를 여러 개 설정하면 하나가 응답하지 않아도 다른 서버로 시도할 수 있어 안정성이 높아집니다. 그 다음으로, TURN 서버 설정이 실행됩니다.
이것은 "최후의 보루"로, 방화벽이 너무 엄격해서 직접 연결이 불가능할 때 중계 역할을 합니다. username과 credential은 보안을 위한 인증 정보로, TURN 서버는 트래픽 비용이 발생하므로 인증된 사용자만 접근할 수 있도록 합니다.
추가 옵션들을 살펴보면, iceTransportPolicy는 연결 방식을 제어합니다. 'all'은 직접 연결과 중계를 모두 시도하고, 'relay'는 무조건 TURN 서버를 통해서만 연결합니다.
iceCandidatePoolSize는 미리 수집할 ICE 후보의 수를 지정하여 연결 속도를 높입니다. 여러분이 이 설정을 사용하면 기업 방화벽, 대학 네트워크, 모바일 네트워크 등 어떤 환경에서도 연결할 수 있습니다.
실제 통계에 따르면 STUN만으로 86%, TURN까지 활용하면 99% 이상의 연결 성공률을 달성할 수 있습니다.
실전 팁
💡 무료 STUN 서버는 Google, Mozilla 등에서 제공하지만 SLA가 없으므로 프로덕션에서는 자체 STUN 서버를 운영하거나 유료 서비스를 사용하세요.
💡 TURN 서버는 대역폭 비용이 많이 들므로 반드시 인증을 설정하고, 시간 제한이 있는 임시 credential을 발급하는 것이 좋습니다.
💡 iceTransportPolicy를 'relay'로 설정하면 개인정보 보호는 향상되지만 연결 품질이 떨어지고 비용이 증가합니다. 일반적으로 'all'을 사용하세요.
💡 iceCandidatePoolSize를 너무 크게 설정하면 초기 로딩이 느려지고, 너무 작으면 연결 속도가 느려집니다. 5-10 사이가 적당합니다.
💡 coturn이나 Janus 같은 오픈소스를 사용하면 자체 TURN 서버를 쉽게 구축할 수 있습니다. AWS나 GCP에 설치하면 지역별 최적화도 가능합니다.
3. 미디어_트랙_추가
시작하며
여러분이 P2P 연결을 만들었는데 "이제 이 연결로 어떻게 카메라 영상을 보내지?" 하는 순간이 옵니다. 연결만 만들어 놓고 실제로 전송할 미디어를 추가하지 않으면 아무 일도 일어나지 않죠.
이런 상황은 전화선은 연결했지만 말을 하지 않는 것과 같습니다. RTCPeerConnection은 데이터를 주고받을 통로일 뿐, 실제로 무엇을 보낼지는 개발자가 지정해야 합니다.
카메라, 마이크, 화면 공유 등 다양한 미디어 소스를 선택할 수 있습니다. 바로 이럴 때 필요한 것이 미디어 트랙 추가입니다.
getUserMedia로 얻은 스트림의 트랙들을 RTCPeerConnection에 추가하면 상대방에게 전송됩니다.
개요
간단히 말해서, 미디어 트랙은 실제로 전송할 오디오나 비디오 데이터의 단위이며, addTrack 메서드로 연결에 추가합니다. 트랙 추가가 필요한 이유는 WebRTC가 세밀한 제어를 가능하게 하기 위함입니다.
예를 들어, 화상 회의에서 카메라는 끄고 마이크만 켜거나, 화면 공유를 추가하거나, 여러 개의 카메라를 동시에 전송하는 것이 모두 가능합니다. 각 트랙을 독립적으로 관리할 수 있기 때문이죠.
기존에는 스트림 전체를 한꺼번에 추가했다면, 최신 WebRTC는 트랙 단위로 추가하여 더 유연한 제어가 가능합니다. 미디어 트랙의 핵심 특징은 첫째, 각 트랙의 독립적인 on/off 제어, 둘째, 동적 추가/제거 가능, 셋째, 트랙별 인코딩 파라미터 설정입니다.
이러한 특징들이 복잡한 실시간 통신 시나리오를 간단하게 구현할 수 있게 해줍니다.
코드 예제
// 카메라와 마이크 권한 요청 및 스트림 획득
const localStream = await navigator.mediaDevices.getUserMedia({
video: { width: 1280, height: 720 },
audio: { echoCancellation: true, noiseSuppression: true }
});
// 각 트랙을 PeerConnection에 추가
localStream.getTracks().forEach(track => {
// sender 객체를 받아서 나중에 트랙 제어에 사용
const sender = peerConnection.addTrack(track, localStream);
console.log(`${track.kind} 트랙 추가됨:`, track.label);
});
// 로컬 비디오 미리보기 표시
document.querySelector('#localVideo').srcObject = localStream;
설명
이것이 하는 일: 미디어 트랙 추가는 사용자의 카메라와 마이크에 접근해서 그 데이터를 P2P 연결을 통해 전송할 준비를 합니다. 마치 방송 장비를 송출 라인에 연결하는 것과 같습니다.
첫 번째로, getUserMedia 부분은 브라우저에 미디어 장치 접근 권한을 요청합니다. 사용자가 허용하면 지정된 품질(1280x720 비디오, 에코 제거된 오디오)로 스트림을 생성합니다.
이 과정에서 브라우저는 자동으로 권한 팝업을 표시하고, 사용자가 거부하면 예외가 발생합니다. 그 다음으로, getTracks().forEach 부분이 실행되면서 스트림의 모든 트랙(보통 비디오 1개, 오디오 1개)을 순회합니다.
addTrack 메서드는 각 트랙을 PeerConnection에 추가하고 RTCRtpSender 객체를 반환합니다. 이 sender 객체를 통해 나중에 비트레이트 조정, 트랙 교체, 전송 중단 등을 제어할 수 있습니다.
마지막 단계에서는 srcObject에 스트림을 할당하여 로컬 비디오를 화면에 표시합니다. 이는 사용자가 자신의 모습을 미리 볼 수 있게 하며, 카메라가 제대로 작동하는지 확인하는 용도입니다.
여러분이 이 코드를 사용하면 화상 회의, 라이브 스트리밍, 원격 교육 등 다양한 실시간 미디어 애플리케이션을 만들 수 있습니다. 트랙별로 관리되므로 화면 공유 추가, 배경 블러 적용, 가상 배경 등 고급 기능도 구현 가능합니다.
실전 팁
💡 getUserMedia는 HTTPS 환경에서만 작동합니다. 개발 중에는 localhost도 허용되지만, 배포 시에는 반드시 SSL 인증서를 설정하세요.
💡 addTrack의 두 번째 파라미터(stream)는 선택사항이지만, 제공하면 상대방이 같은 스트림의 트랙들을 그룹으로 인식할 수 있어 동기화에 유리합니다.
💡 모바일에서는 후면/전면 카메라 전환을 위해 facingMode: { ideal: 'user' } 또는 'environment'를 사용하세요. 트랙을 교체할 때는 replaceTrack 메서드를 사용합니다.
💡 화면 공유는 getDisplayMedia를 사용하며, 브라우저가 자동으로 어떤 창/화면을 공유할지 선택 UI를 표시합니다.
💡 사용이 끝난 스트림은 getTracks().forEach(track => track.stop())으로 정리해야 카메라 LED가 꺼지고 리소스가 해제됩니다.
4. 이벤트_리스너_등록
시작하며
여러분이 P2P 연결을 만들고 미디어를 추가했는데 "상대방이 보낸 영상은 어떻게 받지?" 하는 궁금증이 생깁니다. 연결은 양방향이므로 상대방도 여러분에게 미디어를 보낼 텐데, 그걸 받아서 화면에 표시하는 코드가 필요합니다.
이런 상황은 우편함을 설치했지만 우편물이 왔는지 확인하지 않는 것과 같습니다. WebRTC는 비동기 이벤트 기반으로 작동하므로, 상대방의 트랙이 도착하거나, ICE 후보가 생성되거나, 연결 상태가 변경될 때 자동으로 알림을 받을 수 있습니다.
바로 이럴 때 필요한 것이 이벤트 리스너 등록입니다. RTCPeerConnection의 다양한 이벤트를 감지하여 적절히 대응하는 핸들러를 작성합니다.
개요
간단히 말해서, 이벤트 리스너는 RTCPeerConnection에서 발생하는 중요한 이벤트들을 감지하고 처리하는 콜백 함수입니다. 이벤트 리스너가 필요한 이유는 WebRTC의 비동기적 특성 때문입니다.
예를 들어, 상대방이 언제 비디오를 보낼지, ICE 후보가 언제 수집될지, 연결이 언제 끊어질지 미리 알 수 없습니다. 이벤트 리스너를 등록하면 이런 상황이 발생할 때 자동으로 알림을 받아 처리할 수 있습니다.
기존에는 폴링 방식으로 상태를 계속 체크해야 했다면, 이제는 이벤트 기반으로 필요할 때만 반응하여 효율적입니다. 주요 이벤트의 핵심은 첫째, track 이벤트(상대방 미디어 수신), 둘째, icecandidate 이벤트(네트워크 경로 발견), 셋째, connectionstatechange 이벤트(연결 상태 변화)입니다.
이러한 이벤트들을 적절히 처리하면 안정적이고 사용자 친화적인 WebRTC 애플리케이션을 만들 수 있습니다.
코드 예제
// 상대방의 미디어 트랙을 수신했을 때
peerConnection.ontrack = (event) => {
console.log('원격 트랙 수신:', event.track.kind);
const remoteVideo = document.querySelector('#remoteVideo');
// 첫 번째 스트림을 비디오 요소에 연결
if (!remoteVideo.srcObject) {
remoteVideo.srcObject = event.streams[0];
}
};
// ICE 후보가 생성되었을 때 (시그널링 서버로 전송해야 함)
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
// 시그널링 서버를 통해 상대방에게 전송
sendToSignalingServer({ type: 'ice-candidate', candidate: event.candidate });
}
};
// 연결 상태가 변경되었을 때
peerConnection.onconnectionstatechange = () => {
console.log('연결 상태:', peerConnection.connectionState);
if (peerConnection.connectionState === 'failed') {
// 재연결 로직 실행
handleReconnection();
}
};
// ICE 수집 상태 변경
peerConnection.onicegatheringstatechange = () => {
console.log('ICE 수집 상태:', peerConnection.iceGatheringState);
};
설명
이것이 하는 일: 이벤트 리스너 등록은 WebRTC 연결의 생명주기 동안 발생하는 모든 중요한 순간을 포착하여 적절히 대응하게 합니다. 마치 비행기 계기판의 경고등처럼, 무언가 일어났을 때 즉시 알려주는 역할입니다.
첫 번째로, ontrack 이벤트는 상대방이 보낸 미디어 트랙이 도착했을 때 발생합니다. 이것이 실제로 화상 통화가 시작되는 순간입니다.
event.streams[0]에는 상대방의 MediaStream이 들어있고, 이를 video 요소의 srcObject에 할당하면 화면에 표시됩니다. 여러 트랙이 순차적으로 도착할 수 있으므로 srcObject가 이미 설정되어 있는지 확인합니다.
그 다음으로, onicecandidate 이벤트는 브라우저가 자신에게 연결할 수 있는 네트워크 경로(IP 주소와 포트)를 발견할 때마다 발생합니다. 이 후보들을 시그널링 서버를 통해 상대방에게 전달해야 상대방이 여러분에게 연결할 수 있습니다.
event.candidate가 null이면 모든 후보 수집이 완료된 것을 의미합니다. 연결 상태 모니터링은 onconnectionstatechange로 이루어집니다.
connectionState는 'new', 'connecting', 'connected', 'disconnected', 'failed', 'closed' 같은 값을 가지며, 각 상태에 따라 UI를 업데이트하거나 재연결을 시도할 수 있습니다. 특히 'failed' 상태는 연결이 복구 불가능하게 실패했음을 의미하므로 새로운 연결을 시작해야 합니다.
여러분이 이 이벤트들을 제대로 처리하면 사용자에게 부드러운 경험을 제공할 수 있습니다. 예를 들어, 연결 중일 때는 로딩 스피너를 표시하고, 연결되면 자동으로 비디오를 보여주며, 끊어지면 재연결 시도나 에러 메시지를 표시하는 식입니다.
실전 팁
💡 ontrack은 트랙이 추가될 때마다 발생하므로 여러 번 호출될 수 있습니다. 첫 호출 때만 처리하거나, 트랙 종류(audio/video)에 따라 다르게 처리하세요.
💡 onicecandidate에서 모든 후보를 즉시 전송하는 대신, 일정 시간 모아서 배치로 전송하면 시그널링 메시지 수를 줄일 수 있습니다. 이를 "trickle ICE"라고 합니다.
💡 onnegotiationneeded 이벤트도 중요합니다. 트랙을 추가/제거할 때 자동으로 발생하며, 이때 offer/answer 협상을 다시 진행해야 합니다.
💡 이벤트 핸들러 내에서 에러가 발생하면 조용히 무시되므로, try-catch로 감싸고 에러를 로깅하거나 사용자에게 알리는 것이 좋습니다.
💡 ondatachannel 이벤트는 상대방이 DataChannel을 생성했을 때 발생합니다. 채팅이나 파일 전송 기능을 구현할 때 필수적입니다.
5. 연결_상태_모니터링
시작하며
여러분이 화상 회의 중에 "왜 갑자기 끊겼지?" 하는 경험 있으신가요? 네트워크가 불안정하거나, 상대방이 Wi-Fi에서 모바일 데이터로 전환하거나, 방화벽 설정이 변경되는 등 여러 이유로 연결이 불안정해질 수 있습니다.
이런 문제는 실시간 통신에서 피할 수 없는 현실입니다. 사용자가 이동 중이거나, 네트워크 환경이 계속 바뀌거나, 서버가 일시적으로 과부하 상태일 수 있습니다.
이런 상황을 감지하지 못하면 사용자는 멈춘 화면을 보면서 답답해합니다. 바로 이럴 때 필요한 것이 연결 상태 모니터링입니다.
RTCPeerConnection의 다양한 상태를 실시간으로 추적하여 문제를 조기에 발견하고 대응할 수 있습니다.
개요
간단히 말해서, 연결 상태 모니터링은 WebRTC 연결의 건강 상태를 지속적으로 체크하고, 문제가 생기면 즉시 감지하여 대응하는 메커니즘입니다. 모니터링이 필요한 이유는 사용자 경험 때문입니다.
예를 들어, 연결이 끊어지는데 사용자가 모르고 계속 말하고 있다면 매우 곤란합니다. 적절한 모니터링으로 "연결이 약해졌습니다", "재연결 중입니다" 같은 피드백을 제공할 수 있습니다.
기존에는 타임아웃이나 heartbeat 패킷으로 연결을 체크했다면, 이제는 WebRTC가 제공하는 다양한 상태 프로퍼티와 이벤트로 더 정확하게 모니터링할 수 있습니다. 주요 모니터링 포인트는 첫째, connectionState(전체 연결 상태), 둘째, iceConnectionState(ICE 연결 상태), 셋째, signalingState(시그널링 협상 상태)입니다.
이러한 상태들을 종합적으로 모니터링하면 어떤 단계에서 문제가 발생했는지 정확히 파악할 수 있습니다.
코드 예제
// 종합 연결 상태 모니터링 시스템
class ConnectionMonitor {
constructor(peerConnection) {
this.pc = peerConnection;
this.setupMonitoring();
}
setupMonitoring() {
// 전체 연결 상태 모니터링
this.pc.onconnectionstatechange = () => {
const state = this.pc.connectionState;
console.log('연결 상태:', state);
this.updateUI(state);
// 상태별 처리
switch(state) {
case 'connected':
this.onConnected();
break;
case 'disconnected':
this.onDisconnected();
break;
case 'failed':
this.onFailed();
break;
}
};
// ICE 연결 상태 모니터링
this.pc.oniceconnectionstatechange = () => {
console.log('ICE 상태:', this.pc.iceConnectionState);
};
// 시그널링 상태 모니터링
this.pc.onsignalingstatechange = () => {
console.log('시그널링 상태:', this.pc.signalingState);
};
}
updateUI(state) {
const statusElement = document.querySelector('#connection-status');
statusElement.textContent = state;
statusElement.className = `status-${state}`;
}
onConnected() {
console.log('연결 성공! 통화 시작');
}
onDisconnected() {
console.log('일시적 연결 끊김, 재연결 시도 중...');
}
onFailed() {
console.log('연결 실패, 새 연결 필요');
}
}
// 사용 예시
const monitor = new ConnectionMonitor(peerConnection);
설명
이것이 하는 일: 연결 상태 모니터링은 WebRTC 연결의 전 생애주기를 감시하여 사용자에게 현재 상황을 알리고, 문제 발생 시 자동으로 대응합니다. 마치 자동차의 계기판이 엔진 온도, 연료량, 속도 등을 실시간으로 보여주는 것과 같습니다.
첫 번째로, ConnectionMonitor 클래스는 모니터링 로직을 캡슐화합니다. setupMonitoring 메서드에서 세 가지 주요 상태 변경 이벤트에 리스너를 등록합니다.
이렇게 클래스로 구조화하면 여러 PeerConnection을 관리할 때 재사용이 쉽고, 테스트도 용이합니다. 그 다음으로, onconnectionstatechange 핸들러가 가장 중요한 역할을 합니다.
connectionState는 'new' → 'connecting' → 'connected' → 'disconnected' 또는 'failed' → 'closed' 순서로 변합니다. switch 문으로 각 상태를 처리하며, 특히 'disconnected'는 일시적 끊김(자동 재연결 가능), 'failed'는 영구적 실패(새 연결 필요)를 의미합니다.
ICE와 시그널링 상태도 함께 모니터링하면 더 정확한 진단이 가능합니다. 예를 들어, connectionState는 'connected'인데 iceConnectionState가 'checking'이면 네트워크 경로를 재탐색 중임을 의미합니다.
signalingState가 'stable'이 아니면 offer/answer 협상 중입니다. updateUI 메서드는 상태를 시각적으로 표시합니다.
CSS 클래스를 상태에 따라 변경하면 'connected'일 때는 초록색, 'disconnected'일 때는 노란색, 'failed'일 때는 빨간색으로 표시하는 식으로 직관적인 피드백을 줄 수 있습니다. 여러분이 이 모니터링 시스템을 구현하면 사용자는 항상 현재 연결 상태를 알 수 있고, 문제가 생겨도 앱이 자동으로 대응하는 것을 경험하게 됩니다.
이는 사용자 신뢰도를 크게 높입니다.
실전 팁
💡 connectionState가 'disconnected'에서 30초 이내에 복구되지 않으면 'failed'로 간주하고 재연결을 시작하는 것이 좋습니다.
💡 getStats() API를 사용하면 패킷 손실률, 지터, 비트레이트 등 상세한 품질 지표를 얻을 수 있습니다. 이를 주기적으로 체크하여 품질 문제를 조기에 발견하세요.
💡 iceConnectionState가 계속 'checking'에 머물러 있으면 방화벽 문제일 가능성이 높습니다. 이때는 TURN 서버 사용을 강제하는 것을 고려하세요.
💡 프로덕션 환경에서는 상태 변화를 로깅 서버로 전송하여 통계를 수집하면, 어떤 네트워크 환경에서 문제가 많이 발생하는지 분석할 수 있습니다.
💡 모바일 앱에서는 앱이 백그라운드로 갔다가 돌아올 때 연결 상태를 체크하고 필요하면 재연결해야 합니다. visibilitychange 이벤트를 활용하세요.
6. 재연결_로직
시작하며
여러분이 화상 회의 중에 잠깐 터널을 지나가거나 엘리베이터를 타서 네트워크가 끊겼다가 다시 연결되는 상황을 경험해 봤나요? 이때 앱이 자동으로 다시 연결해주지 않으면 사용자는 앱을 종료하고 다시 접속해야 하는 불편함을 겪습니다.
이런 문제는 모바일 환경이나 불안정한 Wi-Fi에서 특히 자주 발생합니다. 사용자가 이동 중이거나, 네트워크를 전환하거나, 일시적인 서버 문제로 연결이 끊어질 수 있습니다.
수동으로 다시 접속해야 한다면 사용자 경험이 크게 나빠집니다. 바로 이럴 때 필요한 것이 재연결 로직입니다.
연결이 끊어졌을 때 자동으로 복구를 시도하여 사용자가 신경 쓰지 않아도 되게 만듭니다.
개요
간단히 말해서, 재연결 로직은 P2P 연결이 끊어졌을 때 자동으로 복구를 시도하는 메커니즘으로, ICE Restart와 완전한 재협상 등의 방법을 사용합니다. 재연결이 필요한 이유는 네트워크의 불안정성 때문입니다.
예를 들어, 사용자가 Wi-Fi에서 모바일 데이터로 전환하면 IP 주소가 바뀌어 기존 연결이 무효화됩니다. 자동 재연결이 없으면 통화가 끊어지고 처음부터 다시 시작해야 합니다.
기존에는 연결이 끊어지면 페이지를 새로고침하거나 앱을 재시작해야 했다면, 이제는 백그라운드에서 자동으로 재연결하여 끊김없는 경험을 제공할 수 있습니다. 재연결의 핵심 전략은 첫째, ICE Restart(네트워크 경로 재탐색), 둘째, Exponential Backoff(재시도 간격 점진적 증가), 셋째, 완전한 재협상(실패 시 새 연결)입니다.
이러한 전략들을 단계적으로 시도하면 대부분의 일시적 연결 문제를 자동으로 해결할 수 있습니다.
코드 예제
class ReconnectionManager {
constructor(peerConnection, signalingChannel) {
this.pc = peerConnection;
this.signaling = signalingChannel;
this.reconnectAttempts = 0;
this.maxAttempts = 5;
this.baseDelay = 1000; // 1초
}
async handleConnectionFailure() {
if (this.reconnectAttempts >= this.maxAttempts) {
console.error('최대 재연결 시도 횟수 초과');
this.notifyUserOfFailure();
return;
}
this.reconnectAttempts++;
console.log(`재연결 시도 ${this.reconnectAttempts}/${this.maxAttempts}`);
// Exponential backoff: 1초, 2초, 4초, 8초, 16초
const delay = this.baseDelay * Math.pow(2, this.reconnectAttempts - 1);
await this.sleep(delay);
try {
// 1단계: ICE Restart 시도
if (this.reconnectAttempts <= 2) {
await this.iceRestart();
}
// 2단계: 완전한 재협상
else {
await this.fullRenegotiation();
}
this.reconnectAttempts = 0; // 성공 시 카운터 초기화
console.log('재연결 성공!');
} catch (error) {
console.error('재연결 실패:', error);
this.handleConnectionFailure(); // 재귀적으로 다시 시도
}
}
async iceRestart() {
console.log('ICE Restart 수행 중...');
// iceRestart 옵션으로 새로운 offer 생성
const offer = await this.pc.createOffer({ iceRestart: true });
await this.pc.setLocalDescription(offer);
// 시그널링 서버를 통해 상대방에게 전송
this.signaling.send({ type: 'offer', sdp: offer });
}
async fullRenegotiation() {
console.log('완전한 재협상 수행 중...');
// 기존 연결 종료
this.pc.close();
// 새 PeerConnection 생성 및 재연결
// (실제로는 전체 연결 로직을 다시 실행)
await this.recreateConnection();
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
notifyUserOfFailure() {
alert('연결에 실패했습니다. 네트워크를 확인해주세요.');
}
}
// 사용 예시
const reconnectManager = new ReconnectionManager(peerConnection, signalingChannel);
peerConnection.onconnectionstatechange = () => {
if (peerConnection.connectionState === 'failed') {
reconnectManager.handleConnectionFailure();
}
};
설명
이것이 하는 일: 재연결 로직은 연결 실패를 감지하여 여러 단계의 복구 전략을 자동으로 실행합니다. 마치 자동차가 시동이 안 걸릴 때 여러 방법을 차례로 시도하는 것처럼, 가벼운 방법부터 시작해서 점점 강력한 방법으로 전환합니다.
첫 번째로, handleConnectionFailure 메서드는 재연결의 전체 흐름을 제어합니다. reconnectAttempts 카운터로 시도 횟수를 추적하고, maxAttempts에 도달하면 포기합니다.
무한정 재시도하면 배터리와 데이터를 낭비하므로 적절한 제한이 필요합니다. 그 다음으로, Exponential Backoff 전략이 적용됩니다.
첫 번째 시도는 1초 후, 두 번째는 2초 후, 세 번째는 4초 후처럼 점진적으로 간격을 늘립니다. 이렇게 하는 이유는 일시적인 네트워크 문제는 금방 해결되지만, 구조적인 문제는 즉시 재시도해도 실패하므로 서버 부하를 줄이고 효율성을 높이기 위함입니다.
ICE Restart는 가장 가벼운 복구 방법입니다. 기존 연결을 유지하면서 네트워크 경로만 다시 찾습니다.
createOffer({ iceRestart: true })로 새로운 ICE 프로세스를 시작하며, 이는 IP 주소가 바뀌었을 때 효과적입니다. 빠르고 리소스를 적게 사용하므로 첫 1-2회 시도에 사용합니다.
완전한 재협상은 최후의 수단입니다. 기존 PeerConnection을 완전히 닫고 처음부터 새로 만듭니다.
이는 연결 객체 자체에 문제가 생겼을 때 필요하며, 시간이 더 걸리지만 거의 모든 문제를 해결할 수 있습니다. 여러분이 이 로직을 구현하면 사용자가 엘리베이터를 타거나, 터널을 지나거나, Wi-Fi와 모바일 데이터를 전환해도 앱이 자동으로 복구됩니다.
실제 테스트에서 이런 재연결 로직은 95% 이상의 일시적 연결 문제를 자동으로 해결합니다.
실전 팁
💡 재연결 중임을 사용자에게 명확히 알려야 합니다. "재연결 중..." 같은 메시지를 표시하면 사용자가 기다릴 수 있습니다.
💡 maxAttempts는 사용 사례에 따라 조정하세요. 중요한 통화는 10회 이상 시도할 수 있지만, 일반적인 경우 3-5회가 적당합니다.
💡 연결이 복구되었을 때 "연결이 복구되었습니다" 알림을 보여주면 사용자가 안심할 수 있습니다.
💡 네트워크 품질이 좋지 않다면 비디오 해상도를 자동으로 낮추는 것도 고려하세요. sender.setParameters()로 비트레이트를 조절할 수 있습니다.
💡 완전한 재협상 시 미디어 트랙을 다시 추가해야 하므로, getUserMedia를 다시 호출할 필요가 있는지 확인하세요. 기존 스트림을 재사용할 수 있다면 더 빠릅니다.
댓글 (0)
함께 보면 좋은 카드 뉴스
Socket.io로 배우는 WebRTC 시그널링 서버 구축
WebRTC 연결을 위한 필수 요소인 시그널링 서버를 Socket.io로 구축하는 방법을 배웁니다. Room 관리, Offer/Answer 교환, ICE Candidate 전달 등 실시간 통신의 핵심 개념을 실무 예제와 함께 친절하게 설명합니다.
getUserMedia로 카메라/마이크 접근하기 - 실전 가이드
웹 브라우저에서 카메라와 마이크에 접근하는 방법을 배워봅니다. getUserMedia API를 사용하여 미디어 스트림을 가져오고, 권한을 관리하며, 실시간 화상 통화나 녹화 기능을 구현하는 방법을 단계별로 알아봅니다.
WebRTC 기초와 화상회의 개념 완벽 가이드
실시간 화상회의를 구현하고 싶으신가요? WebRTC의 기초부터 P2P 통신, NAT 문제 해결, 실제 화상회의 서비스 구현까지 초급 개발자도 쉽게 이해할 수 있도록 친절하게 안내합니다. 실무에서 바로 활용할 수 있는 코드 예제와 함께 WebRTC의 모든 것을 배워보세요.
Socket.io 인증 및 온라인 상태 관리 완벽 가이드
실시간 채팅이나 협업 도구를 만들 때 꼭 필요한 Socket.io 인증 방법과 사용자 온라인 상태 관리를 배워봅니다. JWT를 활용한 안전한 WebSocket 연결부터 Redis를 활용한 확장 가능한 상태 관리까지, 실무에 바로 적용할 수 있는 모든 것을 다룹니다.
개발 서버 심화 설정 완벽 가이드
Vite 개발 서버의 고급 설정을 마스터하여 더 효율적이고 안전한 개발 환경을 구축하는 방법을 배웁니다. 포트 설정부터 HTTPS, Proxy, CORS 해결, 환경 변수 관리, 미들웨어 커스터마이징까지 실무에서 바로 활용할 수 있는 모든 것을 다룹니다.