Socket.IO 란?

Jiseong Choi·2025년 4월 22일
post-thumbnail

이번 포스트에서는 Socket.IO 라이브러리에 대해서 정리를 해보겠다!

✨ Socket.IO란?

Socket.IO는 브라우저와 서버 간의 실시간 양방향 이벤트 기반 통신을 쉽게 구현할 수 있도록 도와주는 JavaScript 라이브러리이다.

  • 클라이언트와 서버 모두 JavaScript로 작성 가능하다.
  • WebSocket 위에서 동작하지만, 단순 WebSocket보다 풍부한 기능 제공한다.
  • Fallback 메커니즘 내장: 브라우저가 WebSocket을 지원하지 않아도 동작이 가능하다. (XHR long-polling 등으로 대체)

🔍 XHR long-polling이란?
WebSocket을 사용할 수 없는 환경(예: 오래된 브라우저, 방화벽 제한 등)에서 사용하는 대체 통신 방식이다.
클라이언트가 서버에 HTTP 요청을 보내고, 서버가 새 데이터가 생길 때까지 응답을 지연시킨 후 응답을 보내는 방식이다.
실시간성은 WebSocket보다 떨어지지만, WebSocket이 막힌 환경에서도 비슷한 경험을 제공할 수 있는 장점이 있다.


✅ Socket.IO의 구성 요소

Socket.IO는 크게 두 부분으로 구성된다:

  • socket.io (서버): Node.js 기반 서버 모듈
  • socket.io-client (클라이언트): 브라우저 혹은 Node.js에서 실행되는 클라이언트 모듈

🔄 Socket.IO 연결 방식

Socket.IO는 처음에 HTTP를 통해 연결을 시작하지만, 클라이언트와 서버 간 handshake 과정에서 자동으로 WebSocket으로 업그레이드된다.

이 과정은 Socket.IO가 연결되는 과정이다:

  1. 클라이언트가 http:// 또는 https:// 주소로 서버에 연결 요청을 보냄
  2. 서버는 handshake를 통해 WebSocket을 사용할 수 있는지 협상
  3. 브라우저가 지원하면 Upgrade: websocket 헤더를 통해 WebSocket 프로토콜로 전환
  4. 이후 실시간 데이터 통신은 WebSocket(ws:// 또는 wss://)을 통해 지속

즉, 사용자는 그냥 io("http://localhost:3000")처럼 작성해도, Socket.IO가 내부에서 자동으로 WebSocket 연결로 업그레이드해서 사용한다.


⚙️ 기본 사용법

서버 측 (Node.js)

npm install socket.io 	// 먼저 Socket.IO 라이브러리를 설치한다.
const { Server } = require("socket.io");
const io = new Server(3000);

io.on("connection", (socket) => {
  console.log("클라이언트 연결됨", socket.id);

  socket.on("message", (data) => {
    console.log("수신 메시지:", data);
    socket.emit("message", `Echo: ${data}`);
  });

  socket.on("disconnect", () => {
    console.log("클라이언트 연결 해제");
  });
});

클라이언트 측 (브라우저)

<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<script>
  const socket = io("http://localhost:3000");

  socket.on("connect", () => {
    console.log("서버와 연결됨", socket.id);
    socket.emit("message", "Hello Server!");
  });

  socket.on("message", (data) => {
    console.log("서버로부터 메시지:", data);
  });
</script>

🔄 주요 기능 및 특징

1. 자동 재연결 지원

  • 네트워크가 끊겼다가 복구되어도 자동으로 reconnect 시도

2. Fallback 메커니즘

  • WebSocket이 지원되지 않는 환경에서도 XHR-polling으로 대체가 가능하다.

3. 방(Room)과 네임스페이스(Namespace)

  • Room: 특정 유저 그룹에게만 메시지를 브로드캐스트할 수 있다.
  • Namespace: URI를 나누어 채널을 분리한다. (ex. /chat, /admin)

4. 브로드캐스팅

  • 특정 클라이언트를 제외하거나, 방(Room) 안의 모든 유저에게 메시지를 보낼 수 있다
socket.broadcast.emit("msg", "다른 모든 사용자에게 전송");
io.to("room1").emit("msg", "room1 사용자들에게만 전송");

5. 미들웨어 지원

  • 클라이언트 연결 시 인증 로직을 실행하거나, 메시지 전처리가 가능하다
io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  if (isValidToken(token)) {
    next();
  } else {
    next(new Error("인증 실패"));
  }
});

🧠 Socket.IO vs WebSocket 차이점

