SocketIO 프레임워크로 chat 기능 고도화하기

Ryan Ham·2024년 12월 8일
1

Unreal Engine

목록 보기
30/31
post-thumbnail

SocketIO 프레임워크란?

socketIO는 실시간 통신을 아주 쉽게 사용할 수 있도록 만들어진 JS 기반 프레임 워크이다. 기본적으로 webSocket을 사용하지만 방화벽이나 통신 상태에 따라 HTTP의 polling 기능을 포함하여 다양한 방법으로 실시간 통신을 가능하게 한다.

SocketIO는 바닐라 webSocket에 비해 엄청나게 많은 유틸들을 제공해주는데 가장 확실하게 체감되었던 몇 가지 부분을 소개해보면 다음과 같다.

첫 번째로, 이벤트를 보내고 받는 것이 매우 쉬워졌고 확장성이 증가되었다. SocketIO도 바닐라 webSocket과 같이 client<->server에서 서로 메시지를 주고 받는 4가지 경로가 있는 점을 동일하지만, SocketIO에서는 "socket.emit"에서 즉석으로 이벤트를 정의할 수 있고 이를 수신하는 쪽에서는 단순히 해당 이벤트를 "socket.on"으로 받으면 된다. 또한, 클라이언트에서 "socket.emit"의 인자로 콜백함수를 서버에게 넘겨줄수도 있는데, 서버는 이를 받으면 연결된 클라이언트들에게 이 콜백함수를 실행시킨다.

두 번째로, room이라는 개념을 지원한다. 이 "room"은 우리가 생각하는 채팅방과 동일한 기능을 한다. SocketIO는 이 room과 관련된 다양한 함수들을 지원주어서 해당 room에 어떤 client들이 있는지 출력, 특정 client들을 room으로 보내기/퇴장기능, server가 특정 room에게 메시지 보내기 등이 손쉽게 가능해졌다.

세 번째로, 통합 관리 시스템인 admin panel을 제공해준다. 이를 통해, 미리 준비된 UI로 현재 서버에 접속한 client들, byte 전송상태, 트리거된 이벤트들을 파악 가능하다.

SocketIO 서버 코드

// server.js
import express from "express";
import http from "http";
import {Server} from "socket.io";
import {instrument} from "@socket.io/admin-ui";

const app = express();

// http 스타일의 req, res style
app.set("view engine","pug");
app.set("views", __dirname + "/views");
app.use("/public", express.static(__dirname + "/public"));


app.get("/", (req,res) =>res.render("home"));
app.get("/*", (req,res) =>res.redirect("/"));

const handleListen = () => console.log(`Listening on http://localhost:3000`);
const httpServer = http.createServer(app);
const wsServer = new Server(httpServer, {
    cors : {
        origin : ["https://admin.socket.io"],
        credentials : true
}});

instrument(wsServer, {
    auth : false,
});


function publicRooms(){
    const {
        sockets : {
            adapter : {sids, rooms},
        }
    } = wsServer;

    const publicRooms = [];
    rooms.forEach((_, key)=>{
        if(sids.get(key) === undefined){
            publicRooms.push(key);
        }
    })
    return publicRooms;
}

function countRoom(roomname){
    return wsServer.sockets.adapter.rooms.get(roomname)?.size;
}

wsServer.on("connection", socket=>{
    socket["nickname"] = "Anonymous"
    // app.js에서 만든 "enter_room" 이벤트에 대해 반응
    socket.on("enter_room", (roomName, callback)=> {
        // console.log(roomName);
        // join을 통해 roomName 이름의 room에 입장
        socket.join(roomName);
        // socket.to => 특정 room에 msg 보내기
        // socket.leave => 특정 room 나가기
        // socket.id => client socket의 고유 id를 알 수 있음
        // socketsJoin => 특정 client socket을 강제로 특정 room에 join시킬 수 있음. 

        callback();

        socket.to(roomName).emit("welcome", socket.nickname, countRoom(roomName));
        wsServer.sockets.emit("room_change", publicRooms());
        
    });

    socket.on("disconnecting", ()=>{
        socket.rooms.forEach(room => {
            socket.to(room).emit("bye", socket.nickname, countRoom(room)-1 )
        });
    })

    socket.on("new_message", (msg, roomName, callback)=>{
        socket.to(roomName).emit("new_message", `${socket.nickname} : ${msg}`)
        callback()
    })
    socket.on("nickname", nickname =>socket["nickname"] = nickname);    
});


