WebRTC 기초 — P2P 연결, 미디어 스트림, 데이터 채널
WebRTC는 브라우저 간 서버를 거치지 않고 직접 연결하여 영상, 음성, 데이터를 주고받는 기술입니다. 화상회의, 파일 공유, 실시간 게임에 사용됩니다.
WebRTC 연결 흐름
1. Signaling — ICE 후보와 SDP를 교환 (서버 필요)
2. STUN/TURN — NAT 뒤의 IP 주소를 발견
3. P2P 연결 — 직접 연결 수립
4. 미디어/데이터 전송
미디어 스트림 가져오기
// 카메라와 마이크 접근
async function getMedia() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { width: 1280, height: 720 },
audio: true,
});
// 로컬 비디오에 표시
const video = document.getElementById("localVideo");
video.srcObject = stream;
return stream;
} catch (err) {
console.error("미디어 접근 실패:", err);
}
}
// 화면 공유
async function getScreenShare() {
return navigator.mediaDevices.getDisplayMedia({
video: { cursor: "always" },
audio: false,
});
}
P2P 연결 설정
// ICE 서버 설정
const config = {
iceServers: [
{ urls: "stun:stun.l.google.com:19302" }, // 무료 STUN 서버
],
};
// Peer A (Caller)
const peerA = new RTCPeerConnection(config);
// Peer B (Callee)
const peerB = new RTCPeerConnection(config);
// ICE 후보 교환 (실제로는 시그널링 서버를 통해)
peerA.onicecandidate = (e) => {
if (e.candidate) {
// 시그널링 서버를 통해 peerB에 전달
peerB.addIceCandidate(e.candidate);
}
};
peerB.onicecandidate = (e) => {
if (e.candidate) {
peerA.addIceCandidate(e.candidate);
}
};
SDP 교환 (Offer/Answer)
// 1. Caller가 Offer 생성
const offer = await peerA.createOffer();
await peerA.setLocalDescription(offer);
// 2. 시그널링으로 Offer를 Callee에 전달
// signalingServer.send(offer)
// 3. Callee가 Offer를 받고 Answer 생성
await peerB.setRemoteDescription(offer);
const answer = await peerB.createAnswer();
await peerB.setLocalDescription(answer);
// 4. 시그널링으로 Answer를 Caller에 전달
await peerA.setRemoteDescription(answer);
// 5. P2P 연결 완료!
미디어 트랙 전송
// 미디어 스트림의 트랙을 피어 연결에 추가
const localStream = await getMedia();
localStream.getTracks().forEach((track) => {
peerA.addTrack(track, localStream);
});
// 상대방의 미디어 수신
peerB.ontrack = (event) => {
const remoteVideo = document.getElementById("remoteVideo");
remoteVideo.srcObject = event.streams[0];
};
데이터 채널 — 임의 데이터 전송
// Caller 측에서 데이터 채널 생성
const dataChannel = peerA.createDataChannel("chat", {
ordered: true, // 순서 보장
});
dataChannel.onopen = () => {
console.log("데이터 채널 열림");
dataChannel.send("안녕하세요!");
};
dataChannel.onmessage = (e) => {
console.log("받음:", e.data);
};
// Callee 측에서 데이터 채널 수신
peerB.ondatachannel = (event) => {
const channel = event.channel;
channel.onmessage = (e) => {
console.log("받음:", e.data);
channel.send("반갑습니다!");
};
};
연결 상태 모니터링
peerA.onconnectionstatechange = () => {
console.log("연결 상태:", peerA.connectionState);
// "new", "connecting", "connected", "disconnected", "failed", "closed"
if (peerA.connectionState === "failed") {
// 재연결 시도
peerA.restartIce();
}
};
peerA.oniceconnectionstatechange = () => {
console.log("ICE 상태:", peerA.iceConnectionState);
};
미디어 제어
// 비디오 끄기/켜기
function toggleVideo(stream) {
const videoTrack = stream.getVideoTracks()[0];
videoTrack.enabled = !videoTrack.enabled;
}
// 오디오 끄기/켜기 (뮤트)
function toggleAudio(stream) {
const audioTrack = stream.getAudioTracks()[0];
audioTrack.enabled = !audioTrack.enabled;
}
// 카메라 전환
async function switchCamera(peerConnection) {
const newStream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: "environment" }, // 후면 카메라
});
const newTrack = newStream.getVideoTracks()[0];
const sender = peerConnection.getSenders()
.find((s) => s.track?.kind === "video");
await sender.replaceTrack(newTrack);
}
WebRTC vs WebSocket
| 항목 | WebRTC | WebSocket |
|---|---|---|
| 연결 방식 | P2P (직접) | 클라이언트-서버 |
| 서버 부하 | 시그널링만 | 모든 데이터 중계 |
| 미디어 지원 | 내장 | 직접 구현 |
| 지연시간 | 매우 낮음 | 낮음 |
| NAT 통과 | STUN/TURN 필요 | 불필요 |
| 적합한 용도 | 화상통화, P2P 게임 | 채팅, 알림 |
** 기억하기 **: WebRTC는 브라우저 간 직접 연결이지만, 초기 연결 설정에는 시그널링 서버가 필요합니다. Offer → Answer → ICE Candidate 교환이 핵심 흐름이고, 연결이 수립되면 미디어와 데이터를 서버 없이 P2P로 주고받습니다.
댓글 로딩 중...