본문 바로가기

[WebRTC 박살내기 #4] 데이터 채널 구조와 활용법

WebRTC 박살내기 시리즈의 마지막 글입니다. RTCDataChannel API를 이해하고, 채팅과 파일 전송, 게임 상태 동기화까지 영상과 음성이 아닌 데이터를 P2P로 보내는 방법을 실전 예제와 함께 다룹니다.

읽는 시간 20
[WebRTC 박살내기 #4] 데이터 채널 구조와 활용법

TL;DR

지난 편에서는 RTCPeerConnection의 이벤트 흐름을 배웠습니다. PeerConnection을 생성하고 미디어 트랙을 주고받는 방법까지 알아봤는데요.

"WebRTC로 채팅도 만들 수 있을까?"
"게임에서 플레이어 위치를 실시간으로 동기화하려면?"

네, 전부 가능합니다! WebRTC는 영상과 음성뿐만 아니라 모든 종류의 데이터를 P2P로 전송할 수 있어요.
이 역할을 맡는 게 바로 RTCDataChannel입니다. 내부적으로 SCTP 프로토콜을 쓰기 때문에 신뢰성(Reliable/Unreliable)과 순서(Ordered/Unordered)를 채널마다 자유롭게 조합합니다.

이번 글에서 다루는 것은 이렇습니다.

  • RTCDataChannel이 무엇인지
  • WebSocket과의 차이
  • SCTP 전송 모드
  • 채팅과 파일 전송, 게임 좌표 같은 실전 시나리오

데이터 채널은 브라우저끼리 데이터를 주고받는 통로입니다

RTCDataChannel은 두 브라우저가 직접 데이터를 주고받는 통로예요. 서버를 가운데 두지 않고 브라우저끼리 바로 이어집니다.

영상 통화를 하면서 채팅도 하고 파일도 주고받고 게임 데이터도 동기화할 수 있어요.
서버를 거치지 않으니 빠르고 서버 비용도 절약돼요.

DataChannel이 서버를 완전히 안 거치는 것은 아니에요. 연결이 성립되면 브라우저 간 P2P로 통신하지만, 일부 네트워크에서는 TURN 릴레이로 서버를 경유하고 그만큼 대역폭 비용이 발생합니다.

WebSocket은 서버를 거치고 DataChannel은 안 거칩니다

채팅이나 실시간 데이터 전송이라고 하면 WebSocket이 먼저 떠오를 거예요. DataChannel도 비슷하지만 큰 차이가 있습니다.

WebSocket은 사용자A가 보낸 데이터가 서버를 거쳐 사용자B에게 가요. 모든 데이터가 그렇습니다.
DataChannel은 한 번 연결된 뒤로는 서버를 거치지 않고 바로 사용자B에게 갑니다.

특징WebSocketRTCDataChannel
연결 방식서버를 통한 중계P2P 직접 연결
연결을 세우는 값핸드셰이크 한 번시그널링과 ICE, DTLS를 모두 지나야 함
데이터 경로항상 서버를 지남직접 연결이 되면 서버를 안 지남
N명에게 보낼 때서버가 한 번 받아 뿌림상대마다 따로 보내야 함
신뢰성항상 보장선택 가능
순서 보장항상 보장선택 가능

그래서 화상 통화 중에 채팅을 하거나 파일을 주고받아도 서버 트래픽이 늘지 않아요. 사용자가 늘어도 서버가 감당할 데이터가 그만큼 늘지는 않습니다.

다만 서버 비용이 0이 되는 것은 아니에요. 직접 연결이 안 되면 TURN을 거치고, 그때는 중계한 만큼 대역폭 비용이 붙습니다. 지연도 서버를 한 번 도는 만큼 늘어나요.

방향을 뒤집어 보면 WebSocket이 나은 경우도 있어요. 1편에서 본 것처럼 P2P는 사람이 많아질수록 연결이 빠르게 늘어나요. 100명이 함께 쓰는 채팅방이면 연결이 4,950개나 필요해요. 서버가 한 번 받아 100명에게 뿌리는 쪽이 훨씬 쌉니다. DataChannel은 사람이 적고 지연이 중요할 때 씁니다.


SCTP가 채널마다 신뢰성과 순서를 갈라 줍니다

RTCDataChannel은 내부적으로 SCTP (새 창에서 열림)라는 프로토콜을 써요.

SCTP(Stream Control Transmission Protocol)는 메시지를 여러 갈래로 나눠 보내는 전송 프로토콜이에요. 이 갈래를 스트림이라고 부르고, 데이터 채널 하나가 스트림 하나를 씁니다.

채널마다 잃은 메시지를 다시 보낼지, 순서를 지킬지 따로 정할 수 있어요. 메시지마다 정하는 것이 아니라 채널을 만들 때 정합니다.

갈래를 여러 개로 나누는 것이 SCTP를 쓰는 이유예요. TCP는 줄이 하나라서 앞의 패킷이 늦으면 뒤에 온 것들이 다 기다립니다. 이것을 head-of-line blocking이라고 불러요. WebSocket은 TCP 위에 있으니 이 성질을 그대로 물려받습니다.

SCTP는 줄을 여러 개로 나눠서 한 줄이 막혀도 다른 줄은 흘러요. 아예 순서를 포기하면 기다리는 일 자체가 없어집니다.

암호화는 따로 신경 쓰지 않아도 됩니다. 데이터 채널은 SCTP가 DTLS 위에서 동작해요. 1편에서 키를 나눈다고 한 그 DTLS가 여기서는 데이터까지 암호화합니다.

신뢰성과 순서를 조합해 네 가지 모드가 나옵니다

SCTP는 두 가지 옵션을 조합할 수 있어요. 채널마다 신뢰성과 순서를 따로 정합니다.

신뢰성은 잃은 메시지를 다시 보낼지 정합니다

Reliable (신뢰성 보장)
  • 편지가 분실되면 다시 보냄
  • 모든 데이터가 반드시 도착
  • 예: 채팅 메시지, 파일 전송
Unreliable (신뢰성 미보장)
  • 편지가 분실되어도 다시 보내지 않음
  • 빠른 전달이 더 중요할 때
  • 예: 게임 좌표, 실시간 센서값

순서는 보낸 차례를 지킬지 정합니다

Ordered (순서 보장)
  • 보낸 순서대로 받음
  • 1번 다음 2번, 그다음 3번 순서가 중요
  • 예: 파일 조각, 대화 내용
Unordered (순서 미보장)
  • 빨리 도착하는 것부터 받음
  • 최신 정보만 중요
  • 예: 현재 위치, 센서 최신값

두 축을 겹쳐 놓으면 조합이 넷 나와요.

실제로 가장 많이 쓰는 조합은 채팅과 파일 전송, 게임 좌표와 센서값이에요. 대각선으로 마주 보는 칸이라 요구가 정반대입니다. 아래에서 이 둘을 코드로 봐요.

게임과 채팅은 서로 반대쪽 조합을 고릅니다

실시간 게임을 예로 들어볼게요.

플레이어 위치 전송
  • Unreliable: 1초 전 위치는 의미가 없어요
  • Unordered: 최신 위치만 중요해요

이 조합은 채널을 만드는 pc.createDataChannel()의 두 번째 인자로 넘겨요.

{
  ordered: false,
  maxRetransmits: 0
}

ordered: false가 순서를 포기하고 maxRetransmits: 0이 재전송을 포기해요. 위에서 고른 Unordered와 Unreliable이 그대로 두 줄이 됐어요.

채팅 메시지
  • Reliable: 모든 메시지가 도착해야 해요
  • Ordered: 순서가 바뀌면 대화가 이상해져요

같은 자리에 넘기는 객체가 이렇게 달라져요.

{
  ordered: true, // 기본값
}

ordered: true는 기본값이라 순서가 그대로 지켜져요. 재전송 옵션은 아예 적지 않았는데, 비워 두면 무한 재전송이 되고 그것이 Reliable에 해당합니다.

이렇게 채널마다 신뢰성과 순서를 따로 정할 수 있어요.


채널 하나를 만들면서 전송 방식까지 정합니다

채널이 만들어지고 양쪽에서 쓸 수 있게 되기까지의 순서는 이래요.

Offer와 Answer가 오간 뒤에 SCTP Association이 서고, 그다음에 채널이 열려요. 양쪽이 똑같지 않은 것이 눈에 띌 텐데, 만든 쪽은 createDataChannel()이 돌려준 채널을 처음부터 들고 있고 받는 쪽은 ondatachannel이 와야 채널이 생깁니다. 채널을 얻는 방법만 다르고 열릴 때까지 기다리는 것은 양쪽이 같아요. 열린 뒤부터 send로 데이터가 오가고, 보낸 것은 상대 쪽에서 onmessage로 들어와요.

발신자가 만들고 수신자가 받습니다

데이터 채널은 한쪽에서 만들고 다른 쪽에서 받아요. 전화를 거는 사람과 받는 사람이 나뉘는 것과 같습니다.

먼저 채널을 만드는 쪽 코드예요.

const pc = new RTCPeerConnection();

// 채널 생성
const channel = pc.createDataChannel("chat");

// 연결되면 메시지 보낼 수 있음
channel.onopen = () => {
	console.log("채널 연결됨");
	channel.send("안녕하세요!");
};

// 메시지 받기
channel.onmessage = (event) => {
	console.log("받은 메시지:", event.data);
};

createDataChannel()로 채널을 만들고, 연결이 열리면 onopen에서 첫 메시지를 보내요.

받는 쪽은 코드가 이렇게 달라져요.

// 상대방이 만든 채널 받기
pc.ondatachannel = (event) => {
	const channel = event.channel;
	console.log("채널 수신됨");

	channel.onmessage = (event) => {
		console.log("받은 메시지:", event.data);
		// 받은 메시지에 답장
		channel.send("반가워요!");
	};
};

채널을 만드는 줄이 없고 ondatachannel 이벤트로 상대가 만든 채널을 넘겨받아요. 그래서 createDataChannel()을 호출하는 쪽이 Offer를 먼저 보내야 해요. Answer를 보내는 쪽은 ondatachannel로만 채널을 받을 수 있습니다.

옵션 세 개로 신뢰성과 순서를 정합니다

채널을 만들 때 전송 방식을 정할 수 있어요. 용도가 다른 채널 세 개를 나란히 만들어 보면 차이가 드러나요.

// 채팅용 - 모든 메시지가 순서대로 도착
const chatChannel = pc.createDataChannel("chat", {
	ordered: true // 순서 보장 (기본값)
});

// 게임용 - 빠른 전송, 손실 허용
const gameChannel = pc.createDataChannel("position", {
	ordered: false, // 순서 상관없음
	maxRetransmits: 0 // 재전송 안 함
});

// 파일 전송용 - 완전한 신뢰성
const fileChannel = pc.createDataChannel("file", {
	ordered: true
	// maxRetransmits와 maxPacketLifeTime 둘 다 없으면
	// 무한 재전송 = 완전한 신뢰성
});

셋 다 createDataChannel()로 만들고 채널 이름과 옵션 객체를 다르게 넘겨요. 채팅과 파일 전송은 ordered: true만 주고 재전송 옵션을 비워 완전한 신뢰성으로 두고, 게임만 순서와 재전송을 둘 다 포기합니다.

채널을 만들 때 줄 수 있는 옵션은 세 개예요.

옵션의미사용 예시
ordered순서 보장 여부채팅, 파일
maxRetransmits최대 재전송 횟수중요한 명령
maxPacketLifeTime패킷 수명 (밀리초)실시간 센서 데이터

재전송 옵션은 셋 중 하나를 골라요.

  • 둘 다 안 씀: 무한 재전송이라 완전한 신뢰성
  • maxRetransmits: 3: 3번까지만 재시도
  • maxPacketLifeTime: 1000: 1초 안에 못 가면 포기

maxRetransmitsmaxPacketLifeTime을 둘 다 넘기면 안 돼요. 스펙이 그렇게 정해 두어서 createDataChannel()TypeError를 일으킵니다. 재전송 횟수로 포기할지 시간으로 포기할지, 둘 중 하나만 고르라는 뜻이에요.


실제 앱 세 가지에 옵션을 걸어 봅니다

실제로 어떤 설정을 써야 할까요? 대표적인 사용 사례를 볼게요.

채팅은 순서와 신뢰성을 둘 다 켭니다

요구사항은 모든 메시지가 순서대로 도착해야 한다는 것이에요. 채널을 만들고 메시지를 주고받기까지 이어 보면 이렇게 돼요.

const chatChannel = pc.createDataChannel("chat", {
	ordered: true
	// 완전한 신뢰성 (기본값)
});

chatChannel.onopen = () => {
	// 연결되면 메시지 보낼 수 있음
	document.getElementById("sendBtn").disabled = false;
};

chatChannel.onmessage = (event) => {
	// 화면에 메시지 표시
	const msg = JSON.parse(event.data);
	addMessageToChat(msg.text, msg.sender);
};

// 메시지 보내기
function sendMessage(text) {
	const message = {
		text: text,
		sender: "me",
		timestamp: Date.now()
	};
	chatChannel.send(JSON.stringify(message));
}

ordered: true만 주고 재전송 옵션은 비웠어요. 메시지는 JSON.stringify()로 직렬화해 보내고, 받는 쪽에서 다시 파싱해 화면에 올려요.

이 설정을 고른 이유는 둘이에요. 대화가 뒤섞이면 안 되니 ordered: true를 주고, 메시지를 놓치면 안 되니 재전송 제한을 두지 않습니다.

게임 좌표는 순서와 신뢰성을 둘 다 끕니다

요구사항은 최신 위치만 중요하고 이전 데이터는 무시해도 된다는 것이에요. 좌표를 주기적으로 보내는 코드를 봐요.

const positionChannel = pc.createDataChannel("position", {
	ordered: false, // 순서 상관없음
	maxRetransmits: 0 // 재전송 안 함 = 빠른 전달
});

// 60fps로 위치 전송
setInterval(() => {
	// 채널이 닫혀 있으면 send가 예외를 일으킵니다
	if (positionChannel.readyState !== "open") return;
	// 버퍼가 밀려 있으면 이번 프레임은 건너뜁니다
	if (positionChannel.bufferedAmount > 64 * 1024) return;

	const position = {
		x: player.x,
		y: player.y,
		timestamp: Date.now()
	};

	positionChannel.send(JSON.stringify(position));
}, 16); // 약 60fps

positionChannel.onmessage = (event) => {
	const pos = JSON.parse(event.data);
	updateRemotePlayer(pos.x, pos.y);
};

setInterval이 16밀리초마다 좌표를 JSON으로 보내고, 받는 쪽은 그 값으로 상대 플레이어의 위치를 갱신해요.

보내기 전에 두 가지를 확인하는 이유가 있습니다. 채널이 닫힌 뒤에도 타이머는 계속 돌기 때문에 확인 없이 두면 초당 60번씩 예외가 나요. 버퍼를 보는 것은 좌표가 밀리기 시작하면 지난 위치를 계속 쌓아 보내게 되기 때문입니다. 어차피 최신 위치만 쓸 값이라 건너뛰는 편이 낫습니다.

이 설정을 고른 이유도 둘입니다. 0.5초 전 위치는 의미가 없으니 ordered: false를 주고, 재전송하는 동안 새 위치가 또 오니 maxRetransmits: 0으로 둬요.

파일 전송은 조각 순서가 어긋나면 깨집니다

요구사항은 모든 데이터 조각이 정확히 도착해야 한다는 것이에요. 파일을 잘라서 보내는 코드는 이렇게 돼요.

const fileChannel = pc.createDataChannel("file", {
	ordered: true
	// 완전한 신뢰성
});

async function sendFile(file) {
	// 1. 파일 정보 먼저 보내기
	fileChannel.send(
		JSON.stringify({
			type: "file-start",
			name: file.name,
			size: file.size
		})
	);

	// 2. 16KB씩 잘라서 보내기
	const CHUNK_SIZE = 16384;
	const buffer = await file.arrayBuffer();

	for (let i = 0; i < buffer.byteLength; i += CHUNK_SIZE) {
		const chunk = buffer.slice(i, i + CHUNK_SIZE);
		fileChannel.send(chunk);

		// 진행률 표시
		const progress = ((i + CHUNK_SIZE) / buffer.byteLength) * 100;
		console.log(`전송 중: ${Math.min(progress, 100).toFixed(1)}%`);
	}

	// 3. 전송 완료 신호
	fileChannel.send(JSON.stringify({ type: "file-end" }));
}

파일 정보를 먼저 보내고, 16KB씩 잘라 순서대로 보낸 다음, 마지막에 완료 신호를 보내요. 조각을 하나 보낼 때마다 진행률을 계산해서 로그로 찍습니다.

받는 쪽은 조각을 모았다가 완료 신호가 오면 하나로 합쳐요.

let incoming = null;

fileChannel.onmessage = (event) => {
	// 제어 메시지는 문자열로, 조각은 ArrayBuffer로 옵니다
	if (typeof event.data === "string") {
		const message = JSON.parse(event.data);

		if (message.type === "file-start") {
			incoming = { name: message.name, size: message.size, chunks: [] };
		}

		if (message.type === "file-end") {
			const blob = new Blob(incoming.chunks);
			downloadFile(blob, incoming.name);
			incoming = null;
		}
		return;
	}

	incoming?.chunks.push(event.data);
};

제어 메시지를 문자열로 보내고 조각을 바이너리로 보낸 덕분에 typeof로 한 번만 갈라도 둘이 구분돼요. ordered: true로 열어 둔 채널이라 조각이 보낸 순서 그대로 도착하니, 받은 차례대로 배열에 넣기만 하면 됩니다.

이 설정을 고른 이유는 둘 다 파일이 깨지기 때문이에요. 조각 순서가 바뀌어도 깨지고 하나라도 빠져도 깨집니다.


데이터 타입은 보낼 때와 받을 때 다르게 다룹니다

문자열과 JSON, 바이너리, Blob을 보냅니다

데이터 채널은 여러 종류의 데이터를 보낼 수 있어요. 차례로 보내면 이렇습니다.

// 1. 문자열
channel.send("안녕하세요!");

// 2. JSON 데이터
const data = { type: "move", x: 100, y: 200 };
channel.send(JSON.stringify(data));

// 3. 바이너리 데이터 (ArrayBuffer)
const buffer = new ArrayBuffer(8);
channel.send(buffer);

// 4. 파일은 ArrayBuffer로 바꿔서
const file = fileInput.files[0];
channel.send(await file.arrayBuffer());

send()에 문자열과 JSON 문자열, ArrayBuffer를 차례로 넘기고 있어요. 스펙에는 Blob도 보낼 수 있다고 적혀 있지만 브라우저 지원이 갈려서 실무에서는 쓰지 않습니다. 파일은 await file.arrayBuffer()로 바꿔서 넘겨요.

받을 때는 typeof와 instanceof로 가릅니다

보내는 쪽이 여러 종류를 쓰니 받는 쪽은 타입을 갈라야 해요. 타입을 가르는 코드는 이렇게 돼요.

channel.onmessage = (event) => {
	const data = event.data;

	// 문자열인지 확인
	if (typeof data === "string") {
		console.log("텍스트:", data);

		// JSON인지 확인
		try {
			const json = JSON.parse(data);
			console.log("JSON:", json);
		} catch (e) {
			// 일반 문자열
		}
	}
	// 나머지는 전부 ArrayBuffer로 옵니다
	else if (data instanceof ArrayBuffer) {
		console.log("바이너리 데이터:", data.byteLength, "bytes");
	}
};

문자열이면 typeof로 걸러 낸 다음 JSON 파싱을 한 번 시도해요. 파싱에 실패하면 일반 문자열로 둡니다.

Blob 분기를 따로 만들 필요는 없어요. channel.binaryType의 기본값이 arraybuffer라서 바이너리는 언제나 ArrayBuffer로 오거든요.


버퍼와 채널 상태, 메시지 크기에서 막힙니다

너무 빨리 보내면 버퍼가 가득 찹니다

데이터를 너무 빨리 많이 보내면 버퍼가 가득 차요. 버퍼를 확인하지 않는 코드와 확인하는 코드를 나란히 놓고 봐요.

// 나쁜 예: 버퍼 확인 없이 계속 보내기
for (let i = 0; i < 10000; i++) {
	channel.send(`메시지 ${i}`); // 버퍼 초과 가능!
}

// 좋은 예: 버퍼 확인하면서 보내기
function safeSend(data) {
	// 버퍼가 1MB 미만일 때만 보내기
	if (channel.bufferedAmount < 1024 * 1024) {
		channel.send(data);
		return true;
	}

	console.warn("버퍼 가득 참, 잠시 대기");
	return false;
}

// 버퍼 여유 생기면 알림
channel.bufferedAmountLowThreshold = 65536; // 64KB
channel.onbufferedamountlow = () => {
	console.log("버퍼 여유 생김!");
};

safeSend()bufferedAmount가 1MB 미만일 때만 보내고, 넘으면 경고만 남기고 false를 돌려줘요. 버퍼에 여유가 생기면 onbufferedamountlow가 알려줘요.

보내기 전에 readyState를 확인합니다

보내기 전에 채널이 열렸는지 확인하는 코드예요.

// 메시지 보내기 전에 항상 확인
if (channel.readyState === "open") {
	channel.send("Hello!");
} else {
	console.log("채널이 아직 열리지 않았습니다");
}

// readyState 값
// "connecting" - 연결 중
// "open" - 사용 가능
// "closing" - 닫히는 중
// "closed" - 닫힘

readyState"open"일 때만 보내고, 아니면 아직 열리지 않았다고 로그를 남겨요. 상태 값은 connecting과 open, closing, closed 네 가지입니다.

큰 메시지는 채널을 막아서 잘라 보냅니다

요즘 브라우저는 한 메시지에 최소 256KB를 받아요. 실제 상한은 pc.sctp.maxMessageSize로 읽을 수 있어요.

// pc.sctp는 SCTP 전송이 협상된 뒤에 채워집니다.
// 데이터 채널을 만들고 offer와 answer를 주고받기 전에는 null이에요.
console.log(pc.sctp?.maxMessageSize);

// 큰 파일은 잘라서 보내기
const CHUNK_SIZE = 16384; // 16KB

async function sendLargeData(file) {
	const buffer = await file.arrayBuffer();

	for (let i = 0; i < buffer.byteLength; i += CHUNK_SIZE) {
		const chunk = buffer.slice(i, i + CHUNK_SIZE);

		// 버퍼 확인하면서 보내기
		while (channel.bufferedAmount > CHUNK_SIZE * 2) {
			await new Promise((resolve) => setTimeout(resolve, 10));
		}

		channel.send(chunk);
	}
}

sendLargeData()는 파일을 16KB씩 자르고, 버퍼가 청크 두 개 분량을 넘으면 줄어들 때까지 기다렸다가 보내요. 앞의 safeSend()가 버퍼가 찼을 때 보내기를 건너뛰었다면, 이쪽은 버퍼가 줄어들 때까지 기다린 다음 반드시 보냅니다.

상한이 256KB인데도 16KB로 자르는 것은 크기 때문이 아니에요. 큰 메시지 하나를 보내는 동안 같은 채널의 다른 메시지가 뒤에서 기다려야 하고, bufferedAmount로 흐름을 조절하기도 어려워집니다. 잘게 자르면 진행률도 촘촘하게 보여줄 수 있어요.


모던 브라우저는 모두 지원하고 iOS만 제약이 있습니다

RTCDataChannel은 지금 쓰이는 브라우저에서 모두 지원해요. 다만 iOS는 백그라운드/저전력 정책의 영향을 받아 연결 유지가 제한될 수 있어요.

갈리는 것지금 상황코드에서 할 일
한 메시지 크기최소 256KB, Firefox는 그보다 훨씬 큼pc.sctp.maxMessageSize보다 작게 자릅니다
바이너리 수신 타입기본값이 arraybufferBlob 분기를 만들지 않습니다
iOS 백그라운드탭이 내려가면 연결이 끊길 수 있음다시 앞으로 왔을 때 readyState를 보고 재연결합니다

마무리

다룬 것을 한 번에 다시 세워보면 이렇게 돼요.

  • 데이터 채널은 브라우저끼리 직접 데이터를 주고받는 통로
  • SCTP 프로토콜로 신뢰성과 순서를 유연하게 제어
  • 상황에 맞게 네 가지 모드 조합 가능
    • Reliable/Unreliable (신뢰성)
    • Ordered/Unordered (순서)
시나리오별 설정
용도설정
채팅ordered: true (완전한 신뢰성)
파일 전송ordered: true (완전한 신뢰성)
게임 좌표ordered: false, maxRetransmits: 0
센서 데이터ordered: false, maxPacketLifeTime: 1000
주의사항
  • 버퍼 확인하면서 데이터 보내기
  • 채널 상태 확인 (readyState)
  • 큰 파일은 16KB씩 잘라서 전송
WebRTC 전체 그림

시리즈 전체로 넓히면 네 편이 이렇게 이어집니다.

  1. 연결 구조: 시그널링, SDP, ICE, STUN/TURN
  2. 미디어: 카메라, 마이크, 화면 공유
  3. 제어: PeerConnection, 이벤트, 품질 관리
  4. 데이터: 채팅, 파일, 게임 등 모든 데이터 전송

WebRTC는 영상과 음성, 데이터를 모두 P2P로 보낼 수 있는 플랫폼이에요. 화상 통화만이 아니라 실시간 협업과 파일 공유, 게임, IoT에도 씁니다.

다만 P2P가 늘 되는 것은 아니에요. TURN을 거치는 순간 서버 비용과 지연이 다시 붙고, 참가자가 늘면 1편에서 본 SFU나 MCU로 옮겨 가야 합니다. 어디까지 P2P로 버틸지는 서비스마다 다르게 정해야 하는 문제로 남아 있어요.

WebRTC 박살내기 시리즈는 여기서 마무리하겠습니다.

참고자료

댓글

스크롤하면 댓글이 로드됩니다.