WebRTC 코드 정리

WebRTC는 P2P이며 웹에서 1:1로 연결하여 서로 데이터를 실시간으로 주고 받는 웹 표준기술입니다. 추후 적용을 위해 관련 코드를 정리합니다.

먼저 개념도는 다음과 같습니다. (아래 그림은 WebRTC에 대한 이해를 돕기 위해 제가 작성한 것으로 잘못 표기된 부분이 있을 수 있습니다.)

일단 쭉 말로 설명하면.. 먼저 데이터를 주고 받을 채널을 생성하고 채널을 생성한 Peer가 제안(Offer) 객체를 만들어 다른 Peer에게 전달합니다. 그럼 피어는 제안과 함께 받은 채널을 확인하고 응답(Answer) 객체를 생성해 전달합니다. 그러면 이 두 Peer들은 icecandidate라는 이벤트가 발생하게되고 Candidate 객체를 서로 주고 받아 설정함으로써 두 Peer 간의 연결이 이루어 집니다. 이렇게 연결이 이루어지면 앞서 언급한 채널을 통해 메세지를 실시간으로 주고 받을 수 있습니다. 위의 그림을 보면 Peer간의 연결을 위해 Offer, Answer, Candidate 정보를 교환하기 위해 별도의 서버가 필요한 것을 볼 수 있습니다. 하지만 WebRTC의 핵심은 서버없이 Peer 간의 데이터 전송이므로 일단 연결이 되면 더 이상 서버는 필요치 않습니다.

웹 표준 기술이 지향하는 매우 심플한 API 구조에 맞게 코드가 단순한데요. 서버, 클라이언트 코드가 매우 짧으므로 전체 코드를 남깁니다. 먼저 클라이언트에 대한 코드는 다음과 같습니다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
});
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); });
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);
});

서버단의 코드는 다음과 같습니다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
})
});
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); }) });
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입니다.

three.js로 체스, 볼링 게임 웹 만들기

three.js를 이용해 웹에서 어떠한 플러그인 없이도 멋진 3차원 그래픽 웹페이지를 개발할 수 있는데요. 게임 느낌의 3D 웹 개발에 대한 컨텐츠입니다. 내용이 다소 길지만 차근 차근 따라하시다보면 금새 완성하실 수 있습니다.