웹 소켓(WebSocket)

Series: 네트워크

네트워크contains 4

들어가며

HTTP와 HTTPS를 통해 웹에서 데이터가 어떻게 전달되는지를 이해했다면, 자연스럽게 한 가지 의문이 생긴다. 우리가 사용하는 대부분의 웹 통신은 요청을 보내고 응답을 받는 구조인데, 그렇다면 실시간으로 데이터를 주고받는 기능은 어떻게 구현되는 걸까?

예를 들어 채팅 서비스를 생각해보자. 상대방이 메시지를 보내면 즉시 화면에 나타나야 한다. 주식 가격이나 게임 상태처럼 계속해서 변화하는 데이터를 실시간으로 반영해야 하는 경우도 있다. 이런 기능을 단순한 HTTP 요청-응답 구조로 구현하려고 하면 생각보다 비효율적인 방법을 사용해야 한다. 이 문제를 해결하기 위해 등장한 것이 WebSocket이다. 이 글에서는 HTTP의 한계를 출발점으로, WebSocket이 왜 필요하게 되었는지, 그리고 실제로 어떻게 동작하는지를 흐름 중심으로 정리해보려고 한다.

1. HTTP 통신의 한계

1.1 요청-응답 구조의 특징

HTTP는 기본적으로 요청과 응답이라는 구조를 가진다. 클라이언트가 먼저 요청을 보내야 하고, 서버는 그 요청에 대한 응답을 반환한다. 이 구조는 단순하고 이해하기 쉽지만, 한 가지 중요한 특징이 있다. 항상 클라이언트가 먼저 시작해야 한다는 점이다.

sequenceDiagram
    participant Client as 클라이언트
    participant Server as 서버

    Client->>Server: 요청
    Server-->>Client: 응답

서버는 클라이언트가 요청을 보내기 전까지는 아무런 데이터를 보낼 수 없다. 즉, 서버가 먼저 데이터를 전달하는 구조가 아니라는 점이 HTTP의 기본적인 제약이다. 이 구조는 일반적인 웹 페이지 조회, API 통신에는 문제가 없다. 하지만 실시간 통신이 필요한 상황에서는 문제가 된다. 서버에서 새로운 데이터가 발생하더라도, 클라이언트가 요청을 보내지 않으면 이를 전달할 방법이 없다. 예를 들어 채팅 메시지가 서버에 도착했을 때, 서버는 이 사실을 클라이언트에게 즉시 전달하고 싶지만 HTTP 구조에서는 이를 직접적으로 수행할 수 없다. 결국 클라이언트가 주기적으로 서버에게 '새로운 데이터가 있나요?'라고 물어봐야 하는 구조가 된다.

1.2 Polling

앞에서 살펴본 것처럼 HTTP는 기본적으로 클라이언트가 먼저 요청을 보내야만 서버가 응답을 할 수 있는 구조를 가진다. 이 구조는 단순한 데이터 조회에는 문제가 없지만, 실시간으로 변화하는 데이터를 처리해야 하는 상황에서는 큰 제약이 된다. 서버에서 새로운 데이터가 발생하더라도, 클라이언트가 요청을 보내지 않는 한 이를 전달할 방법이 없기 때문이다. 이 문제를 해결하기 위해 가장 먼저 사용된 방식이 Polling이다. Polling은 말 그대로 '주기적으로 확인한다'는 개념으로, 클라이언트가 일정한 간격을 두고 서버에 계속 요청을 보내는 방식이다. 예를 들어 1초마다 한 번씩 서버에 요청을 보내서 새로운 데이터가 있는지를 확인하는 식이다.

sequenceDiagram
    participant Client
    participant Server

    loop 일정 주기 반복
        Client->>Server: 데이터 요청
        Server-->>Client: 응답
    end

이 방식은 구현이 매우 단순하다. 기존 HTTP 구조를 그대로 사용하면서도 어느 정도 실시간에 가까운 동작을 만들 수 있기 때문이다. 서버 입장에서도 특별한 처리를 할 필요 없이, 요청이 오면 현재 상태를 반환해주면 된다. 하지만 이 구조에는 명확한 비효율이 존재한다. 이를테면, 채팅 서비스에서 어떤 사용자가 1개월 단위로 답장을 한다고 가정해보자. 그러면 29일 동안 매 1분마다의 요청은 새로운 데이터가 없음이라는 동일한 응답을 반복하게 된다. 즉, 실제로 데이터가 변경되지 않았음에도 불구하고 계속해서 요청과 응답이 발생한다. 요청 간격을 짧게 잡으면 실시간성은 좋아지지만 서버 부하와 네트워크 트래픽이 증가하고, 간격을 길게 잡으면 트래픽은 줄어들지만 실시간성이 떨어진다. 결국 이 방식은 성능과 실시간성 사이에서 타협할 수밖에 없는 구조를 가진다.

1.3. Long Polling

앞서 살펴본 Polling의 문제점을 개선하기 위해 등장한 방식이 Long Polling이다. Long Polling은 기본 아이디어는 동일하지만, 요청과 응답의 타이밍을 다르게 가져간다. 클라이언트가 요청을 보내면 서버는 즉시 응답하지 않고, 새로운 데이터가 생길 때까지 기다린다. 데이터가 발생하면 그때 응답을 보내고, 클라이언트는 다시 새로운 요청을 보내는 방식이다.

sequenceDiagram
    participant Client
    participant Server

    Client->>Server: 요청
    Note over Server: 데이터 발생까지 대기
    Server-->>Client: 응답

이 방식은 Polling보다 훨씬 효율적이다. 불필요한 요청 횟수를 줄일 수 있고, 데이터가 발생했을 때 거의 즉시 응답을 받을 수 있기 때문에 실시간성도 개선된다. 특히 데이터가 자주 발생하지 않는 환경에서는 Polling보다 훨씬 적은 네트워크 비용으로 동작할 수 있다. 하지만 Long Polling 역시 근본적인 한계를 완전히 해결하지는 못한다. 여전히 HTTP의 요청-응답 구조 위에서 동작하기 때문에, 응답이 끝나면 연결이 종료되고 클라이언트는 다시 요청을 보내야 한다. 즉, 하나의 지속적인 연결이 유지되는 것이 아니라, 요청과 응답이 반복되는 형태를 유지한다. 또한 서버 입장에서는 여러 클라이언트의 요청을 오랫동안 대기 상태로 유지해야 하기 때문에 자원 관리 측면에서도 부담이 될 수 있다. 결국 Polling과 Long Polling은 HTTP 구조 안에서 실시간 통신을 구현하기 위한 일종의 우회적인 방법이라고 볼 수 있다. 어느 정도 문제를 완화할 수는 있지만, 구조적인 한계를 완전히 해결하지는 못한다. 이러한 한계가 쌓이면서, 아예 새로운 통신 방식이 필요해졌고, 그 결과로 등장한 것이 WebSocket이다.

2. WebSocket

HTTP 기반 방식들은 결국 클라이언트가 먼저 요청해야 한다는 구조적인 한계를 가지고 있다. 이 한계를 해결하기 위해서는 아예 다른 방식의 통신 구조가 필요했다. 바로 한 번 연결을 맺은 이후에는 양쪽이 자유롭게 데이터를 주고받을 수 있는 방식이다. 이러한 요구를 해결하기 위해 등장한 것이 WebSocket이다. WebSocket은 HTTP와는 다른 통신 모델을 제공하며, 한 번 연결이 수립되면 그 연결을 유지한 상태로 데이터를 주고받는다. WebSocket은 클라이언트와 서버 간에 지속적인 연결을 유지하면서 양방향 통신을 가능하게 하는 프로토콜이다. HTTP와 달리 요청과 응답이 한 쌍으로 묶이지 않고, 연결이 유지되는 동안 양쪽이 언제든지 데이터를 보낼 수 있다.

