Logo

Socket.IO 기본 사용법

실시간 양방향 통신을 위해서 웹소켓(WebSocket)이 표준이 된지가 꽤 되었고, 현재 대부분의 모던 브라우저에서 웹소켓 API를 지원하고 있습니다. 하지만 여전히 우리는 모든 사용자가 최신 브라우저를 사용한다고 단정할 수 없으며, 웹소켓이 지원되지는 않는 환경에서는 어쩔 수 없이 대안 기술을 사용해야 하죠.

Socket.IO는 이런 호환성 부분에 대해서 개발자가 크게 걱정할 필요없이 실시간 양방향 통신을 하는 웹 애플리케이션을 작성할 수 있도록 도와주는 라이브러리입니다. 이번 포스팅에서는 간단한 실습을 통해서 Socket.IO를 기본적으로 어떻게 사용하는지 알아보겠습니다.

실시간 양방향 통신을 필요로 하는 애플리케이션을 구현하는데 필수적인 기술인 웹소켓(WebSocket)에 대해서 별도 포스팅을 참고 바랍니다.

Socket.IO 설치

Socket.IO는 npm 패키지 저장소에 socket.io라는 이름으로 올라와 있습니다. 서버에서는 패키지 매니저를 통해서 쉽게 내려 받을 수 있습니다.

Node.js 프로젝트에서는 터미널에서 npm add 명령어로 설치합니다.

$ npm add socket.io

Bun을 사용하는 프로젝트에서는 bun add 명령어로 설치합니다.

$ bun add socket.io

기본 서버 코드 작성

실습으로 아주 간단한 채팅 애플리케이션을 구현해보려고 하는데요. 먼저 기본적인 서버 코드를 작성하도록 하겠습니다.

서버에서 웹소켓을 지원하려면 우선 HTTP 요청을 처리할 수 있어야하는데요. 웹소켓 연결이 맺기 전에 이뤄지는 클라이언트와 서버 간의 핸드쉐이크 과정이 HTTP를 통해서 이뤄지기 때문입니다.

따라서 node:http 모듈을 통해서 우선 HTTP 서버를 생성합니다. 다음 그 HTTP 서버를 socket.io 모듈에서 불러온 Server 클래스의 생성자로 넘기면 웹소켓 서버를 만들 수 있습니다.

클라이언트에서 이 웹소켓 서버에 접속하면 connection 이벤트가 발생합니다. 그래서 이제부터 우리는 이 이벤트 핸들러 안에 실습 코드를 추가하도록 하겠습니다.

서버
import { createServer } from "node:http";
import { readFile } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import { Server } from "socket.io";

const __dirname = dirname(fileURLToPath(import.meta.url));

const server = createServer((req, res) => {
  readFile(join(__dirname, "index.html"), (err, data) => {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.end(data);
  });
});

const io = new Server(server);

io.on("connection", (socket) => {
  // 👉 여기에 웹소켓 서버 코드 작성
});

server.listen(3000, () => {
  console.log("http://localhost:3000 에서 서버 구동 중...");
});

기본 클라이언트 코드 작성

이제 브라우저에서 돌아가는 클라이언트 코드를 작성할 차례입니다.

우선 CDN을 통해서 Socket.IO 클라이언트 코드를 불러온 후, io() 함수로 소켓 객체를 생성하겠습니다.

클라이언트
<head>
  <script type="module">
    import { io } from "https://cdn.socket.io/4.7.5/socket.io.esm.min.js";
    const socket = io();

    // 👉 여기에 웹소켓 클라이언트 코드 작성
  </script>
</head>

간단한 채팅 UI를 웹 페이지에 그리기 위한 HTML도 작성하겠습니다.

클라이언트
<body>
  <main class="container">
    <article>
      <ul></ul>
    </article>
    <form>
      <fieldset role="group">
        <input />
        <button>Send</button>
      </fieldset>
    </form>
  </main>
</body>

서버에서 모든 클라이언트로 데이터 보내기

웹소켓을 사용하면 하나의 서버가 다수의 클라이언트에게 데이터를 보낼 수 있는데요. 여러 명이 모여서 대화를 나누는 채팅 애플리케이션에서 꼭 필요한 기능이겠죠?

io.emit(<이벤트명>, <데이터>) 함수를 사용하여, 모든 클라이언트에게 동일한 데이터를 보낼 수 있습니다.

