본문 바로가기
Develop/Zoom만들기

ZOOM 만들기 8. socket.io 이용하여 채팅룸 구현(1)

by 보보트레인 2023. 8. 31.

socket.io란? 

실시간, 양방향, 이벤트에 기반한 통신 등을 모두 지원하는 라이브러리다.

웹소켓과 유사하지만 엄연히 다른, '웹소켓을 활용하는 라이브러리' 라고 이해하면 좋다.

 

1. socket.io 적용 준비하기

 

1-1) server.js 코드 수정

- ws와 관련된 코드 모두 삭제

import http from 'http';
import express from 'express';



const app = express();

app.set("view engine", "pug");
//dirname은 Node.js기본 전역변수로, 현재 실행되는 폴더의 경로를 의미
app.set("views", __dirname + "/views");
app.use("/public", express.static(__dirname + "/public"));

// '/'를 받으면 home으로 가게 설정
app.get("/", (req, res) => res.render("home"));
//만약홈이 아닌 다른 주소로 get요청을 보내더라도 홈으로 리다이렉션하게 예외처리함
app.get("/*", (req, res) => res.redirect("/"));


const server = http.createServer(app);


const handleListen = () => console.log("Listening on http://localhost:3000");
server.listen(3000,handleListen);

1-2) socket.io 다운로드

1-3) socket.io에 맞게 server.js코드 재수정

import http from 'http';
import SocketIO from "socket.io";
import express from 'express';



const app = express();

app.set("view engine", "pug");
//dirname은 Node.js기본 전역변수로, 현재 실행되는 폴더의 경로를 의미
app.set("views", __dirname + "/views");
app.use("/public", express.static(__dirname + "/public"));

// '/'를 받으면 home으로 가게 설정
app.get("/", (req, res) => res.render("home"));
//만약홈이 아닌 다른 주소로 get요청을 보내더라도 홈으로 리다이렉션하게 예외처리함
app.get("/*", (req, res) => res.redirect("/"));

const httpServer = http.createServer(app);
const wsServer = SocketIO(httpServer)l

const handleListen = () => console.log("Listening on http://localhost:3000");
httpServer.listen(3000,handleListen);

설명 : socket.io를 이용해 웹소켓 서버를 만드는 방식은 기존ws 방식과 유사.

HTTP 서버와 웹소켓 서버를 명확히 구분하기 위해 이름을 각각 httpServer, wsServer로 정했음

wsServer를 만들기 위해 우리는 SocketIO에 httpServer를 넘겨주고 있음.

 

<실행결과>

잘 실행되어 콘솔로그("Listening on http://localhost:3000") 역시 잘 출력됨을 확인할 수 있다.


2. socket.io 적용하기

사용자가 채팅에 참여하고 싶다면 먼저 채팅룸부터 만들고 그 안에서 메시지를 교환할 수 있게 할 예정.

 

2-1) home.pug수정하기

home.pug에서 socket.io 설치 작업도 진행될 예정.

doctype html

html(lang="en")
    head
        meta(charset="UTF-8")
        meta(http-equiv="X-UA-Compatible", content="IE=edge")
        meta(name="viewport", content="width=device-width, initial-scale=1.0")
        title Zoom
        //MVP.css는 우리가 태그에 class나 id같은 특성을 추가하지 않아도 자동으로 스타일을 적용해주는 라이브러리다.
        link(rel="stylesheet", href="https://unpkg.com/mvp.css")
    body
        //h1 It works!
        header
            h1 Zoom
        main
       
        script(src="/socket.io/socket.io.js")
        script(src="/public/js/app.js")

설명:        script(src="/socket.io/socket.io.js"

이 코드가 가능한 이유는 socket.io가 단순히 설치하여 서버를 만들어주기만 하여도 우리에게 url을 제공해주기 때문.

브라우저를 열고 주소 창에 http://localhost:3000/socket.io/socket.io.js를 입력하면

다음과 같이 socket.io의 소스 코드를 제공하고 있는 url임을 확인할 수 있음.

이렇게 소스코드를 제공받음으로써, 사용자는 socket.io가 제공하는 기능을 브라우저에 적용할 수 있게 됨.

 

2-2) app.js 수정하기

여태껏 코드에서 new WebSocket(`ws://${window.location.host}`)가 바로 서버와 연결을 시도하는 부분이였는데,

