별도의 플러그인이나 프로그램 설치 없이, 웹 브라우저만으로 밀리초(ms) 단위의 초저지연 비디오·오디오·데이터 통신을 구현하는 WebRTC 기술의 심층 가이드입니다. 시그널링 메커니즘, SDP, STUN/TURN 서버, SFU 미디어 아키텍처, 실무에서 겪는 네트워크 트랩까지 완벽하게 파헤칩니다.
**WebRTC(Web Real-Time Communication)**는 웹 브라우저 간에 중간 서버를 거치지 않고(또는 최소한으로만 거치고) Peer-to-Peer(P2P) 방식으로 음성, 영상, 임의의 binary 데이터를 실시간으로 송수신할 수 있게 해주는 오픈소스 웹 표준 사양입니다.
기존의 HTTP 기반 HLS(HTTP Live Streaming)나 DASH 스트리밍은 2~10초 정도의 지연 시간(Latency)이 발생하지만, WebRTC는 **200ms 미만의 초저지연(Sub-second Latency)**을 보장합니다.
| 프로토콜 | 전송 계층 | 주요 지연 시간 | 대표적인 용도 |
|---|---|---|---|
| HLS / DASH | HTTP (TCP) | 2초 ~ 10초 | VOD, 대규모 단방향 라이브 방송 |
| WebSockets | TCP | 100ms ~ 500ms | 채팅, 알림, 실시간 데이터 업데이트 |
| WebRTC | UDP (SRTP / SCTP) | 50ms ~ 200ms | 화상회의(Zoom, Google Meet), P2P 파일 전송, 실시간 게임 |
WebRTC는 브라우저 자바스크립트 환경에서 크게 3가지 주요 API로 구성됩니다.
MediaDevices.getUserMedia(): 사용자의 마이크와 카메라에 접근하여 비디오/오디오 스트림(MediaStream)을 획득합니다.RTCPeerConnection: 두 피어 간의 연결을 설정하고, 암호화 및 대역폭을 관리하며, 비디오/오디오 미디어를 송수신하는 핵심 객체입니다.RTCDataChannel: 피어 간에 미디어 스트림 외에 임의의 텍스트 및 이진 데이터(Binary Data)를 저지연으로 직접 송수신할 수 있는 양방향 데이터 통로입니다.// 1. 미디어 스트림 획득 및 제약 조건 설정 예시
async function getLocalStream() {
try {
const constraints = {
video: {
width: { ideal: 1280, max: 1920 },
height: { ideal: 720, max: 1080 },
frameRate: { ideal: 30, max: 60 }
},
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
const videoElement = document.getElementById('myVideo');
if (videoElement) {
videoElement.srcObject = stream;
}
return stream;
} catch (err) {
console.error('카메라/마이크 접근 실패 (NotAllowedError 또는 NotFoundError):', err);
throw err;
}
}WebRTC는 P2P 통신을 지향하지만, **최초 연결 설정 시 두 피어가 서로의 존재와 네트워크 정보, 미디어 포맷을 알기 위해서는 중앙 서버(시그널링 서버)**가 필요합니다.
WebRTC 표준 사양은 시그널링의 구현 방식(WebSocket, Socket.io, SSE, HTTP POST 등)을 지정하지 않으므로 개발자가 자유롭게 선택할 수 있습니다.
SDP는 피어 간에 통신할 미디어의 기능과 설정을 협상하기 위한 텍스트 메타데이터 포맷입니다.
Offer-Answer 모델을 사용하여 한 피어가 Offer SDP를 보내면 상대 피어가 Answer SDP로 응답합니다.
v=0
o=- 42039201948290 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104
a=rtpmap:111 opus/48000/2
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98
a=rtpmap:96 VP8/90000m=audio, m=video: 지원되는 미디어 유형과 연동 포트a=rtpmap: 지원하는 비디오/오디오 코덱 (Opus, VP8, VP9, H.264, AV1)a=fingerprint: 보안 암호화(DTLS) 인증서 핑거프린트대부분의 브라우저는 공유기(NAT)나 방화벽 뒤에 존재하므로 사설 IP(Private IP)를 가지고 있어 상대방과 직접 P2P 통신이 불가능할 수 있습니다. 이를 해결하는 기술이 **ICE(Interactive Connectivity Establishment)**입니다.
// RTCPeerConnection 생성 시 ICE 서버 구성 및 설정 예시
const configuration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:my-turn-server.example.com:3478',
username: 'user123',
credential: 'password_secret'
}
],
iceTransportPolicy: 'all', // 'relay' 지정 시 TURN만 강제 사용 가능
bundlePolicy: 'max-bundle'
};
const peerConnection = new RTCPeerConnection(configuration);다음은 연결 설정부터 P2P 세션 개시까지의 전체 흐름입니다.
1:1 통신을 넘어 N:N 다자간 화상 회의 시스템을 구축할 때는 클라이언트 단독 P2P 방식만으로는 한계가 발생합니다.
각 아키텍처의 구체적인 특징과 장단점을 비교한 내용입니다.
아래 코드는 WebSocket 시그널링 서버와 연동되는 PeerConnection 수립의 전체적인 핵심 로직 구조입니다.
// peer.js - 피어 연결 관리 클래스
class WebRTCClient {
constructor(socket) {
this.socket = socket;
this.pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
this.candidateQueue = [];
this.isRemoteDescriptionSet = false;
this.initListeners();
}
initListeners() {
// 1. ICE Candidate 수집 시 상대방에게 전달
this.pc.onicecandidate = (event) => {
if (event.candidate) {
this.socket.emit('ice-candidate', event.candidate);
}
};
// 2. 상대방의 미디어 트랙 수신 시
this.pc.ontrack = (event) => {
const remoteVideo = document.getElementById('remoteVideo');
if (remoteVideo && event.streams[0]) {
remoteVideo.srcObject = event.streams[0];
}
};
}
// Offer 생성 및 송신
async makeOffer() {
try {
const offer = await this.pc.createOffer();
await this.pc.setLocalDescription(offer);
this.socket.emit('offer', offer);
} catch (err) {
console.error('Offer 생성 오류:', err);
}
}
// Offer 수신 처리 및 Answer 생성
async handleOffer(offer) {
try {
await this.pc.setRemoteDescription(new RTCSessionDescription(offer));
this.isRemoteDescriptionSet = true;
this.processCandidateQueue();
const answer = await this.pc.createAnswer();
await this.pc.setLocalDescription(answer);
this.socket.emit('answer', answer);
} catch (err) {
console.error('Offer 핸들링 오류:', err);
}
}
// Answer 수신 처리
async handleAnswer(answer) {
try {
await this.pc.setRemoteDescription(new RTCSessionDescription(answer));
this.isRemoteDescriptionSet = true;
this.processCandidateQueue();
} catch (err) {
console.error('Answer 핸들링 오류:', err);
}
}
// ICE Candidate 추가 (Race Condition 방지 큐 적용)
async handleCandidate(candidate) {
const iceCandidate = new RTCIceCandidate(candidate);
if (!this.isRemoteDescriptionSet) {
this.candidateQueue.push(iceCandidate);
return;
}
try {
await this.pc.addIceCandidate(iceCandidate);
} catch (e) {
console.error('ICE Candidate 추가 오류:', e);
}
}
// 큐에 대기 중인 Candidate 일괄 처리
async processCandidateQueue() {
while (this.candidateQueue.length > 0) {
const candidate = this.candidateQueue.shift();
try {
await this.pc.addIceCandidate(candidate);
} catch (e) {
console.error('큐 내 ICE Candidate 추가 오류:', e);
}
}
}
}화면 공유 기능은 navigator.mediaDevices.getDisplayMedia()를 사용하며, 기존 비디오 트랙을 화면 공유 트랙으로 즉시 교체(replaceTrack)할 수 있습니다.
async function startScreenShare(peerConnection) {
try {
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: "always",
displaySurface: "monitor"
},
audio: false
});
const screenTrack = screenStream.getVideoTracks()[0];
// RTCRtpSender를 통해 피어 커넥션 재연결 없이 비디오 트랙만 교체
const senders = peerConnection.getSenders();
const videoSender = senders.find(s => s.track && s.track.kind === 'video');
if (videoSender) {
await videoSender.replaceTrack(screenTrack);
}
// 사용자가 브라우저 기본 UI의 '공유 중단' 버튼을 누른 경우 처리
screenTrack.onended = async () => {
console.log('사용자에 의해 화면 공유 종료됨');
// 원래 카메라 스트림 복구 로직 연동 가능
};
} catch (err) {
console.error('화면 공유 실패:', err);
}
}WebRTC를 프로덕션 환경에 배포할 때 가장 자주 겪는 4가지 실무 함정입니다.
setRemoteDescription이 완료되기 전에 시그널링을 통해 들어온 ICE Candidate를 addIceCandidate 하면 InvalidStateError 예외가 발생합니다.setRemoteDescription 이행 완료 후 순차 처리합니다.getUserMedia 거부localhost를 제외한 http:// 환경에서 카메라와 마이크 권한 접근을 엄격히 차단합니다.https://)하여 테스트해야 합니다.<video> 태그의 autoplay 재생을 차단합니다.<video autoplay playsinline muted> 속성을 반드시 지정하고, 오디오 재생 시 사용자의 ‘소리 켜기’ 버튼 클릭 이벤트를 유도해야 합니다.WebRTC가 만능은 아닙니다. 아래와 같은 사용 사례에서는 다른 기술을 고려하는 것이 훨씬 합리적입니다.
| 유스케이스 | WebRTC 사용 비추천 이유 | 권장 대안 기술 |
|---|---|---|
| 1:N 대규모 인원 대상 방송 (10,000명+) | P2P 기반 스트림 중계 비용 및 대역폭 감당 불가 | HLS, Low-Latency HLS, LL-DASH |
| 단방향 텍스트/숫자 데이터 수신 | UDP 기반 시그널링 패킷 손실 및 오버헤드 | WebSocket, SSE (Server-Sent Events) |
| 단순 파일 다운로드 및 서버 저장 | P2P 세션 유지 복잡성 및 브라우저 메모리 제약 | HTTP/2, HTTP/3 Direct Download |
✨ 한 줄 요약
웹에서 초저지연 실시간 경험을 선사하는 WebRTC, 시그널링과 SFU 아키텍처의 단단한 이해가 성공적인 라이브 미디어 서비스 구축의 핵심입니다.
