WebSocket — SvelteKit에서 실시간 통신 구현하기
HTTP는 클라이언트가 물어봐야 대답하지만, WebSocket은 서버가 먼저 말을 걸 수 있습니다.
개념 정의
WebSocket 은 클라이언트와 서버 간 전이중(full-duplex) 통신을 제공하는 프로토콜입니다. HTTP와 달리 한 번 연결되면 양쪽에서 자유롭게 메시지를 주고받을 수 있어, 실시간 기능에 적합합니다.
Svelte에서 WebSocket 클라이언트
<script>
import { onMount } from 'svelte';
let messages = $state([]);
let input = $state('');
let ws = $state(null);
let connected = $state(false);
onMount(() => {
ws = new WebSocket('ws://localhost:3001');
ws.onopen = () => {
connected = true;
console.log('WebSocket 연결됨');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
messages = [...messages, data];
};
ws.onclose = () => {
connected = false;
console.log('WebSocket 연결 종료');
};
ws.onerror = (error) => {
console.error('WebSocket 에러:', error);
};
return () => ws?.close();
});
function sendMessage() {
if (!input.trim() || !ws) return;
ws.send(JSON.stringify({ text: input, timestamp: Date.now() }));
input = '';
}
</script>
<div class="chat">
<div class="status">{connected ? '연결됨' : '연결 끊김'}</div>
<div class="messages">
{#each messages as msg}
<div class="message">
<span class="time">{new Date(msg.timestamp).toLocaleTimeString()}</span>
<span>{msg.text}</span>
</div>
{/each}
</div>
<form onsubmit={(e) => { e.preventDefault(); sendMessage(); }}>
<input bind:value={input} placeholder="메시지 입력" disabled={!connected} />
<button disabled={!connected}>전송</button>
</form>
</div>
재사용 가능한 WebSocket Store
// stores/websocket.js
import { writable } from 'svelte/store';
export function createWebSocketStore(url) {
const { subscribe, set, update } = writable({
connected: false,
messages: [],
error: null,
});
let ws;
let reconnectTimer;
function connect() {
ws = new WebSocket(url);
ws.onopen = () => update(s => ({ ...s, connected: true, error: null }));
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
update(s => ({ ...s, messages: [...s.messages, data] }));
};
ws.onclose = () => {
update(s => ({ ...s, connected: false }));
reconnectTimer = setTimeout(connect, 3000);
};
ws.onerror = () => update(s => ({ ...s, error: '연결 오류' }));
}
connect();
return {
subscribe,
send(data) {
if (ws?.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(data));
}
},
close() {
clearTimeout(reconnectTimer);
ws?.close();
},
};
}
SSE (Server-Sent Events) 대안
단방향 실시간 통신만 필요하면 SSE가 더 간단합니다.
// src/routes/api/events/+server.js
export function GET() {
const stream = new ReadableStream({
start(controller) {
const encoder = new TextEncoder();
const interval = setInterval(() => {
const data = JSON.stringify({ time: new Date().toISOString() });
controller.enqueue(encoder.encode(`data: ${data}\n\n`));
}, 1000);
return () => clearInterval(interval);
}
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
}
});
}
<script>
import { onMount } from 'svelte';
let serverTime = $state('');
onMount(() => {
const eventSource = new EventSource('/api/events');
eventSource.onmessage = (e) => {
serverTime = JSON.parse(e.data).time;
};
return () => eventSource.close();
});
</script>
<p>서버 시간: {serverTime}</p>
면접 포인트
- "WebSocket과 SSE의 차이는?": WebSocket은 양방향, SSE는 서버→클라이언트 단방향입니다. 채팅처럼 양방향이 필요하면 WebSocket, 알림이나 실시간 데이터 피드만 필요하면 SSE가 적합합니다.
- "재연결 로직은 왜 필요한가요?": 네트워크 불안정으로 연결이 끊길 수 있으므로, 자동 재연결과 지수 백오프(exponential backoff)를 구현해야 프로덕션에서 안정적입니다.
정리
실시간 기능은 WebSocket(양방향)이나 SSE(단방향)로 구현합니다. Svelte의 반응성 시스템과 결합하면 실시간 데이터가 UI에 자동으로 반영되어, 복잡한 상태 관리 없이도 실시간 앱을 만들 수 있습니다.
댓글 로딩 중...