HTTP는 클라이언트가 물어봐야 대답하지만, WebSocket은 서버가 먼저 말을 걸 수 있습니다.

개념 정의

WebSocket 은 클라이언트와 서버 간 전이중(full-duplex) 통신을 제공하는 프로토콜입니다. HTTP와 달리 한 번 연결되면 양쪽에서 자유롭게 메시지를 주고받을 수 있어, 실시간 기능에 적합합니다.

Svelte에서 WebSocket 클라이언트

SVELTE
<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

JAVASCRIPT
// 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가 더 간단합니다.

JAVASCRIPT
// 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',
    }
  });
}
SVELTE
<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에 자동으로 반영되어, 복잡한 상태 관리 없이도 실시간 앱을 만들 수 있습니다.

댓글 로딩 중...