websocket 통신이 실제로 어떻게 이루어지는지, 그 기본 단위인 메시지 프레임에 대해 깊이 파헤쳐 보려고 한다. 전통적인 웹의 HTTP 요청-응답 모델은 실시간 양방향 통신에는 한계가 명확했다. WebSocket(RFC 6455)은 이 문제를 해결하기 위해 등장했고, 단일 TCP 연결 위에서 지속적인 양방향 통신 채널을 제공한다.
TCP 프로토콜 그 자체로는 '메시지'의 경계를 모르는 바이트 스트림 프로토콜이다. 즉 보내는 쪽에서 메시지를 나눠 보내도 받는 쪽은 그냥 연속된 데이터로 받게 된다. 여기서 WebSocket은 어떻게 "여기서부터 여기까지가 하나의 메시지야!!"라고 알려줄 수 있을까?
바로 WebSocket 프레이밍 프로토콜 덕분인데 이번엔 RFC 6455 표준, 특히 WebSocket 메시지 프레임 구조와 그 안에 담긴 정보들을 확인해 보려고 한다.
WebSocket 핸드셰이크가 성공하면, 이후 모든 통신은 표준화된 '프레임'단위로 이루어진다. 이 프레임은 헤더와 페이로드로 구성되며, 헤더에는 중요한 메타 정보가 담겨 있다. RFC 6455에 정의된 구조는 다음과 같다.

프레임의 헤더 필드를 보면 다양한게 있는데 하나씩 알아가보자.
여기서 초기 7비트라 하면 0~127이어야 하는데 왜 125일까? 이유는 7비트 필드 내에서 특정 값을 기준으로 가변 길이 인코딩을 효율적으로 구현하기 위함에 초기의 값은 125까지 사용가능하며 126이면 추가로 2바이트의 값을 더한 길이를 사용하겠다라는 의미고 127이면 추가로 8바이트를 더 사용하겠다라는 의미다 그래서 헤더의 길이가 가변적이고 최소 2바이트에서 최대 14바이트까지 될 수 있다.
Opcode는 프레임의 성격을 규정한다. 크게 데이터 프레임과 제어 프레임으로 나뉜다.
이러한 규칙들은 제어 프레임이 복잡한 처리 없이 빠르고 안정적으로 처리되도록 보장하여, 연결 종료나 상태 확인 같은 중요한 작업을 신속하게 수행하게 한다.
메시지가 너무 클 경우 WebSocket은 이를 여러 프레임으로 조각내어 보낼 수 있다. 왜 조각화가 필요할까?
WebSocket의 흥미로운 규칙중 하나는 클라이언트가 서버로 보내는 모든 프레임은 반드시 페이로드를 마스킹해야된다는 점이다. 반면 서버는 마스킹되지 않은 프레임을 보낸다.
마스킹 알고리즘
1. 클라이언트는 각 프레임마다 무작위 4바이트 마스킹 키를 생성하여 헤더에 포함한다.
2. 페이로드의 각 바이트를 마스킹 키의 해당 바이트와 XOR 연산하여 변환된 데이터를 전송한다. (키는 순환적으로 사용)
언마스킹(서버)
서버는 프레임 헤더에서 마스킹 키를 읽어, 수신된 페이로드 데이터에 동일한 XOR 연산을 적용하여 원본 데이터를 복원한다.
이유가 뭘까?
왜 이렇게 복잡한 과정으로 처리를 하게 될까? 주된 이유는 중간 프록시 서버의 캐시 오염 공격을 방지하기 위해서이다. 과거 일부 프록시는 WebSocket을 잘 몰라서, 특정 바이트 패턴을 보고 HTTP 트래픽으로 오인하여 잘못 캐싱할 수 있었다. 악의적인 클라이언트가 조작된 WebSocket프레임을 보내면 프록시가 이를 잘못된 내용으로 캐싱하여 다른 사용자에게 영향을 줄 수 있었다.
하지만 마스킹은 매번 다른 랜덤 키로 페이로드 내용을 변형시켜, 동일한 데이터라도 네트워크 상에서는 매번 다른 바이트 패턴으로 보이게 한다. 이렇게 하면 프록시가 내용을 예측하거나 특정 패턴 기반으로 오작동하기 어렵게 만들어 캐시 오염 위험을 크게 줄인다.
이러한 마스킹은 보안 연결(WSS, TLS/SSL) 시에도 필수적으로 사용된다고 한다. TLS는 채널 전체를 암호화하지만 마스킹은 암호화된 터널 내부에서 WebSocket 프로토콜 자체를 대상으로 하는 특정 프록시 관련 공격을 막는 추가적인 보호 계층이다.
WebSocket 프레임을 수신하고 처리하는 것은 개념적으로 다음과 같은 과정을 거치게 된다.
1. 헤더읽기
2. FIN, RSV, Opcode, MASK, Payload length 해석
3. 마스킹 키 읽기
4. Payload length 만큼 페이로드 데이터 읽기
5. 언마스킹 수행
6. Opcode에 따라 처리
이러한 조각화된 메시지의 상태나 다양한 프로토콜 오류 처리 등을 저수준에서 라이브러리가 제공해주기 때문에 이 복잡한 과정을 구현안해줘도 된다. 그러나 이러한 프레이밍 메커니즘을 이해하는 것은 네트워크 문제 해결, 성능 최적화, 추가적인 고급 기능을 구현할 때 매우 중요하다고 볼 수 있다.
| 참고자료 |
** websocket에 대한 내용이 더 궁금하다면 다음 사이트를 참고하자
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers