WebRTC(Web Real-Time Communication)
- WebRTC(Web Real-Time Communication)
- Peer to Peer간 실시간 커뮤니케이션을 제공해주는 기술이다.
- 별다른 소프트웨어 없이 다른 사용자와 실시간 채팅, 화상회의, 파일 공유 등의 서비스를 제공할 수 있다.
- Latency가 짧고, P2P이기 때문에 서버에 부하가 줄어든다.
- P2P를 위해 Signaling이란 과정을 거쳐야 한다.
- 오래된 브라우저의 경우 제대로 지원이 안될수 있고, STUN/TRUN 서버가 필요하다는 단점이 있다.
- STUN/TRUN 서버란?
- NAT 및 방화벽을 우회하여 P2P 연결을 설정하는데 사용되는 서버이다.
- WebRTC의 경우 ICE 프레임워크를 사용하여 P2P 연결을 설정하는데, 가능한 모든 연결 경로(candidates)를 수집하고 그 중 가장 적합한 경로를 선택한다.
→ 클라이언트는 STUN 서버에 요청을 보내 자신의 공용 IP 주소와 포트를 확인하고, 이 정보를 ICE Candidate로 사용하여 연결을 시도한다.
- 통신 API
- MediaStream API
- 클라이언트의 미디어 기기에 접근하기 위한 API
- 클라이언트 카메라/오디오에 접근하는 예시 코드
const openCamera = async () => {
const constraints = {
video: true,
audio: true,
};
navigator.mediaDevices.getUserMedia(constraints).then((stream) =>{
userVideo.current.srcObject = stream
userStream.current = stream
})
};
- 연결을 초기화하고 Peer을 연결하여 미디어스트림을 연결한다.
- 신뢰할 수 없는 네트워크에서도 실시간 통신을 가능하게 한다.
const createPeer = () => {
console.log("Creating Peer Connection");
const peer = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
// 여기서 iceServers는 STUN 서버로, 무료로 사용할 수 있는 구글 STUN 서버를 사용한다.
});
// Event
peer.onnegotiationneeded = handleNegotiationNeeded;
peer.onicecandidate = handleIceCandidateEvent;
peer.ontrack = handleTrackEvent;
return peer;
};
- RTCPeerConnection 객체에서 오는 채널을 생성한다.
var peerConn = new RTCPeerConnection();
//establishing peer connection
//...
//end of establishing peer connection
var dataChannel = peerConnection.createDataChannel("myChannel", dataChannelOptions);
// here we can start sending direct messages to another peer
- Signaling 흐름
- RTCPeerConnection를 통해 Peer 생성
- 각각의 Peer들은 Offer 객체를 생성한다. offer은 세션의 정보를 SDP 포맷으로 가지고 있다. 생성한 offer 정보를 SDP description을 포함하는 메세지로 만들고, webSocket을 통해 상대 클라이언트에게 전달한다.
- SDP를 교환한 Peer들은 ICE candidate들을 교환한다. candidate들이 호환된다면, 미디어는 통신을 시작한다(getUserMedia).
- SDP answer를 생성하여 상대방에게 전송한 후, Track를 등록/수신하여 video 데이터를 주고받는다.
const peer = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});
peer.onnegotiationneeded = handleNegotiationNeeded;
peer.onicecandidate = handleIceCandidateEvent;
peer.ontrack = handleTrackEvent;
const offer = await peerRef.current.createOffer();
await peerRef.current.setLocalDescription(offer);
sendMessage(webSocketRef.current, { offer: peerRef.current.localDescription });
export const addIceCandidate = async (peer, candidate) => {
try {
await peer.addIceCandidate(candidate);
} catch (err) {
console.error("Error adding ICE Candidate", err);
}
};
...
if (message.iceCandidate && peerRef.current) {
console.log("iceCandidate", message.iceCandidate)
await addIceCandidate(peerRef.current, message.iceCandidate);
}
// track 발신
userStream.getTracks().forEach(track => {
peer.addTrack(track, userStream);
});
const answer = await peer.createAnswer();
await peer.setLocalDescription(answer);
sendMessage({ answer: peer.localDescription });
// track 수신
const handleTrackEvent = (e) => {
console.log("Track Event")
partnerVideo.current.srcObject = e.streams[0];
};

ref by
Share article