티스토리 뷰
웹소켓 (WebSocket)
클라이언트와 서버가 지속적으로 연결을 유지하며 양방향 통신을 할 수 있게 해주는 통신 프로토콜이다.
HTTP 처럼 TCP 위에서 동작하지만 연결이 끊기지 않고 지속된다.
기본 프로토콜은 ws:// 또는 wss:// (보안) 이며 실시간 데이터 전송으로 채팅, 게임, 알림 등에 사용할 수 있다. (채팅 시스템, 실시간 주식이나 코인 가격, 실시간 알림, 온라인 게임, Google Docs와 같은 공동 문서 편집)
클라이언트가 서버에 handshake 요청을 하면 연결 수립 후 양방향 데이터 전송을 하는 구조이다.
HTTP와의 차이는 HTTP는 요청/응답 1회성이라면, 웹 소켓은 연결 유지 및 푸쉬가 가능하다.
브라우저와 서버 모두 웹소켓 지원이 필요하며, 프록시/방화벽이 웹소켓을 차단할 수도 있다.
연결 유지하는데 서버 리스소기 많이 소모되는 단점이 있다.
기존 HTTP 통신 방식은 요청-응답 구조만 가능하여 서버가 먼저 클라이언트에게 메시지 전송이 불가능했으며, 클라이언트가 주기적으로 서버에 폴링(Polling)하여 새로운 정보를 가져오는 비효율적 방식을 사용했으며, 이러한 한계로 인해 등장한 것이 웹소켓이다.
Q. 'HTTP 처럼 TCP 위에서 동작하지만 연결이 끊기지 않고 지속된다.'? 새로고침하면 어떻게 되지?
A. 웹 소켓은 TCP 위에서 연결을 맺고, 연결이 끊기지 않도록 클라이언트와 서버가 계속 통신을 유지한다. 하지만 새로고침하면 연결은 끊어지고 다시 연결된다.
웹 소켓은 원칙적으로는 지속 가능하지만 브라우저 단에서 새로고침하면 끊긴다라는 점이 중요하다.
HTTP는 요청/응답 후 연결을 끊는다. 하지만 웹 소켓은 한번 연결이 되면 유지된다.
웹 소켓은 브라우저의 탭 단위로 유지가 되는데, 새로고침은 페이지 전체를 다시 로딩하기 때문에 자바스크립트를 다시 실행하게되고 이전의 웹소켓 연결은 종료된다. 즉 다시 연결을 새로 시도하게 되는 것이다.
이건 브라우저가 소켓을 끊은게 아니라 클라이언트 코드가 죽었기 때문이다.
끊기지 않게 하려면 새로고침이 없어야하고 SPA 방식으로 이동하여 웹소켓을 유지할 수 있다.
또는 brforeunload 이벤트 등을 이용해 연결 복구 로직을 넣을 수 있다. (beforeunload에서 sessionStorage, localStorage 등에 상태 저장 후 페이지가 로드된 후 실제 재연결은 페이지에서 진행)
새로고침 시, 어떻게 동작할까?
- 현재 탭의 자바스크립트 컨텍스트가 종료된다.
- 웹 소켓 인스턴스도 사라진다.
- 브라우저가 TCP 커넥션을 정리하며 .close()와 유사한 처리를 수행한다.
- 서버는 보통 웹소켓 연결이 끊어진 것을 인지하고 cleanup을 수행한다.
웹 소켓과 다른 방식의 통신
항목 | WebSocket | HTTP Polling | SSE (Server-Sent Events) |
양방향 통신 | ✅ | ❌ | ❌ (서버 → 클라이언트만) |
연결 유지 | ✅ | ❌ (매번 새 연결) | ✅ |
실시간성 | 🔥 최고 | 낮음 | 중간 |
브라우저 호환성 | 대부분 ✅ | ✅ | IE X |
웹 소켓 동작 흐름
[초기 연결]
1. 클라이언트가 HTTP 업그레이드 요청 (Handshake) → "웹소켓으로 바꿔도 될까요?"
2. 서버가 업그레이드 허용 응답 → 연결 전환
3. WebSocket 연결 수립됨
[연결 이후]
- 양방향으로 자유롭게 데이터 송수신 가능
- 연결 유지(keep-alive), 필요 시 재연결
구현 예시 (ws 라이브러리 사용)
// server.js
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', socket => {
console.log('🔌 Client connected');
socket.on('message', message => {
console.log('📩 Received:', message);
socket.send(`Echo: ${message}`);
});
socket.on('close', () => {
console.log('❌ Client disconnected');
});
});
// client
const socket = new WebSocket("ws://localhost:8080");
socket.onopen = () => {
console.log("✅ Connected to server");
socket.send("Hello Server!");
};
socket.onmessage = (event) => {
console.log("💬 Message from server:", event.data);
};
socket.onclose = () => {
console.log("❌ Disconnected from server");
};
주요 개념
개념 | 설명 |
ws / wss | ws는 WebSocket, wss는 TLS(https처럼 보안 연결) 사용 |
Handshake | HTTP로 시작해서 WebSocket으로 업그레이드 하는 과정 |
Full Duplex | 양쪽에서 동시에 메시지 주고받기 가능 |
프레임 기반 전송 | 데이터는 텍스트/바이너리 프레임 단위로 전송 |
Keep-Alive | 연결이 계속 살아있음 (ping/pong 신호로 감시 가능) |
Connection 수 관리 | 서버가 너무 많은 연결을 유지해야 하므로, 스케일링이 중요 |
웹소켓 연결 유지 전략
웹 소켓은 연결이 한 번 수립되면 클라이언트와 서버가 지속적으로 연결된 상태를 유지한다.
하지만 인터넷 환경에서는 네트워크 단절, 클라이언트 죽음 등..의 경우로 연결이 끊기는 경우가 발생하는데, 이때 연결을 잘 유지하고 감지하는 전략이 필요하다.
1. Pink/Pong 프레임
웹 소켓 프로토콜에서 정의된 특수한 제어 프레임 (Control Frame)이다.
서버에서 클라이언트로 Ping, 클라이언트는 자동으로 Pong 응답을 하며 반대로 방향도 가능하다.
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
console.log('클라이언트 연결됨');
const interval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping();
}
}, 30000); // 30초마다 ping
ws.on('pong', () => { console.log('pong 받음 → 연결 살아있음');
});
ws.on('close', () => clearInterval(interval)); });
2. 타임아웃
일정 시간 동안 Ping/Pong 응답이 없으면 연결을 종료한다.
서버 또는 클라이언트가 이를 감지하고 .close()를 실행한다.
let lastPong = Date.now();
ws.on('pong', () => {
lastPong = Date.now();
});
setInterval(() => {
const diff = Date.now() - lastPong;
if (diff > 60000) {
// 1분 이상 pong 없음
ws.terminate(); // 강제 종료
} else {
ws.ping();
}
}, 30000);
프록시/로드밸런서가 유휴 연결을 자르지 않도록 주기적인 ping은 30초 이하로 하는 것이 안전하며 클라이언트와 서버 모두 ping/pong 타임아웃을 적절히 조절해야한다.
ws.terminate()는 강제 종료이며 ws.close()는 정상 종료이다.
3. 자동 재연결
클라이언트가 끊어졌을때 자동으로 재시도를 한다.
function connect() {
const socket = new WebSocket("wss://example.com");
socket.onopen = () => console.log("연결됨");
socket.onclose = () => {
console.log("끊김 → 재연결 시도");
setTimeout(connect, 3000); // 3초 후 재연결
};
socket.onerror = (e) => {
console.error("에러 발생", e);
socket.close(); // 종료 후 재시도 유도
};
}
connect();
4. 하트비트 구현방식 (앱)
클라이언트 또는 서버가 주기적으로 신호를 연결해 감시한다. Ping/Pong 대신 앱에서 메시지를 주고 받는 방식이다.
setInterval(() => {
socket.send(JSON.stringify({ type: 'ping' }));
}, 30000);
socket.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.type === 'pong') {
console.log("pong 수신");
}
};
정리
전략 | 동작 | 사용 이유 | 예시 |
Ping/Pong | 클라이언트 또는 서버가 연결이 살아있는지 확인한다. | 연결 상태 확인 | ws.ping() / on('pong') |
Timeout | 클라이언트 또는 서버가 무응답 시 연결을 끊는다. | 무응답 시 연결 종료 | 60초 넘으면 terminate() |
자동 재연결 | 주로 클라이언트가 사용하며 연결이 끊겼을때 다시 연결을 시도한다. | 끊겼을 때 복구 | setTimeout(connect, 3000) |
Heartbeat 메시지 | 클라이언트 또는 서버가 주기적으로 신호로 연결을 감시한다. | 앱 논리 기반 감시 | send({type:'ping'}) ↔ pong 응답 |
수평 확장 및 부하 분산
대규모 웹 소켓 서비스를 운영할 때는 단일 서버로 감당하기 어렵기 때문에 수평 확장 및 부하 분산이 필수이다.
예시 (Redis pub/sub + WebSocket)
[Client 1] [Client 2]
| |
┌──────▼──────┐ ┌──────▼──────┐
│ WS Server A │ │ WS Server B │
└──────┬──────┘ └──────┬──────┘
│ │
└────── Redis Pub/Sub ───┘
클라이언트는 부하 분산을 통해 여러 WS 서버에 연결된다.
하나의 서버가 메시지를 수신하면 Redis의 publish()로 브로드캐스트하고 다른 서버들은 Redis subscribe()를 통해 메시지를 수신하고 연결된 클라이언트에 전달한다.
웹 소켓 서버는 수평 확장을 위해 무상태(stateless)하게 유지하며 로드밸런서에 동일한 클라이언트가 항상 같은 웹 소켓 서버에 연결되도록 sticky session 설정한다. (또는 클라이언트가 재연결 시 세션을 다시 등록하도록 구현한다)
Redis pub/sub을 통해 웹소켓 서버간 메시지를 전달한다. (=> 다만 Redis pub/sub은 속도가 빠르고 간단하지만 메시지 유실 가능성이 있으므로 보장이 필요한 경우 Redis Stream, Kafka 등을 고려할 수 있다고 한다.)
Q. 웹 소켓 서버는 수평 확장을 위해 무상태(stateless)하게 유지한다란
A. 서버가 클라이언트에 대한 정보를 '기억하지 않는' 구조를 말한다.
'누가 로그인했는지, 어떤 방에 접속했는지, 마지막 메시지가 무엇인지' 등을 서버가 기억하지 않고 다른 저장소(Redis, DB, 세션 스토어 등)에 따로 저장한다.
웹 소켓 서버는 단순히 메시지를 받고 필요한 곳에 전달만 하는게 중요하다. 이렇게 해야 서버를 늘려야할 경우 유저 분산이 쉽기 때문에 수평 확장이 가능하다.
'개념 > 2025 학습' 카테고리의 다른 글
.m3u8 (HLS, HTTP Live Streaming) (2) | 2025.07.08 |
---|---|
React의 동시성 기능 (Concurrent Features) (0) | 2025.04.24 |
React의 예측 가능한(predictable) UI를 위한 설계 철학 (0) | 2025.04.23 |
자바스크립트 힙 (Heap) (1) | 2025.04.20 |
주요 시간 관련 용어 정리 (UTC, KST, ISO String, Timestamp) (0) | 2025.04.20 |
React 18에서 Strict Mode (0) | 2025.04.20 |
클라이언트에서 API 호출할때 사용하는 도구에는 뭐가 있을까? (fetch, axios, TanStack Query, SWR) + graphql, firebase, supabase (0) | 2025.04.10 |
프로젝트에서 Vue 쓸까? React 쓸까? (0) | 2025.04.06 |