icon

안동민 개발노트

12장 : HTTP/2, HTTP/3, WebSocket

WebSocket과 실시간 통신


HTTP는 클라이언트가 요청하면 서버가 응답하는 단방향 모델입니다. 서버가 먼저 클라이언트에게 데이터를 보낼 수 없습니다. 채팅, 실시간 알림, 주식 시세처럼 서버에서 클라이언트로 즉각적으로 데이터를 밀어넣어야 하는 경우, HTTP의 요청-응답 모델은 근본적으로 적합하지 않습니다.


HTTP로 실시간을 흉내내는 방법들

실시간 통신 기법 진화
폴링 (Polling)
  Client: "새 메시지?"  Server: "없음"     (1초 후)
  Client: "새 메시지?"  Server: "없음"     (1초 후)
  Client: "새 메시지?"  Server: "있음!"    ← 90% 낭비
  → 불필요한 요청으로 서버 부하

롱 폴링 (Long Polling)
  Client: "새 메시지?" ─────────→ Server: (대기...)
                                          (30초간 대기)
                       ←───────── Server: "메시지 도착!"
  Client: "다음 메시지?" ────────→ Server: (대기...)
  → 폴링보다 효율적이지만, 매번 HTTP 연결 재수립

WebSocket
  Client ──── HTTP Upgrade ────→ Server
  Client ←════ 양방향 프레임 ════→ Server (영구 연결)
  → 프로토콜 레벨에서 양방향 지원

WebSocket 핸드셰이크

WebSocket 연결은 HTTP Upgrade 메커니즘으로 시작합니다.

WebSocket 핸드셰이크 과정
클라이언트 ──────────────────→ 서버
  GET /chat HTTP/1.1
  Host: example.com
  Upgrade: websocket            ← 프로토콜 전환 요청
  Connection: Upgrade
  Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
  Sec-WebSocket-Version: 13

서버 ──────────────────────→ 클라이언트
  HTTP/1.1 101 Switching Protocols
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

  이후: HTTP가 아닌 WebSocket 프레임 전송
  ┌────────────────────────────────────┐
  │ 같은 TCP 연결 위에서               │
  │ 클라이언트 ←── 프레임 ──→ 서버     │
  │           양방향, 전이중           │
  └────────────────────────────────────┘

Sec-WebSocket-Accept는 클라이언트가 보낸 키에 매직 문자열 258EAFA5-E914-47DA-95CA-C5AB0DC85B11을 결합하고 SHA-1 해시를 취한 값입니다.


WebSocket 프레임 구조

WebSocket vs HTTP 오버헤드 비교
HTTP 요청 (매번)
  GET /api/data HTTP/1.1\r\n
  Host: example.com\r\n
  Authorization: Bearer xxx...\r\n
  Content-Type: application/json\r\n
  Cookie: session=abc123\r\n
  \r\n
  → 헤더만 200~800 bytes

WebSocket 프레임
  ┌──────┬────────┬──────────────┐
  │ FIN  │ Opcode │ Payload Len  │  2~14 bytes
  │ 1bit │ 4bits  │ 7+bits       │
  ├──────┴────────┴──────────────┤
  │ Masking Key (클라이언트만)   │  4 bytes
  ├──────────────────────────────┤
  │ Payload Data                 │
  └──────────────────────────────┘
  → 오버헤드 2~14 bytes!

1초마다 작은 메시지 전송 시
  HTTP:      200 bytes * 60/분 = 12KB/분
  WebSocket: 6 bytes * 60/분 = 360B/분
  → 33배 차이
Opcode프레임 유형용도
0x0Continuation이전 프레임의 계속
0x1TextUTF-8 텍스트 메시지
0x2Binary바이너리 데이터
0x8Close연결 종료
0x9Ping연결 상태 확인
0xAPongPing에 대한 응답

WebSocket 구현

websocket_server.py
import asyncio
import websockets

connected = set()

async def handler(websocket):
    connected.add(websocket)
    try:
        async for message in websocket:
            # 받은 메시지를 모든 클라이언트에게 브로드캐스트
            for ws in connected:
                if ws != websocket:
                    await ws.send(message)
    finally:
        connected.discard(websocket)

async def main():
    async with websockets.serve(handler, "0.0.0.0", 8080):
        print("WebSocket server on ws://0.0.0.0:8080")
        await asyncio.Future()  # 서버 유지

asyncio.run(main())
websocket_client.js
const ws = new WebSocket("ws://localhost:8080");

ws.onopen = () => {
  console.log("Connected");
  ws.send("Hello!");
};

ws.onmessage = (event) => {
  console.log("Received:", event.data);
};

ws.onclose = () => {
  console.log("Disconnected");
};

ws.onerror = (error) => {
  console.error("Error:", error);
};

SSE (Server-Sent Events)

모든 실시간 통신에 WebSocket이 필요한 것은 아닙니다. SSE(Server-Sent Events)서버에서 클라이언트로의 단방향 스트리밍을 지원합니다.

SSE vs WebSocket 비교
SSE (단방향: 서버 → 클라이언트)
  Client ────── HTTP GET ─────→ Server
  Client ←── data: event1\n\n ── Server
  Client ←── data: event2\n\n ── Server
  Client ←── data: event3\n\n ── Server
  (HTTP 연결 유지, 서버가 계속 push)

WebSocket (양방향)
  Client ←═══ 프레임 ═══→ Server
  Client ←═══ 프레임 ═══→ Server
  (양쪽 모두 언제든 전송 가능)
항목WebSocketSSELong Polling
방향양방향서버→클라이언트서버→클라이언트
프로토콜ws://HTTPHTTP
자동 재연결직접 구현내장직접 구현
바이너리지원텍스트만지원
HTTP/2 호환별도완벽완벽
프록시 호환낮음높음높음
적합한 사례채팅, 게임알림, 피드레거시 호환

Socket.IO의 역할

Socket.IO는 WebSocket을 감싸는 라이브러리로, 실시간 통신의 실무적인 문제들을 해결합니다.

Socket.IO 기능
Socket.IO가 해결하는 문제
  1. 폴백: WebSocket 불가 시 → Long Polling 자동 전환
  2. 재연결: 끊어지면 자동 재연결 + 큐잉
  3. 방(Room): 특정 그룹에만 메시지 전송
  4. 네임스페이스: /chat, /game 등 논리적 분리
  5. ACK: 메시지 수신 확인

주의: Socket.IO ≠ WebSocket
  Socket.IO 서버 ←✗→ 일반 WebSocket 클라이언트
  자체 프로토콜 사용, 양쪽 다 Socket.IO 필요

선택 기준
  순수 WebSocket
    * 표준 호환 필요
    * 번들 크기 최소화
    * 직접 제어 원함

  Socket.IO
    * 빠른 개발
    * 폴백/재연결 자동화
    * Room/네임스페이스 필요

다음 장에서는 코드를 넘어 실무 네트워크 인프라 — CDN, 로드 밸런서, 프록시, 클라우드 네트워크 — 를 다루겠습니다.

목차