sequenceDiagram
    participant Client
    participant Server

    Client->>Server: 연결 요청
    Server-->>Client: 연결 수립

    Client->>Server: 데이터 전송
    Server->>Client: 데이터 전송

이 구조에서는 클라이언트뿐만 아니라 서버도 자유롭게 데이터를 보낼 수 있다. 즉, 실시간 통신이 자연스럽게 가능해진다. HTTP는 요청-응답 기반의 단방향 흐름에 가까운 구조를 가진다. 반면 WebSocket은 연결을 유지하면서 양방향으로 데이터를 주고받는다. 또한 HTTP는 요청마다 연결이 생성되거나 관리되지만, WebSocket은 하나의 연결을 지속적으로 유지한다는 차이가 있다. 이 차이는 단순한 구조의 차이를 넘어서, 구현 방식과 성능에도 큰 영향을 미친다.

3. WebSocket 동작 과정

3.1 HTTP Handshake(Upgrade 요청)

WebSocket은 완전히 새로운 프로토콜이지만, 연결을 시작하는 방식은 의외로 HTTP에서 출발한다. 이는 기존 웹 환경과의 호환성을 고려한 설계이다. 브라우저와 서버는 이미 HTTP 통신을 기본으로 사용하고 있기 때문에, 새로운 프로토콜을 바로 사용하는 대신 기존 HTTP 연결을 활용하여 시작하는 방식을 선택한 것이다. 클라이언트는 서버에게 '이 연결을 WebSocket으로 바꾸자'는 요청을 보내는데, 이를 Upgrade 요청이라고 한다. 이 요청은 일반적인 HTTP 요청 형태를 그대로 사용하지만, 헤더에 Upgrade 정보를 포함한다.

GET /chat HTTP/1.1
Connection: Upgrade
Upgrade: websocket

이 메시지의 의미는 단순하다. 기존 HTTP 연결을 유지한 상태에서, 이후 통신 방식을 WebSocket으로 변경하겠다는 요청이다. 즉, 처음에는 HTTP로 시작하지만, 그 이후에는 전혀 다른 방식으로 통신하겠다는 선언이라고 볼 수 있다. 이 방식이 중요한 이유는 WebSocket이 HTTP 위에서 자연스럽게 동작할 수 있게 해주기 때문이다. 별도의 포트를 열거나 완전히 새로운 연결 방식을 사용할 필요 없이 기존 HTTP 인프라를 그대로 활용할 수 있다.

3.2 WebSocket 연결 수립 과정

서버가 Upgrade 요청을 수락하면, HTTP 연결은 WebSocket 연결로 전환된다. 이때 서버는 상태 코드 101(Switching Protocols)을 응답으로 보내면서 프로토콜 변경을 승인한다.

sequenceDiagram
    participant Client
    participant Server

    Client->>Server: HTTP Upgrade 요청
    Server-->>Client: 101 Switching Protocols

    Note over Client,Server: WebSocket 연결 수립

이 순간부터 클라이언트와 서버는 더 이상 HTTP 규칙을 따르지 않는다. 기존에는 요청을 보내고 응답을 기다리는 구조였다면, 이제는 하나의 연결을 공유하면서 계속해서 데이터를 주고받을 수 있는 상태가 된다. 여기서 중요한 점은, 이 연결이 하나의 요청을 처리하기 위한 연결이 아니라는 것이다. WebSocket 연결은 지속적으로 유지되며, 이 연결 자체가 하나의 통신 채널 역할을 하게 된다. 즉, 한 번 연결이 만들어지면 이후에는 새로운 요청을 만들 필요 없이 같은 연결을 계속 사용한다.

3.3 연결 이후 데이터 흐름

WebSocket 연결이 수립된 이후에는 HTTP의 요청-응답 구조는 더 이상 사용되지 않는다. 대신 클라이언트와 서버가 동일한 연결 위에서 자유롭게 데이터를 주고받을 수 있는 구조가 된다.

sequenceDiagram
    participant Client
    participant Server

    Client->>Server: 메시지 전송
    Server->>Client: 메시지 전송
    Client->>Server: 메시지 전송

이 구조의 핵심은 누가 먼저 보내야 한다는 제약이 사라진다는 점이다. HTTP에서는 항상 클라이언트가 먼저 요청을 보내야 했지만, WebSocket에서는 서버도 언제든지 데이터를 전송할 수 있다. 이 때문에 실시간 알림, 채팅, 게임 상태 동기화와 같은 기능을 자연스럽게 구현할 수 있다. 또한 데이터 전송 방식도 HTTP보다 가볍다. HTTP는 매 요청마다 헤더와 함께 메시지를 구성해야 하지만, WebSocket은 이미 연결이 수립된 상태이기 때문에 최소한의 프레임 구조만으로 데이터를 주고받는다. 이 덕분에 네트워크 오버헤드가 줄어들고, 지속적인 통신에서도 효율적으로 동작할 수 있다. 결과적으로 WebSocket의 동작 과정을 보면, HTTP로 시작해서 연결을 만든 뒤, 그 연결을 완전히 다른 방식의 통신 채널로 전환하는 구조라고 이해할 수 있다. 그리고 이 전환 이후의 구조가 바로 실시간 통신을 가능하게 만드는 핵심이다.

4. WebSocket의 특징

4.1 Full-Duplex 통신

WebSocket의 가장 큰 특징은 양방향 통신이 가능하다는 점이다. HTTP에서는 항상 클라이언트가 먼저 요청을 보내야 하고, 서버는 그에 대한 응답만 할 수 있었다. 즉, 통신의 시작점이 항상 클라이언트에 있었다. 하지만 WebSocket에서는 이 구조가 완전히 달라진다. 한 번 연결이 수립되면 클라이언트와 서버가 동일한 연결 위에서 데이터를 주고받기 때문에, 어느 쪽이 먼저 보내는지에 대한 제약이 없다. 클라이언트가 메시지를 보내지 않아도 서버가 먼저 데이터를 전달할 수 있고, 반대로 클라이언트도 필요할 때 언제든지 데이터를 보낼 수 있다. 이 구조는 단순히 양쪽이 보낼 수 있다는 의미를 넘어서, 통신 자체를 하나의 대화처럼 만들어준다. 요청과 응답이 명확히 구분되는 구조가 아니라, 서로가 동시에 데이터를 주고받는 흐름이 되기 때문에 실시간 통신이 자연스럽게 구현된다. 채팅 서비스나 실시간 알림과 같은 기능이 WebSocket에서 잘 동작하는 이유도 바로 이 구조 때문이다.

4.2 지속 연결 (Persistent Connection)

HTTP에서는 요청을 보낼 때마다 연결을 생성하거나, 연결을 재사용하더라도 요청 단위로 통신이 이루어진다. 즉, 요청 하나가 끝나면 그 통신도 함께 종료되는 구조에 가깝다. 반면 WebSocket은 연결 자체를 유지하는 것을 전제로 한다. 처음에 연결이 한 번 수립되면, 그 연결은 끊어지지 않고 계속 유지되며, 그 위에서 모든 데이터가 오간다. 이 연결은 단순히 하나의 요청을 처리하기 위한 것이 아니라, 클라이언트와 서버 사이에 지속적으로 유지되는 통신 채널이라고 볼 수 있다. 이 구조는 두 가지 측면에서 중요한 의미를 가진다. 첫 번째는 성능이다. 매번 연결을 생성하고 종료하는 과정이 없기 때문에 불필요한 네트워크 비용이 줄어든다. 두 번째는 통신 방식이다. 연결이 유지되기 때문에 상태를 기반으로 한 흐름을 자연스럽게 만들 수 있고, 데이터가 발생하는 즉시 전달할 수 있다. 즉, WebSocket은 요청을 처리하는 연결이 아니라 계속 이어지는 통신 채널이라는 점에서 HTTP와 근본적으로 다른 구조를 가진다.

