WebRTC 코드 정리

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

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다