이제는 socket.io를 이용하기 때문에 io라는 함수로 대체하게 됨.

 

코드를 전부 지우고 다음의 코드로 대체

const socket = io();

io()는 알아서 서버를 찾고 기능을 제공하는 꿀같은 명령어임!

 

2-3) server.js 수정하기

서버 쪽에서 연결(connection)이벤트 핸들러를 만들어서 연결을 확인해보려고 함.

wsServer.on("connection", (socket) => {
    console.log(socket);
});

다음의 코드를 추가하고 브라우저에 접속해 보자.

 

<터미널(콘솔) 화면>

socket에 대한 방대한 내용이 잘 출력됨을 확인가능하다.

2-4) home.pug 수정하기

채팅룸에 접속할 때 채팅룸의 이름을 입력할 폼부터 만들어 보자.

  • 이미 존재하는 이름 - 거기에 참가하는 것
  • 새로운 이름 - 채팅룸이 생성되면서 첫 번째 참가자가 되는 것
doctype html

html(lang="en")
    head
        meta(charset="UTF-8")
        meta(http-equiv="X-UA-Compatible", content="IE=edge")
        meta(name="viewport", content="width=device-width, initial-scale=1.0")
        title Zoom
        //MVP.css는 우리가 태그에 class나 id같은 특성을 추가하지 않아도 자동으로 스타일을 적용해주는 라이브러리다.
        link(rel="stylesheet", href="https://unpkg.com/mvp.css")
    body
        //h1 It works!
        header
            h1 Zoom
        main
            div#welcome
                form
                    input(placeholder="room name", required, type="text")
                    button Enter Room
        script(src="/socket.io/socket.io.js")
        script(src="/public/js/app.js")


2-5) app.js 코드 수정

const socket = io();

const welcome = document.getElementById("welcome");
const form = welcome.querySelector("form");

function handleRoomSubmit(event){
    event.preventDefault();
    const input = form.querySelector("input");
    socket.emit("enter_room", { payload: input.value });
    input.value = "";
}

form.addEventListener("submit", handleRoomSubmit);

설명 : submit 발생시 handleRoomSubmit 함수가 실행됨.

 

socket.emit 메서드는 이벤트를 발생시키는 역할을 함. (emit = 발생시키다)

 

socket.emit에 해당하는 이벤트를 설명하자면

enter_room이라는 이름으로 이벤트를 발생시켜 실제값(payload)으로 input값을 담아서 보낸다는 뜻임.

여기서 실제값 = 방이름을 의미한다.

 

2-6) 이벤트 핸들링 테스트하기

server.js에서 app.js에서 보낸 enter_room에 해당하는 이벤트를 받아서 처리해야함

 

wsServer에 해당하는 코드를 다음과 같이 고침

wsServer.on("connection", (socket) => {
    socket.on("enter_room", (roomName) => console.log(roomName));
});

enter_room 이라는 이름을 가진 이벤트를 받아 로그로 찍어봄.

 

<결과화면>

여기서 Enter Room을 누르면 

값이 잘 전달됨을 알 수 있다.

 

2-7) app.js 수정하기

 

서버 쪽에서 실행할 수 있는 콜백 함수를 socket.emit으로 대체할 수 있음.

다음과 같이 코드 수정

const socket = io();

const welcome = document.getElementById("welcome");
const form = welcome.querySelector("form");

function handleRoomSubmit(event){
    event.preventDefault();
    const input = form.querySelector("input");
    socket.emit("enter_room", input.value, () => {
        console.log("server is done!");
    });
    input.value = "";
}

form.addEventListener("submit", handleRoomSubmit);

설명 : 첫 번째 인자로는 이벤트명, 두 번째 인자로는 서버에 전송할 데이터, 세 번째 인자로 서버에서 호출할 콜백 함수.

 

2-8) server.js 수정하기

이벤트가 발생할 때 전달받은 콜백 함수를 서버 쪽에서 호출해 보자.

<전체 코드>

import http from 'http';
import SocketIO from "socket.io";
import express from 'express';

const app = express();

app.set("view engine", "pug");
//dirname은 Node.js기본 전역변수로, 현재 실행되는 폴더의 경로를 의미
app.set("views", __dirname + "/views");
app.use("/public", express.static(__dirname + "/public"));

