본문으로 건너뛰기
김신건의 로그

[Network] WebSocket: HTTP upgrade, frame, ping/pong

· 수정 · 📖 약 4분 · 1,393자/단어 #websocket #network #realtime #backend
WebSocket, WS, WSS, RFC 6455, Upgrade: websocket, websocket frame, 웹소켓

정의

WebSocket (RFC 6455)은 HTTP/1.1 Upgrade로 시작하는 양방향 영속 메시지 채널이다. 하나의 TCP 연결 위에서 서버와 클라이언트가 임의 시점에 메시지를 push할 수 있다.

HTTP는 클라이언트가 요청해야 서버가 응답하는 단방향 구조다. WebSocket은 이 제약을 제거해 서버가 먼저 메시지를 보낼 수 있게 한다.

주요 활용: 실시간 채팅, 트레이딩 시세, 멀티플레이어 게임, 알림 시스템, 공동 편집, 라이브 대시보드.

Handshake: HTTP Upgrade

sequenceDiagram
    autonumber
    participant C as Client
    participant S as Server

    C->>S: GET /chat HTTP/1.1<br/>Host: example.com<br/>Upgrade: websocket<br/>Connection: Upgrade<br/>Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==<br/>Sec-WebSocket-Version: 13
    S-->>C: HTTP/1.1 101 Switching Protocols<br/>Upgrade: websocket<br/>Connection: Upgrade<br/>Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

    Note over C,S: 이후 같은 TCP 위에서 WS frame 양방향 교환
    C->>S: WS frame (text: "hello")
    S->>C: WS frame (서버 push: "world")
    C->>S: WS frame (binary: 이미지 데이터)

Sec-WebSocket-Accept는 클라이언트가 보낸 key와 고정 GUID를 결합한 SHA-1 해시의 Base64 인코딩이다:

Accept = Base64(SHA-1(Sec-WebSocket-Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))

이 절차는 프로토콜 착각(평범한 HTTP 응답으로 오해)을 방지하기 위한 핸드셰이크 무결성 검증이다.

Frame 구조

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len == 126/127) |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+-------------------------------+
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
|    Masking-key (continued)    |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+---------------------------------------------------------------+
필드크기설명
FIN1 bit마지막 fragment이면 1
RSV1-3각 1 bit확장에 예약 (압축 확장은 RSV1 사용)
opcode4 bit메시지 타입
MASK1 bit페이로드 마스킹 여부
Payload len7 bit125 이하면 직접 길이, 126이면 다음 2 bytes, 127이면 다음 8 bytes
Masking-key4 bytesMASK=1일 때만 존재
Payload Data가변실제 데이터 (마스킹된 경우 XOR 복호화)

Opcode 목록

opcode의미
0x0Continuation (이전 frame 이어짐)
0x1Text (UTF-8 인코딩)
0x2Binary
0x8Close
0x9Ping
0xAPong

IMPORTANT

클라이언트 발 frame은 반드시 MASK 처리 (XOR with 4-byte masking key). 캐시 포이즈닝 공격 방지 목적이다. 서버 발 frame은 unmask. 이를 위반하면 서버는 연결을 즉시 닫아야 한다.

Ping / Pong (keep-alive)

sequenceDiagram
    participant C as Client
    participant S as Server

    loop 30초마다
        S->>C: PING frame (opcode 0x9)
        C-->>S: PONG frame (opcode 0xA, payload 동일)
    end
    Note over S: 90초 PONG 없음 시 close frame 전송

Ping/Pong의 목적:

  • NAT 매핑 유지: NAT 장비는 유휴 UDP/TCP 연결을 일정 시간 후 만료. 주기적 ping으로 방지
  • 프록시 idle close 방지: nginx, ALB 같은 중간 장비의 idle timeout 초과 방지
  • 연결 생존 확인: 상대방이 살아있는지 확인 (silent disconnect 감지)

TIP

NAT 일반 idle timeout은 약 60-300초다. Ping 간격은 30-60초가 권장값이다. AWS ALB의 기본 idle timeout은 60초이므로 30초 이하 ping을 사용해야 한다.

Subprotocol

Sec-WebSocket-Protocol: chat, soap

클라이언트가 지원하는 서브프로토콜 목록을 전송하면, 서버는 그 중 하나를 선택해 응답 헤더에 반환한다.

