WebRTC는 P2P이며 웹에서 1:1로 연결하여 서로 데이터를 실시간으로 주고 받는 웹 표준기술입니다. 추후 적용을 위해 관련 코드를 정리합니다.
먼저 개념도는 다음과 같습니다. (아래 그림은 WebRTC에 대한 이해를 돕기 위해 제가 작성한 것으로 잘못 표기된 부분이 있을 수 있습니다.)
일단 쭉 말로 설명하면.. 먼저 데이터를 주고 받을 채널을 생성하고 채널을 생성한 Peer가 제안(Offer) 객체를 만들어 다른 Peer에게 전달합니다. 그럼 피어는 제안과 함께 받은 채널을 확인하고 응답(Answer) 객체를 생성해 전달합니다. 그러면 이 두 Peer들은 icecandidate라는 이벤트가 발생하게되고 Candidate 객체를 서로 주고 받아 설정함으로써 두 Peer 간의 연결이 이루어 집니다. 이렇게 연결이 이루어지면 앞서 언급한 채널을 통해 메세지를 실시간으로 주고 받을 수 있습니다. 위의 그림을 보면 Peer간의 연결을 위해 Offer, Answer, Candidate 정보를 교환하기 위해 별도의 서버가 필요한 것을 볼 수 있습니다. 하지만 WebRTC의 핵심은 서버없이 Peer 간의 데이터 전송이므로 일단 연결이 되면 더 이상 서버는 필요치 않습니다.
웹 표준 기술이 지향하는 매우 심플한 API 구조에 맞게 코드가 단순한데요. 서버, 클라이언트 코드가 매우 짧으므로 전체 코드를 남깁니다. 먼저 클라이언트에 대한 코드는 다음과 같습니다.
const socket = io(); const roomName = "room1"; let peerConnection; let dataChannel; document.querySelector("form").addEventListener("submit", (event) => { event.preventDefault(); if(dataChannel) { const value = document.querySelector("form input").value dataChannel.send(value); } }); function onReady() { peerConnection = new RTCPeerConnection({ iceServers: [{ urls: [ "stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302", "stun:stun2.l.google.com:19302", "stun:stun3.l.google.com:19302", "stun:stun4.l.google.com:19302", ] }]}); peerConnection.addEventListener("icecandidate", (event) => { socket.emit("ice", event.candidate, roomName); }); } window.onload = () => { socket.emit("join", roomName, onReady); } function onMessage(msg) { const ul = document.querySelector("ul"); const li = document.createElement("li"); li.innerText = msg; ul.append(li); } socket.on("welcome", async () => { dataChannel = peerConnection.createDataChannel("chat"); dataChannel.addEventListener("message", (event) => { onMessage(event.data); }); const offer = await peerConnection.createOffer(); peerConnection.setLocalDescription(offer); socket.emit("offer", offer, roomName); }); socket.on("offer", async (offer) => { peerConnection.addEventListener("datachannel", (event) => { dataChannel = event.channel; dataChannel.addEventListener("message", (event)=> { onMessage(event.data); }); }) peerConnection.setRemoteDescription(offer); const answer = await peerConnection.createAnswer(); peerConnection.setLocalDescription(answer); socket.emit("answer", answer, roomName); }); socket.on("answer", (answer) => { peerConnection.setRemoteDescription(answer); }); socket.on("ice", (ice) => { peerConnection.addIceCandidate(ice); });
서버단의 코드는 다음과 같습니다.
import http from "http" import express from "express"; import SocketIO from "socket.io"; const app = express(); app.set("view engine", "pug"); app.set("views", __dirname + "/views"); app.use("/public", express.static(__dirname + "/public")); app.get("/", (_, res) => res.render("home")); const httpServer = http.createServer(app); const wsServer = SocketIO(httpServer); const port = 3000; const handleListen = () => console.log(`Listening on http://localhost:${port}`) httpServer.listen(port, handleListen); wsServer.on("connection", socket => { socket.on("join", (roomName, done) => { socket.join(roomName); done(); socket.to(roomName).emit("welcome"); }); socket.on("offer", (offer, roomName) => { socket.to(roomName).emit("offer", offer); }); socket.on("answer", (answer, roomName) => { socket.to(roomName).emit("answer", answer); }); socket.on("ice", (ice, roomName) => { socket.to(roomName).emit("ice", ice); }) });
이용한 기술은 JS, Node.JS, Express, Socket.IO, Pug입니다.