안동민 개발노트

본문 시작

WebSocket과 실시간 통신

폴링·SSE·WebSocket의 통신 방향과 비용을 비교하고 핸드셰이크·프레임·연결 관리로 실시간 기능을 구현합니다.

HTTP는 기본적으로 클라이언트가 요청을 열고 서버가 응답을 돌려주는 모델입니다.

서버가 브라우저 쪽에 임의의 순간 새 연결을 열어 데이터를 밀어 넣는 구조가 아니기 때문에, 채팅, 실시간 알림, 주식 시세처럼 지연이 짧은 양방향 통신에서는 단순한 요청-응답만으로 한계가 드러납니다.


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

짧은 주기로 계속 물어보는 polling은 구현이 쉽지만 빈 응답과 HTTP 헤더 비용이 큽니다.

Long polling은 서버가 새 이벤트가 생길 때까지 응답을 늦춰 낭비를 줄이지만, 응답이 끝날 때마다 다시 요청을 열어야 합니다.

SSE는 서버에서 클라이언트로 흐르는 이벤트 스트림에 잘 맞고, WebSocket은 한 번 연결한 뒤 같은 연결에서 양쪽이 독립적으로 메시지를 보낼 수 있습니다.


WebSocket 핸드셰이크

브라우저의 WebSocket 연결은 보통 HTTP/1.1 Upgrade 메커니즘으로 시작합니다.

wss://를 쓰면 이 핸드셰이크도 TLS 연결 위에서 이뤄집니다.

HTTP/1.1 Upgrade 뒤 같은 연결의 WebSocket 메시지

HTTP/1.1 WebSocket 핸드셰이크가 성공한 뒤 클라이언트와 서버가 각각 텍스트 메시지를 보내는 예시입니다. HTTP/2와 HTTP/3의 Extended CONNECT 경로는 제외합니다. 애플리케이션 메시지 두 개가 반드시 요청과 응답 한 쌍이라는 뜻은 아닙니다.

HTTP/1.1 Upgrade 뒤 같은 연결의 WebSocket 메시지HTTP/1.1 WebSocket 핸드셰이크가 성공한 뒤 클라이언트와 서버가 각각 텍스트 메시지를 보내는 예시입니다. HTTP/2와 HTTP/3의 Extended CONNECT 경로는 제외합니다. 애플리케이션 메시지 두 개가 반드시 요청과 응답 한 쌍이라는 뜻은 아닙니다.GET /chat HTTP/1.1Upgrade: websocket101 Switching ProtocolsSec-WebSocket-Accept 검증텍스트 메시지클라이언트 → 서버 · 마스킹텍스트 메시지서버 → 클라이언트클라이언트서버
HTTP/1.1 Upgrade 뒤 같은 연결의 WebSocket 메시지HTTP/1.1 WebSocket 핸드셰이크가 성공한 뒤 클라이언트와 서버가 각각 텍스트 메시지를 보내는 예시입니다. HTTP/2와 HTTP/3의 Extended CONNECT 경로는 제외합니다. 애플리케이션 메시지 두 개가 반드시 요청과 응답 한 쌍이라는 뜻은 아닙니다.GET /chat HTTP/1.1Upgrade: websocket101 Switching ProtocolsSec-WebSocket-Accept 검증텍스트 메시지클라이언트 → 서버 · 마스킹텍스트 메시지서버 → 클라이언트클라이언트서버

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

이 값은 서버가 WebSocket 핸드셰이크를 이해했는지 확인하기 위한 장치이지, 사용자 인증이나 암호화 기능은 아닙니다.

HTTP/2에서는 일반적인 Upgrade 헤더 대신 RFC 8441의 Extended CONNECT 방식으로 WebSocket을 시작할 수 있습니다.

다만 클라이언트, 서버, 프록시, 로드 밸런서가 모두 지원해야 하므로 실무에서는 인프라 지원 여부를 확인해야 합니다.


WebSocket 프레임 구조

WebSocket은 메시지를 하나 이상의 프레임으로 나눠 보낼 수 있습니다.