HTTP/1.1 101 Switching Protocols
Sec-WebSocket-Protocol: chat

서브프로토콜로 흔히 사용하는 것:

  • STOMP (Simple Text Oriented Messaging Protocol): Spring WebSocket에서 주로 사용
  • MQTT over WebSocket: IoT 브로커 연결
  • GraphQL over WebSocket (graphql-ws, subscriptions-transport-ws)
  • WAMP (Web Application Messaging Protocol)

WSS (WebSocket Secure)

wss://는 WebSocket over TLS다. 운영 환경에서는 사실상 필수다:

  • ws:// 는 일부 ISP와 프록시가 차단 또는 내용 변조 가능
  • https:// 페이지에서 ws:// 연결은 브라우저가 mixed content로 거부
  • 대부분의 WAF와 로드 밸런서가 WSS에 최적화
flowchart LR
    Browser["브라우저\nwss://example.com/chat"]
    LB["로드 밸런서\n(TLS 종료)"]
    WS["WebSocket 서버\nws://10.0.0.5:8080"]

    Browser <-->|"wss (TLS)"| LB
    LB <-->|"ws (평문, 내부망)"| WS

실무에서는 로드 밸런서가 TLS를 종료하고 내부에서 평문 ws://로 전달하는 구성이 일반적이다.

실전 예시

Node.js (ws 라이브러리)

const WebSocket = require("ws");

// 서버
const wss = new WebSocket.Server({ port: 8080 });

wss.on("connection", (ws, req) => {
  console.log(`클라이언트 연결: ${req.socket.remoteAddress}`);

  // 30초마다 ping
  const pingInterval = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.ping();
    }
  }, 30000);

  ws.on("pong", () => {
    console.log("pong 수신, 연결 살아있음");
  });

  ws.on("message", (data) => {
    const message = data.toString();
    console.log(`수신: ${message}`);
    // 같은 채널 모든 클라이언트에게 broadcast
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({ text: message }));
      }
    });
  });

  ws.on("close", (code, reason) => {
    clearInterval(pingInterval);
    console.log(`연결 종료: ${code} ${reason}`);
  });
});

Python (websockets)

import asyncio
import websockets

async def handler(websocket, path):
    try:
        async for message in websocket:
            print(f"수신: {message}")
            await websocket.send(f"echo: {message}")
    except websockets.exceptions.ConnectionClosed:
        print("연결 종료")

async def main():
    # ping_interval=20: 20초마다 ping, ping_timeout=10: 10초 응답 없으면 close
    async with websockets.serve(handler, "localhost", 8080,
                                 ping_interval=20, ping_timeout=10):
        await asyncio.Future()  # 영원히 실행

asyncio.run(main())

브라우저 클라이언트

const ws = new WebSocket("wss://example.com/chat", ["chat"]); // 서브프로토콜 지정

ws.onopen = (event) => {
  console.log("연결됨, 서브프로토콜:", ws.protocol);
  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(`종료: code=${event.code}, reason=${event.reason}, clean=${event.wasClean}`);
  // 자동 재연결 (exponential backoff)
  setTimeout(() => reconnect(), 1000);
};

ws.onerror = (error) => {
  console.error("WebSocket 오류:", error);
};

// 명시적 종료 (1000: 정상 종료)
// ws.close(1000, "사용자 로그아웃");

패턴: 채팅 Fan-out

flowchart LR
    C1["Client 1"] -->|"ws"| N1["Server Node A"]
    C2["Client 2"] -->|"ws"| N2["Server Node B"]
    C3["Client 3"] -->|"ws"| N1

    N1 -->|"PUBLISH msg"| Redis[("Redis Pub/Sub\n또는 Streams")]
    N2 -->|"SUBSCRIBE"| Redis
    N1 -->|"SUBSCRIBE"| Redis

    Redis -->|"fan-out"| N1 & N2

단일 서버는 in-memory로 fan-out 가능하지만, 다중 서버 환경에서는 Redis Pub/Sub 또는 Streams로 노드 간 메시지를 전달해야 한다.

닫기 핸드셰이크

sequenceDiagram
    participant C as Client
    participant S as Server

    C->>S: Close frame (opcode 0x8, code=1000, reason="done")
    S-->>C: Close frame (echo back)
    Note over C,S: 양방향 TCP 연결 종료