4.3 낮은 오버헤드

HTTP 통신에서는 요청을 보낼 때마다 헤더를 포함한 메시지 전체를 구성해야 한다. 요청마다 반복되는 헤더 정보는 통신 과정에서 상당한 오버헤드를 만들어낸다. 특히 짧은 간격으로 반복 요청이 발생하는 Polling 방식에서는 이 오버헤드가 더욱 크게 느껴진다. WebSocket은 이 문제를 해결한다. 연결이 한 번 수립된 이후에는 추가적인 연결 설정이나 헤더 전송이 필요하지 않기 때문이다. 데이터는 비교적 간단한 프레임 구조로 전달되며, 매번 동일한 정보를 반복해서 보내지 않는다. 이 덕분에 동일한 양의 데이터를 전송하더라도 네트워크 사용량이 줄어들고, 지연 시간도 감소한다. 특히 실시간으로 작은 메시지를 자주 주고받는 환경에서는 이러한 차이가 더욱 크게 나타난다. 결국 WebSocket의 특징을 정리하면, 단순히 새로운 통신 방식이 아니라 HTTP의 구조적인 한계를 해결하기 위해 설계된 방식이라고 볼 수 있다. 요청과 응답이라는 제한에서 벗어나, 하나의 연결 위에서 지속적으로 데이터를 주고받는 구조로 바뀌면서 실시간 통신이 자연스럽게 가능해진 것이다.

4.4. 사용 사례

WebSocket은 단순히 새로운 통신 방식이라기보다, HTTP 구조로는 해결하기 어려웠던 실시간 통신 문제를 해결하기 위해 등장한 프로토콜이다. 따라서 WebSocket이 사용되는 서비스들은 공통적으로 하나의 특징을 가진다. 서버에서 발생한 데이터를 지연 없이 클라이언트에게 전달해야 한다는 점이다. 가장 대표적인 예는 채팅 서비스이다. 채팅에서는 한 사용자가 메시지를 보내면, 상대방에게 즉시 전달되어야 한다. HTTP 구조에서는 상대방이 주기적으로 서버에 요청을 보내야만 새로운 메시지를 받을 수 있었지만, WebSocket에서는 서버가 메시지를 받는 즉시 해당 사용자에게 전달할 수 있다. 이 과정에서 별도의 요청을 기다릴 필요가 없기 때문에 자연스럽게 실시간성이 확보된다.

실시간 알림 시스템도 비슷한 구조를 가진다. 예를 들어 어떤 이벤트가 발생했을 때, 이를 사용자에게 즉시 알려야 하는 경우가 있다. HTTP 기반에서는 클라이언트가 일정 주기로 서버에 요청을 보내야 하지만, WebSocket에서는 서버가 이벤트 발생 시점에 바로 데이터를 전송할 수 있다. 이 차이는 사용자 경험에서 큰 차이를 만든다. 게임이나 스트리밍 서비스에서도 WebSocket의 특징이 잘 드러난다. 온라인 게임에서는 플레이어의 상태가 계속해서 변경되고, 이 정보가 다른 사용자에게 즉시 전달되어야 한다. 스트리밍 역시 데이터가 끊임없이 생성되고 전달되어야 하는 환경이다. 이러한 경우에는 요청-응답 구조보다, 지속적인 연결을 유지하면서 데이터를 계속 전달하는 방식이 훨씬 적합하다.

이처럼 WebSocket은 서버가 먼저 데이터를 보내야 하는 상황에서 강점을 가진다. HTTP에서는 클라이언트가 요청을 보내야만 통신이 시작되지만, WebSocket에서는 서버도 언제든지 데이터를 전송할 수 있기 때문에 이러한 요구를 자연스럽게 충족할 수 있다.