클라이언트가 서버로 보내는 프레임은 마스킹이 필수이며, 이는 중간 장비 오동작을 줄이기 위한 프로토콜 장치이지 암호화가 아닙니다.

Opcode의미
0x0Continuation: 앞서 시작한 메시지의 연속 프레임
0x1Text: UTF-8 텍스트 메시지
0x2Binary: 바이너리 메시지
0x8Close: 연결 종료
0x9Ping: 연결 확인 등의 제어 메시지
0xAPong: Ping 응답 또는 비요청 알림

제어 프레임은 분할할 수 없고 페이로드는 최대 125바이트입니다.


WebSocket 구현

websocket_server.py
import asyncio
import websockets
from websockets.exceptions import ConnectionClosed

connected = set()

async def handler(websocket):
    connected.add(websocket)
    try:
        async for message in websocket:
            # 보낸 사람을 제외한 클라이언트에게 순차 전송
            for ws in connected.copy():
                if ws != websocket:
                    try:
                        await ws.send(message)
                    except ConnectionClosed:
                        pass
    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())

접속 집합을 복사해 await 사이의 접속·종료가 순회를 깨뜨리지 않게 하고, 복사 후 종료된 수신자는 건너뜁니다. 공식 broadcast 문서에서 설명하듯 순차 send는 느린 수신자의 버퍼가 비워질 때까지 다른 전송도 기다립니다. 이 학습 예제에는 인증·Origin 검사·재연결·메시지 재생이나 수신자별 대기열 정책이 구현되어 있지 않습니다.

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)는 HTTP 응답을 text/event-stream으로 유지하면서 서버에서 클라이언트로 이벤트를 흘려보냅니다.

SSE는 브라우저의 EventSource가 재연결과 Last-Event-ID 전달을 도와주므로 알림·피드·진행률에 적합합니다. 누락 이벤트 복구에는 서버의 ID 부여·보관·재생 처리가 별도로 필요합니다.

반대로 클라이언트도 높은 빈도로 메시지를 보내야 하거나 바이너리 데이터를 주고받아야 한다면 WebSocket이 더 자연스럽습니다.

방식방향·연결·복구와 사용처
WebSocketws://·wss:// 연결에서 양방향 텍스트·바이너리를 주고받습니다. 채팅·게임·협업에 적합하며 재연결은 애플리케이션이 구현합니다. HTTP/2·3 전송에는 인프라 지원이 필요합니다.
SSE일반 HTTP 응답의 UTF-8 이벤트 스트림입니다. 브라우저가 재연결을 돕고 알림·피드에 적합하지만, 누락 복구는 서버 재생 정책에 달려 있습니다.
Long pollingHTTP 응답이 끝나면 새 요청을 엽니다. 응답 형식에 따라 바이너리도 가능하며 이전 환경과의 호환에 활용합니다.

세 방식 모두 프록시의 버퍼링·유휴 시간 제한·연결 종료 정책을 확인해야 합니다. SSE라는 이유만으로 즉시 전달이 보장되지는 않습니다.

Socket.IO의 역할

Socket.IO는 단순한 WebSocket wrapper라기보다 자체 클라이언트와 서버 프로토콜을 가진 실시간 통신 라이브러리입니다.

Socket.IO의 기본 연결 순서는 HTTP long polling으로 연결한 뒤 WebSocket 업그레이드를 시도하는 방식입니다. 현재 지원 전송에는 WebTransport도 있으며, 순서와 대체 시도는 클라이언트 옵션·서버 지원에 따라 달라집니다.

Socket.IO는 재연결, heartbeat, 방(room), namespace, acknowledgement 같은 기능을 제공하지만, 순수 WebSocket 클라이언트와 그대로 호환되는 프로토콜은 아닙니다.

양쪽 모두 Socket.IO를 쓰는 애플리케이션인지, 아니면 표준 WebSocket API만 필요한지 먼저 구분해야 합니다.

운영에서는 heartbeat와 유휴 제한, 재접속 간격, 인증 만료 시 연결 종료, 메시지 ID에 의한 중복 처리 여부를 함께 정합니다. TCP 연결 유지나 재연결 성공은 메시지 전달 완료와 별개입니다.

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