WebSocket
웹소켓
양방향 실시간 통신 프로토콜. 채팅, 게임, 실시간 알림.
웹소켓
양방향 실시간 통신 프로토콜. 채팅, 게임, 실시간 알림.
WebSocket은 클라이언트와 서버 간 양방향 실시간 통신을 위한 프로토콜입니다. HTTP와 달리 한 번 연결을 맺으면 연결이 유지되어, 서버가 클라이언트에게 언제든 데이터를 푸시할 수 있습니다. 채팅, 실시간 알림, 게임, 주식 시세 등에 필수적입니다.
WebSocket은 HTTP 핸드셰이크로 시작합니다. 클라이언트가 Upgrade: websocket 헤더를 보내면, 서버가 101 Switching Protocols로 응답하고 프로토콜이 전환됩니다. 이후 ws:// 또는 wss://(보안) 프로토콜로 양방향 메시지를 주고받습니다.
HTTP 폴링 대비 WebSocket의 장점은 지연 시간과 네트워크 효율입니다. 매번 연결을 맺고 끊는 HTTP와 달리 연결이 유지되어 헤더 오버헤드가 없고, 서버가 즉시 메시지를 전송할 수 있습니다. 단, 상태를 유지해야 해서 수평 확장이 복잡합니다.
실무에서는 Socket.IO(Node.js), ws(Node.js 순수 구현), Spring WebSocket(Java) 등을 사용합니다. Socket.IO는 자동 재연결, 룸/네임스페이스, 폴링 폴백 등 편의 기능을 제공합니다. AWS API Gateway, Cloudflare 등에서 관리형 WebSocket도 지원합니다.
// 브라우저 WebSocket API - 기본 사용법
const ws = new WebSocket('wss://example.com/chat');
// 연결 성공
ws.onopen = () => {
console.log('WebSocket 연결됨');
ws.send(JSON.stringify({ type: 'join', room: 'general' }));
};
// 메시지 수신
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('메시지 수신:', data);
};
// 연결 종료
ws.onclose = (event) => {
console.log(`연결 종료: ${event.code} ${event.reason}`);
};
// 에러 처리
ws.onerror = (error) => {
console.error('WebSocket 에러:', error);
};
// 메시지 전송
ws.send(JSON.stringify({ type: 'message', text: '안녕하세요!' }));
// 재연결 로직 (지수 백오프)
function connectWithRetry(url, maxRetries = 5) {
let retries = 0;
function connect() {
const ws = new WebSocket(url);
ws.onclose = () => {
if (retries < maxRetries) {
const delay = Math.pow(2, retries) * 1000; // 1s, 2s, 4s, 8s...
console.log(`${delay/1000}초 후 재연결 시도...`);
setTimeout(connect, delay);
retries++;
}
};
ws.onopen = () => { retries = 0; }; // 성공시 리셋
return ws;
}
return connect();
}
// Node.js 서버 (ws 라이브러리)
import { WebSocketServer, WebSocket } from 'ws';
import http from 'http';
// HTTP 서버와 함께 WebSocket 서버 생성
const server = http.createServer();
const wss = new WebSocketServer({ server });
// 연결된 클라이언트 관리
const clients = new Map();
wss.on('connection', (ws, req) => {
const clientId = req.headers['sec-websocket-key'];
clients.set(clientId, ws);
console.log(`클라이언트 연결: ${clientId}`);
// ping/pong으로 연결 상태 확인
ws.isAlive = true;
ws.on('pong', () => { ws.isAlive = true; });
// 메시지 수신
ws.on('message', (data) => {
const message = JSON.parse(data);
console.log('수신:', message);
// 모든 클라이언트에게 브로드캐스트
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
...message,
timestamp: Date.now()
}));
}
});
});
ws.on('close', () => {
clients.delete(clientId);
console.log(`클라이언트 종료: ${clientId}`);
});
});
// Heartbeat - 30초마다 연결 상태 확인
const heartbeat = setInterval(() => {
wss.clients.forEach((ws) => {
if (!ws.isAlive) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
wss.on('close', () => clearInterval(heartbeat));
server.listen(8080, () => console.log('WebSocket 서버: ws://localhost:8080'));
// Socket.IO (룸, 네임스페이스, 자동 재연결)
import { Server } from 'socket.io';
const io = new Server(server, { cors: { origin: '*' } });
io.on('connection', (socket) => {
socket.join('room-1'); // 룸 참가
socket.to('room-1').emit('user-joined', socket.id); // 룸에 브로드캐스트
});
# Python 클라이언트 (websockets 라이브러리)
import asyncio
import websockets
import json
# 기본 WebSocket 클라이언트
async def websocket_client():
uri = "wss://example.com/chat"
async with websockets.connect(uri) as ws:
# 메시지 전송
await ws.send(json.dumps({
"type": "join",
"room": "general"
}))
# 메시지 수신 (무한 루프)
async for message in ws:
data = json.loads(message)
print(f"수신: {data}")
asyncio.run(websocket_client())
# Python 서버 (websockets 라이브러리)
import asyncio
import websockets
import json
connected_clients = set()
async def handler(websocket, path):
connected_clients.add(websocket)
try:
async for message in websocket:
data = json.loads(message)
print(f"수신: {data}")
# 모든 클라이언트에게 브로드캐스트
broadcast = json.dumps({**data, "timestamp": asyncio.get_event_loop().time()})
await asyncio.gather(
*[client.send(broadcast) for client in connected_clients]
)
finally:
connected_clients.remove(websocket)
async def main():
async with websockets.serve(handler, "localhost", 8080):
print("WebSocket 서버: ws://localhost:8080")
await asyncio.Future() # 무한 대기
asyncio.run(main())
# FastAPI + WebSocket (프로덕션 권장)
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from typing import List
app = FastAPI()
connections: List[WebSocket] = []
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
connections.append(websocket)
try:
while True:
data = await websocket.receive_json()
for conn in connections:
await conn.send_json(data)
except WebSocketDisconnect:
connections.remove(websocket)
"실시간 채팅이면 WebSocket이 맞아요. 폴링은 지연도 있고 서버 부하도 크죠. Socket.IO 쓰면 자동 재연결, 룸 기능도 있어서 편합니다. 서버가 여러 대면 Redis Adapter로 메시지 브로드캐스팅하면 돼요."
"WebSocket은 HTTP 핸드셰이크 후 양방향 전이중 통신을 합니다. HTTP/2의 Server Push와 달리 클라이언트도 서버에 언제든 메시지를 보낼 수 있습니다. 연결 유지 비용이 있어서 대규모 서비스에서는 연결 수 관리와 수평 확장 전략이 중요합니다."
"WebSocket 연결이 자주 끊어지면 프록시나 로드밸런서의 타임아웃 설정을 확인하세요. ping/pong 프레임으로 연결 유지하고, 클라이언트에서 재연결 로직을 구현해야 합니다. Socket.IO는 이게 기본 내장이에요."
네트워크 불안정, 서버 재시작, 프록시 타임아웃 등으로 연결이 끊어질 수 있습니다. 지수 백오프(1초, 2초, 4초...)로 자동 재연결을 구현하거나 Socket.IO처럼 내장된 라이브러리를 사용하세요.
프록시, 로드밸런서, 방화벽은 유휴 연결을 끊습니다. 30초마다 ping/pong 프레임을 교환하여 연결 유지와 상태 확인을 해야 합니다. Nginx의 경우 proxy_read_timeout 설정도 확인하세요.
여러 서버에서 WebSocket을 운영하면 연결이 특정 서버에 종속됩니다. Redis Pub/Sub(Socket.IO Adapter), Kafka, AWS API Gateway + Lambda 등으로 서버 간 메시지를 공유해야 합니다.
연결 시 JWT 토큰 인증, 30초 heartbeat, 메시지 ID로 중복 방지, 연결 수/메모리 모니터링을 구현하세요. 단방향 알림만 필요하면 Server-Sent Events(SSE)가 더 간단합니다.