httpServer.listen(3000, handleListen);

SocketIO 클라이언트 코드

// app.js
const socket = io();

// socketIO에서는 기본적으로 연결되어 있는 client를 저장하는 map구조가 존재한다. 

const welcome = document.getElementById("welcome")
const form = document.querySelector("#welcome form");
const room = document.getElementById("room")
room.hidden = true;

let roomName;

function addMessage(message){
    const ul = room.querySelector("ul");
    const li = document.createElement("li")
    li.innerText = message
    ul.appendChild(li);
}

function handleMessageSubmit(event){
    event.preventDefault();
    const input = room.querySelector("#msg input");
    const msgvalue = input.value;
    socket.emit("new_message", input.value, roomName, ()=>{
        addMessage(`You ${msgvalue}`)
    })   
    input.value = ""; 
}

function handleNicknameSubmit(event){
    event.preventDefault();
    const input = room.querySelector("#name input");
    socket.emit("nickname", input.value);
}

function handleRoomSubmit(event){
    event.preventDefault();
    const input = form.querySelector("input");
    
    // emit 1번째 인자 : 즉석에서 "enter_room"이라는 event를 바로 만들어 버릴 수 있다. 
    // emit 2번째 인자 : socketIO에서는 전송할때 꼭 objecy를 string으로 바꾸지 않고 emit 함수를 통해 바로 object를 보낼 수 있다. 
    // emit 3번째 인자 : callback 함수를 보낼 수 있다. 헷갈릴 수 있지만, 이 함수는 server 쪽에서 실행되는 것이 아닌 server와 연결된 client에서 실행되는 것. server쪽에서는 이 callback 함수를 받아서 client 쪽에서 어떻게 이 함수를 실행하는지 설정할 수 있다.
    socket.emit("enter_room", input.value, ()=>{
        console.log("server is done!");
        welcome.hidden = true;
        room.hidden = false;    
        const h3 = room.querySelector("h3");
        h3.innerText = `Room ${roomName}`;
        const msgform = room.querySelector("#msg")
        const nameform = room.querySelector("#name")
        msgform.addEventListener("submit", handleMessageSubmit)
        nameform.addEventListener("submit", handleNicknameSubmit)
    });
    roomName = input.value;
    input.value = "";
}

form.addEventListener("submit", handleRoomSubmit);

socket.on("welcome", (user, newCount)=>{
    const h3 = room.querySelector("h3");
    h3.innerText = `Room ${roomName} (${newCount})`;    
    addMessage(`${user} has joined!`)
})

socket.on("bye", (user, newCount)=>{ 
    const h3 = room.querySelector("h3");
    h3.innerText = `Room ${roomName} (${newCount})`;             
    addMessage(`${user} has left!`)
})

socket.on("new_message", (msg)=>{
    addMessage(msg)
})

socket.on("room_change", (rooms)=>{
    if(rooms.length === 0){
        roomList.innerHTML = "";
        return;
    }
    const roomList = welcome.querySelector("ul")
    rooms.forEach(room => {
        const li = document.createElement("li");
        li.innerText = room;
        roomList.append(li);
    });
})

socketIO를 사용한 코드에서는 room의 유틸을 사용해 실제 채팅방에 접속해서 사용자들이 실시간 통신을 할 수 있고, 앞에 닉네임을 prefix로 붙여서 누가 무슨말을 했는지 시각적으로 보이는 기능을 추가했다.

2명의 클라이언트가 같은 채팅방에 입장 후 채팅하는 모습


소스 코드

https://github.com/jerryhtw/webRTC_test/tree/socketIO


Reference

socketIO
https://socket.io/

노마드코더
https://www.youtube.com/@nomadcoders

profile
🏦KAIST EE | 🏦SNU AI(빅데이터 핀테크 전문가 과정) | 📙CryptoHipsters 저자

0개의 댓글