
이번 포스트에서는 Socket.IO 라이브러리에 대해서 정리를 해보겠다!
Socket.IO는 브라우저와 서버 간의 실시간 양방향 이벤트 기반 통신을 쉽게 구현할 수 있도록 도와주는 JavaScript 라이브러리이다.
🔍 XHR long-polling이란?
WebSocket을 사용할 수 없는 환경(예: 오래된 브라우저, 방화벽 제한 등)에서 사용하는 대체 통신 방식이다.
클라이언트가 서버에 HTTP 요청을 보내고, 서버가 새 데이터가 생길 때까지 응답을 지연시킨 후 응답을 보내는 방식이다.
실시간성은 WebSocket보다 떨어지지만, WebSocket이 막힌 환경에서도 비슷한 경험을 제공할 수 있는 장점이 있다.
Socket.IO는 크게 두 부분으로 구성된다:
Socket.IO는 처음에 HTTP를 통해 연결을 시작하지만, 클라이언트와 서버 간 handshake 과정에서 자동으로 WebSocket으로 업그레이드된다.
이 과정은 Socket.IO가 연결되는 과정이다:
http:// 또는 https:// 주소로 서버에 연결 요청을 보냄Upgrade: websocket 헤더를 통해 WebSocket 프로토콜로 전환ws:// 또는 wss://)을 통해 지속즉, 사용자는 그냥 io("http://localhost:3000")처럼 작성해도, Socket.IO가 내부에서 자동으로 WebSocket 연결로 업그레이드해서 사용한다.
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>
/chat, /admin)socket.broadcast.emit("msg", "다른 모든 사용자에게 전송");
io.to("room1").emit("msg", "room1 사용자들에게만 전송");
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (isValidToken(token)) {
next();
} else {
next(new Error("인증 실패"));
}
});
| 항목 | WebSocket | Socket.IO |
|---|---|---|
| 프로토콜 | 표준 WebSocket (RFC 6455) | 자체 프로토콜 (WebSocket 위 또는 Polling) |
| 지원 브라우저 | 최신 브라우저 필요 | 구형 브라우저도 지원 (Fallback 있음) |
| 자동 재연결 | ❌ 없음 | ✅ 기본 지원 |
| 네임스페이스/방 | ❌ 직접 구현해야 함 | ✅ 내장 기능 |
| 메시지 구조 | 문자열 또는 바이너리 | 이벤트 기반 (이벤트명 + 데이터) |
| 미들웨어 | ❌ 없음 | ✅ 연결 미들웨어 사용 가능 |
Socket.IO는 강력하고 편리한 기능을 제공하지만, 아래와 같은 한계점도 존재한다:
Upgrade 헤더가 차단될 수 있다.handshake.auth를 활용해 JWT 등 인증 정보를 전달할 수 있다.Socket.IO에서는 handshake.auth를 활용해 클라이언트가 연결 시 인증 정보를 함께 전달할 수 있다. 가장 일반적인 예는 JWT(Json Web Token) 기반 인증이다.
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 연결에서도 인증 로직을 적용할 수 있도록 해준다.
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`에 주입하여 이후 이벤트 핸들러에서 활용할 수 있다.