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입니다.