Close 코드:

  • 1000: 정상 종료
  • 1001: Going Away (탭 닫기, 서버 재시작)
  • 1008: Policy Violation
  • 1011: Unexpected Error
  • 4000-4999: 애플리케이션 정의 코드 (사용자 정의)

대안 비교

기술방향영속성주요 용도
WebSocket양방향영속채팅, 게임, 트레이딩
SSE서버 -> 클라이언트영속알림, 라이브 피드
Long Polling양방향 (서버 hold)짧음레거시 fallback
HTTP/2 Push단방향 (폐기됨)--
WebRTC DataChannelP2P 양방향영속게임, 파일 공유
WebTransport양방향 다중스트림영속WebSocket 차세대 대안

자세한 비교는 실시간 기술 비교 참고.

함정

WARNING

  1. Idle timeout 사고: AWS ALB, nginx의 기본 idle timeout은 60초다. PING/PONG 간격이 60초 이상이면 연결이 silent close된다. 반드시 idle timeout보다 짧은 ping 간격 설정 필요.
  2. Backpressure 부재: 서버가 빠르게 send하면 클라이언트 수신 버퍼가 가득 찬다. 애플리케이션 레벨 flow control (ACK/credit 방식) 이 필요하다.
  3. Sticky Session 필요: 같은 사용자의 WebSocket 요청이 같은 서버 노드에 라우팅되어야 한다. LB cookie 또는 IP hash를 사용하거나, Redis pub/sub으로 모든 노드에 fan-out.
  4. CORS가 WebSocket에 적용 안 됨: Origin 헤더는 브라우저가 자동 설정하지만 서버가 수동으로 검증해야 한다. 미검증 시 CSRF 공격에 취약. ws.upgradeReq.headers.origin 를 허용 목록과 대조.
  5. 메모리 leak: 연결이 close될 때 이벤트 리스너, 타이머, 방 구독을 모두 해제해야 한다. ws.on("close") 핸들러에서 clearInterval, 구독 해제 필수.
  6. 대용량 이진 데이터: WebSocket 위에서 파일 전송 시 단편화(fragmentation)가 발생하면 조각을 합쳐야 한다. FIN 비트로 마지막 fragment 판단.

관련 위키

  • HTTP/1.1 - WebSocket upgrade handshake의 기반
  • TCP - WebSocket의 영속 연결 토대
  • TLS - WSS 암호화
  • 실시간 기술 비교 - WebSocket vs SSE vs Long Polling
  • SSE - 서버 단방향 영속 스트림
  • WebRTC - P2P 데이터 채널 (DTLS 기반)
이 글의 용어 (6개)
[Network] HTTP/1.1: keep-alive, pipelining, chunked transfernetwork
정의 HTTP/1.1 (1997, RFC 9112 으로 재정리) 는 텍스트 기반 stateless request/response 프로토콜. 2026 시점에도 대부분의 트래픽이 H…
[Network] Realtime 전송 비교: Polling / SSE / WebSocket / WebRTCnetwork
정의 실시간 (서버 → 클라이언트) 데이터 전송 의 7가지 패턴 비교. 결정 트리 7가지 비교 | 패턴 | 방향 | 영속 | 프로토콜 | 사용 | |---|---|---|---|…
[Network] SSE (Server-Sent Events): 단방향 영속 스트림network
정의 Server-Sent Events (SSE) 는 HTTP 위 단방향 (서버 → 클라이언트) 영속 스트림. MIME + 클라이언트의 API. 장점: HTTP/1.1 위에서 동…
[Voice AI] WebRTC: PeerConnection, ICE, STUN/TURN, SFUnetwork
정의 WebRTC = 브라우저 / 모바일 P2P 실시간 미디어 + 데이터 표준. 음성, 비디오, 데이터 채널. 2026 시점 voice agent + 영상 통화 + 화면 공유 의…
TCPnetwork
정의 TCP (Transmission Control Protocol) 는 신뢰성 있는 연결 지향 전송 계층 프로토콜이다. RFC 9293 으로 정의되어 있다. HTTP/1.1·2…
TLS / SSLnetwork
정의 TLS (Transport Layer Security) 는 (또는 위 ) 위에서 동작하는 암호화·인증 프로토콜이다. SSL 은 TLS 의 전신 (Netscape 1995, …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기