실습 서버에서는 어떤 클라이언트가 새로 접속하면 연결된 사용자가 있다는 메시지를 모든 클라이언트에게 보내보겠습니다. 이벤트 이름을 message로 하였으나, 어떤 문자열을 사용하든 상관없습니다. 뒤에서 나오는 클라이언트 코드에서 사용하는 이벤트 이름과 일치시켜주기면 하면 됩니다.

서버
io.on("connection", (socket) => {
  io.emit("message", `${socket.id} 님이 연결되었습니다.`);
});

이벤트 핸들러의 인자인 socket은 각 클라이언트를 나타냅니다. id 속성에는 클라이언트의 식별자가 들어 있습니다.

클라이언트에서 서버에서 보낸 데이터 처리하기

이제 클라이언트에서는 서버에서 보낸 데이터를 처리해야하는데요.

socket.on(<이벤트명>, <핸들러>) 함수를 사용하여, 특정 이벤트가 발생했을 때 실행할 로직을 지정해줄 수 있습니다.

실습 클라이언트에서는 서버로 부터 받은 메시지를 담은 li 요소를 생성한 후에, 웹 페이지 상의 ul 요소 아래에 추가해주도록 하겠습니다.

클라이언트
const ul = document.querySelector('ul');

socket.on('message', (msg) => {
  const li = document.createElement('li');
  li.textContent = msg;
  ul.appendChild(li);
  window.scrollTo(0, document.body.scrollHeight);
});

이제 웹 페이지를 새로 고침해보시면, OSbp9CljZ0A1Z078AAAh 님이 연결되었습니다.와 같은 메시지가 보일 것입니다. 🎉

클라이언트에서 서버로 데이터 보내기

이번에는 반대로 클라이언트에서 서버로 데이터를 보내볼까요?

socket.emit(<이벤트명>, <데이터>) 함수를 사용하면 서버로 특정 데이터를 전송할 수 있습니다.

input 요소로 사용자로 부터 텍스트를 입력받아 서버로 전송해보겠습니다. 양식(form) 요소에서 submit 이벤트가 발생했을 때 socket.emit() 함수가 실행되도록 해줍니다.

클라이언트
form.addEventListener('submit', (event) => {
  event.preventDefault();
  if (input.value) {
    socket.emit('message', input.value);
    input.value = '';
  }
});

메시지 전송 후에는 input 요소에 입력된 텍스트를 지워줍니다.

서버에서 받은 메시지를 다른 클라이언트에 중계하기

채팅 애플리케이션에서는 한 사용자가 보낸 메시지가 다른 모든 사용자에게 보내지죠?

socket.on(<이벤트명>, <핸들러>) 함수를 사용하여, 처음에 서버에 접속했을 때와 동일하게 io.emit() 함수가 호출되게 하면 됩니다. 이렇게 클라이언트에서 받은 메시지를 현재 연결된 모든 클라이언트에 중계할 수 있습니다.

서버
io.on("connection", (socket) => {
  socket.on("message", (msg) => {
    io.emit("message", `${socket.id}: ${msg}`);
  });
});

이제 브라우저 창을 두 개 띄워서 한 브라우저에서 메시지를 보내면, 다른 브라우저에서 해당 메시지가 나타날 것입니다.

서버에서 연결이 끊기면 클라이언트에 알려주기

어떤 클라이언트의 연결이 끊겼을 때 다른 클라이언트에게 알려주고 싶다면 socket.broadcast.emit() 함수를 사용합니다. io.emit() 대신에 socket.broadcast.emit()를 사용하면 이벤트를 발생시킨 클라이언트는 제외한 모든 클라이언트에게 데이터를 보낼 수 있습니다.

연결이 끊겼을 때는, 해당 클라이언트 소캣에서 disconnect 이벤트가 발생합니다. 따라서 socket.on() 함수를 통해서 해당 이벤트를 처리해주면 되겠습니다.

서버
io.on("connection", (socket) => {
  socket.on("disconnect", () => {
    socket.broadcast.emit("message", `${socket.id} 님의 연결이 끊어졌습니다.`);
  });
});

전체 코드

본 포스팅에서 작성한 코드는 아래에서 확인하고 직접 실행해보실 수 있습니다.

마치면서

지금까지 간단한 채팅 애플리케이션 구현하기 위해서 서버와 클라이언트 단에서 Socket IO를 어떻게 사용하는지 알아보았습니다. Socket IO의 기본 사용법과 전반적인 개발 흐름을 익히시는데 도움이 되었으면 좋겠습니다.