항목WebSocketSocket.IO
프로토콜표준 WebSocket (RFC 6455)자체 프로토콜 (WebSocket 위 또는 Polling)
지원 브라우저최신 브라우저 필요구형 브라우저도 지원 (Fallback 있음)
자동 재연결❌ 없음✅ 기본 지원
네임스페이스/방❌ 직접 구현해야 함✅ 내장 기능
메시지 구조문자열 또는 바이너리이벤트 기반 (이벤트명 + 데이터)
미들웨어❌ 없음✅ 연결 미들웨어 사용 가능

⚠️ Socket.IO의 한계점

Socket.IO는 강력하고 편리한 기능을 제공하지만, 아래와 같은 한계점도 존재한다:

✅ WebSocket과 공유하는 한계점

  • 상태 기반 연결 유지: 연결된 모든 클라이언트에 대해 소켓을 유지해야 하므로, 많은 사용자가 접속하면 메모리/소켓 자원이 많이 소모된다.
  • 프록시/방화벽 문제: 일부 네트워크 환경에서 WebSocket과 마찬가지로 연결이 차단되거나, Upgrade 헤더가 차단될 수 있다.
  • REST 기반 인프라와의 통합 난이도: 미들웨어 체인, 인증 흐름 등이 WebSocket과 다르기 때문에 통합 설계가 필요하다.

⚠️ Socket.IO만의 추가 제약

  • 표준이 아닌 자체 프로토콜 사용: WebSocket 표준(RFC 6455)과는 다르게 Socket.IO는 자체 포맷과 handshake 구조를 사용하기 때문에, 표준 WebSocket 클라이언트와는 호환되지 않는다.
  • 버전 간 호환성 문제: 서버와 클라이언트의 Socket.IO 버전이 다르면 연결이 되지 않거나 오류가 발생할 수 있다. (같은 메이저 버전 권장)
  • 추가 오버헤드 발생: 이벤트명, 네임스페이스, 방(Room) 정보 등 메타데이터로 인해 성능 부하가 늘어날 수 있다.

🔐 인증 및 보안

1. handshake.auth를 활용해 JWT 등 인증 정보를 전달할 수 있다.

Socket.IO에서는 handshake.auth를 활용해 클라이언트가 연결 시 인증 정보를 함께 전달할 수 있다. 가장 일반적인 예는 JWT(Json Web Token) 기반 인증이다.

✅ JWT 인증 흐름 예시

📦 클라이언트 측 (JWT 전달)

const socket = io("http://localhost:3000", {
  auth: {
    token: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }
});
  • auth 필드에 JWT 또는 다른 인증 데이터를 담아 서버로 보냅니다.

🔐 서버 측 (인증 처리)

io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET);
    socket.user = payload; // 이후 socket.user로 사용자 정보 접근 가능
    next();
  } catch (err) {
    next(new Error("인증 실패: 토큰이 유효하지 않음"));
  }
});
  • socket.handshake.auth.token을 꺼내서 jwt.verify() 등으로 검증한다.
  • 실패 시 next(new Error(...))로 연결을 차단할 수 있다.

이 방식은 REST API에서의 Authorization: Bearer <token> 방식과 유사하게, Socket.IO 연결에서도 인증 로직을 적용할 수 있도록 해준다.

2. HTTPS + WSS 기반 배포를 권장한다.

3. 미들웨어를 통한 인증 필터링 구현을 권장한다.

  • Socket.IO의 io.use() 메서드를 활용하여 서버에 연결 요청이 들어오기 전에 인증/인가 로직을 선행 적용할 수 있다.
  • 이를 통해 모든 연결 시도에 대해 공통 필터처럼 인증 로직을 삽입할 수 있고, 필요 시 연결을 거부하거나 추가 정보를 주입할 수 있다.

🔍 예시: 관리자 권한을 가진 사용자만 연결 허용하기

 io.use((socket, next) => {
   const token = socket.handshake.auth.token;
   try {
     const payload = jwt.verify(token, process.env.JWT_SECRET);
     if (payload.role !== 'admin') {
       return next(new Error("접근 권한 없음"));
     }
     socket.user = payload;
     next();
   } catch (err) {
     next(new Error("인증 실패: 유효하지 않은 토큰"));
   }
 });
- 이 예시에서는 `JWT` 토큰의 payload 안에 있는 `role` 값을 기준으로, **관리자가 아닐 경우 연결을 거부한**다.
- 인증에 성공한 사용자 정보는 `socket.user`에 주입하여 이후 이벤트 핸들러에서 활용할 수 있다.

🔗 참고 링크

profile
나 혼자 공부하고, 끄적이는 공간. (Node.JS / Back-End Developer)

0개의 댓글