4.5. 한계와 고려사항

WebSocket은 실시간 통신을 효율적으로 처리할 수 있는 강력한 구조를 제공하지만, 모든 상황에서 적합한 선택은 아니다. 특히 HTTP와는 다른 방식으로 동작하기 때문에, 그에 따른 고려사항도 함께 존재한다. 가장 먼저 생각해야 할 부분은 연결 유지 비용이다. WebSocket은 한 번 연결을 맺으면 이를 계속 유지해야 하기 때문에, 서버는 각 클라이언트와의 연결 상태를 지속적으로 관리해야 한다. HTTP처럼 요청이 끝나면 연결이 종료되는 구조가 아니기 때문에 동시에 많은 사용자가 접속하는 환경에서는 서버 자원 사용량이 크게 증가할 수 있다.

이와 연결되는 문제는 스케일링이다. WebSocket은 상태를 유지하는 연결이기 때문에, 단순히 요청을 여러 서버로 분산시키는 방식으로는 처리하기 어렵다. 특정 클라이언트와 연결된 서버가 계속해서 해당 연결을 유지해야 하기 때문에 로드 밸런싱이나 서버 확장 전략이 HTTP보다 복잡해진다. 이를 해결하기 위해서는 별도의 메시지 브로커나 세션 관리 구조가 필요해지는 경우도 많다. 보안 측면에서도 주의가 필요하다. WebSocket 역시 네트워크를 통해 데이터를 주고받기 때문에, 암호화되지 않은 연결은 위험할 수 있다. 이를 해결하기 위해 HTTPS와 마찬가지로 WSS(WebSocket Secure)를 사용하여 TLS 기반으로 통신을 보호하는 것이 일반적이다. 즉, WebSocket도 결국 HTTPS와 동일하게 보안 계층 위에서 동작하는 것이 권장된다.

결국 WebSocket은 HTTP의 한계를 보완하기 위한 강력한 도구이지만, 모든 상황에 적용하기보다는 실시간성이 필요한 경우에 선택적으로 사용하는 것이 중요하다. 단순한 데이터 조회나 일반적인 API 호출에서는 HTTP가 여전히 더 적합할 수 있으며, WebSocket은 그 구조적 특성이 필요한 상황에서 사용할 때 가장 큰 효과를 발휘한다.

마치며

HTTP와 HTTPS를 먼저 이해한 상태에서 WebSocket을 다시 바라보니 단순히 새로운 프로토콜을 하나 더 배우는 느낌보다는 기존 구조의 한계를 보완하기 위해 등장한 흐름이라는 점이 더 크게 와닿았다. 특히 요청과 응답으로 명확하게 끊어지는 HTTP 구조에서, 하나의 연결을 계속 유지하면서 데이터를 주고받는 방식으로 바뀌는 과정이 인상적이었다. 처음에는 Polling이나 Long Polling 같은 방식도 충분히 동작할 수 있는 해결책처럼 보였지만, 실제로 통신 흐름을 따라가면서 정리해보니 왜 이러한 방식들이 근본적인 해결책이 될 수 없는지도 자연스럽게 이해할 수 있었다. 결국 WebSocket은 기존 HTTP 구조를 억지로 확장하는 방식이 아니라, 실시간 통신이라는 요구를 만족시키기 위해 통신 모델 자체를 바꾼 접근이라고 보는 것이 더 적절하다고 느껴졌다. 이번 글을 정리하면서 네트워크를 이해할 때 단순히 기술을 나열하는 것이 아니라 왜 이런 방식이 필요했는지를 흐름으로 따라가는 것이 중요하다는 것을 느꼈다. HTTP의 구조를 이해하고, 그 한계를 인지한 뒤 어떤 방식으로 문제를 해결하려고 했는지를 이어서 보는 과정 자체가 하나의 설계 흐름처럼 느껴졌다.