실시간 통신을 구현하는 방법은 다양하지만 양방향 통신 및 효율성 측면을 고려하여 WebSocket을 선택하였습니다
| 기술 | 통신 방향 | 특징 |
|---|---|---|
| HTTP Polling | 단방향 | 주기적인 서버 요청으로 데이터 확인 (오버헤드 발생) |
| SSE (Server-Sent Events) | 서버 → 클라이언트 | 연결을 유지하며 서버로부터 데이터 수신만 가능 (클라이언트 응답 시 별도 HTTP 요청 필요) |
| WebSocket | 양방향 | 한 번의 연결로 자유로운 양방향 데이터 송수신 가능 |
웹소켓 연결은 Upgrade 메커니즘을 통해 이루어지며 크게 '연결 확립'과 '양방향 통신', '연결 종료' 단계로 나뉩니다

wss라면 TLS 핸드쉐이크를 통해 암호화된 세션 구성Upgrade: websocket, Connection: Upgrade 헤더와 함께 랜덤 생성된 Sec-WebSocket-Key를 포함GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Sec-WebSocket-Key에 Magic String을 더해 SHA-1 해싱Sec-WebSocket-Accept 헤더에 담고 HTTP 101 Switching Protocols 상태 코드로 응답HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
Accept + Magic String 사용하는 이유
중간 프록시 서버가101 Switching Protocols응답을 캐싱하여 클라이언트에게 잘못된 연결 정보를 제공하는 상황을 방지합니다. 클라이언트는 이Sec-WebSocket-Accept값을 검증함으로써 서버가 웹소켓 표준을 정확히 구현하였음을 확인하고, 안전하게 데이터를 전송할 수 있습니다.
클라이언트가 서버의 Sec-WebSocket-Accept 키를 성공적으로 검증하면 통신프로토콜은 WebSocket으로 전환됨
메시지를 프레임 단위로 나누어 데이터를 주고받습니다
FIN (1 bit): 이 프레임이 전체 메시지의 마지막 조각인지 여부
1: 마지막 프레임 (단일 프레임 메시지 포함)0: 뒤에 프레임이 더 있음 (메시지 분할 전송 시)RSV1, 2, 3 (각 1 bit): 확장을 위해 예약된 비트로 확장이 사용되지 않으면 0
Opcode (4 bits): 전송되는 payload 데이터 유형을 정의
0x0: 이전 프레임과 이어짐0x1: Text (UTF-8)0x2: Binary0x8: 연결 종료0x9: Ping0xA: PongMask (1 bit): payload 데이터가 마스킹(암호화) 여부
1이어야 함 (보안상의 이유)0이어야 함Payload Length (7 bits): payload 데이터의 길이를 나타내며 이 7비트의 값에 따라 실제 길이 표현 방식이 달라짐
Masking-Key (32 bits): Mask 비트가 1일 때만 존재하며 클라이언트가 데이터를 전송할 때 사용하는 4바이트의 랜덤 키입니다.
Payload Data: 실제 전송하려는 데이터 본문
🤔왜 최소 오버헤드인가?
- HTTP: 최소 수백 바이트의 텍스트 기반 헤더를 매번 보냄
- WebSocket: HTTP와 달리 프레임 앞에 최소 2바이트의 헤더만으로 통신이 가능하여 HTTP보다 오버헤드가 줄어듦
🤔 왜 클라이언트 → 서버는 마스킹하고 그 반대는 안할까?
- 중간 프록시 서버가 웹소켓 데이터를 HTTP 요청으로 오해하여 캐시를 오염시키는 것을 방지하기 위함
- 반면 서버는 신뢰할 수 있는 주체로 간주되며 수많은 클라이언트에게 데이터를 보낼 때 발생하는 CPU 부하를 줄이기 위해 서버 측 마스킹은 수행하지 않음
Close Frame을 보내고 상대방도 Close Frame 응답Close Frame 내 Status Code를 통해 종료 원인(예: 1000 - 정상 종료, 1001 - 서버 재시작, 1006 - 비정상 종료)을 전달