// '/'를 받으면 home으로 가게 설정
app.get("/", (req, res) => res.render("home"));
//만약홈이 아닌 다른 주소로 get요청을 보내더라도 홈으로 리다이렉션하게 예외처리함
app.get("/*", (req, res) => res.redirect("/"));

const httpServer = http.createServer(app);
const wsServer = SocketIO(httpServer);

wsServer.on("connection", (socket) => {
    socket.on("enter_room", (roomName,done) => {
       console.log(roomName);
       setTimeout(() => {
        done();
       }, 5000);
    });
});

const handleListen = () => console.log("Listening on http://localhost:3000");
httpServer.listen(3000,handleListen);

설명 : event_room  이벤트 핸들러에 매개변수 done을 추가함. (콜백 함수를 전달받는 역할)

일단 전달된 메시지를 콘솔에 출력한 다음 setTimeout 메서드를 통해서 5초 뒤에 done 호출한다는 뜻.

여기에서 중요한 사실은, done에 전달된 콜백 함수를 호출하는 것은 서버지만, 콜백 함수가 정의된 곳은 프론트앤드(app.js)라는 점이다. 따라서 enter_room 이벤트가 발생하고 나면 5초 후에 app.js에서 콜백함수가 동작하게 됨.

 

<테스트 결과 화면>

브라우저에서 방 이름을 등록하면 5초 후에 브라우저 콘솔창에 server is done! 이 출력되어야 함.

잘 출력됨을 확인할 수 있다.

server.js의 done이라는 매개체는 5초 후에  app.js의

() => {
        console.log("server is done!");

를 실행한다는 매커니즘을 이해하자! 


3. 채팅룸 만들기

현재 접속한 모든 사용자가 서로 대화를 나눌 필요가 없음.

우리는 서로 소통할 수 있는 웹소켓 그룹이 필요함.

 

socket.io는 채팅룸 서비스를 제공할 때 사용할 만한 유용한 기능들을 제공하는데, 룸 단위로 묶는 기능도 제공함.

 

3-1) server.js 수정하기

wsServer.on("connection", (socket) => {
    socket.on("enter_room", (roomName,done) => {
       console.log(roomName);
       socket.join(roomName);
    });
});

단순히 join 메서드만 이용하면 쉽게 룸단위로 접근 가능함.

 

why?)

socket.io에서 서버에 연결된 개별 소켓은 다양한 속성을 포함하고 있음.

콘솔을 찍어보며 채팅룸 기능이 어떻게 동작하는지 확인해보자

wsServer.on("connection", (socket) => {
    socket.on("enter_room", (roomName,done) => {
       console.log(roomName);
       console.log(socket.id);
       console.log(socket.rooms);
       socket.join(roomName);
       console.log(socket.rooms);
    });
});

설명 : id 속성은 해당 소켓 만의 고유한 값. - 여러명이 서버에 접속해도 서로 구별 가능

rooms 속성은 소켓이 현재 어떤 룸에 있는지를 나타내는데 소켓이 접속한 방이 하나가 아닐수도 있다는 점을 인지해야함.

 

<테스트 해보기>

다음과 같이 입력하면

방 이름이 먼저 출력되고

그 다음으로 id가 출력

그 다음으로 join 전의 출력

그 다음은 join 후의 출력 임을 확인할 수 있다.

 

여기서 핵심 ! 

룸에 join하기도 전에 set(1)을 보면 방이 하나 조회되는 것을 볼 수 있다.

어? 방을 만들지도 않았는데 왜 조회되지?

 

그 이유는 socket.io에서는 join을 이용해 어딘가에 접속하지 않더라도, 개별 소켓은 서버에서 제공하는 개인 공간에 들어가 있는 상태이다. 이러한 개인 공간은 소켓과 서버 사이에 형성된 채팅룸이라고 할 수 있음 → private room(프라이빗룸)

 

결론 : 맨 처음 접속할 때, 프라이빗 룸에만 머무르던 소켓이 join을 이용하면 다른 소켓과 그룹을 형성해 채팅룸을 만들 수 있는데 , join에는 룸 이름이 전달된다. 전달된 이름이 만약 서버에 존재하면 소켓은 그 방에 합류하고, 서버에 존재하지 않으면 방이 새롭게 만들어지고 그 방에 합류하게 된다.

 

3-2) home.pug 수정하기

 

반응형