WebSocket이란 무엇인가? HTTP와의 차이

WebSocket이란 무엇인가? HTTP와의 차이

채팅 앱을 쓰다 보면 상대방이 메시지를 보내는 순간 바로 화면에 뜹니다. 새로고침을 누르지도 않았는데 실시간으로 내용이 나타납니다. 주식 차트를 보고 있으면 가격이 쉴 새 없이 바뀌고, 온라인 게임에서는 상대방의 움직임이 즉시 내 화면에 반영됩니다. 이런 실시간 통신은 우리가 평소에 사용하는 HTTP만으로는 구현하기가 어렵습니다. 여기서 등장하는 게 WebSocket입니다. 이번 글에서는 WebSocket이 무엇인지, HTTP와 어떻게 다른지, 어떤 상황에서 쓰는 건지를 정리해보겠습니다.

HTTP 통신의 기본 구조부터 다시 보기

WebSocket을 이해하려면 먼저 HTTP가 어떤 방식으로 동작하는지를 다시 짚어야 합니다.

HTTP는 요청과 응답으로 이루어진 통신 방식입니다. 클라이언트가 서버에 요청을 보내면 서버가 응답을 돌려주고, 그걸로 통신이 끝납니다. 브라우저에서 웹 페이지를 열면 HTML을 달라고 요청하고, 서버가 HTML을 보내주고, 연결이 끊어집니다. 이미지가 필요하면 다시 요청하고, CSS가 필요하면 또 요청합니다. 매번 요청이 있어야 응답이 오는 구조입니다.

이 방식의 핵심적인 특징은 서버가 먼저 클라이언트에게 데이터를 보낼 수 없다는 점입니다. 서버 쪽에서 새로운 데이터가 생겨도 클라이언트가 요청하지 않는 한 전달할 방법이 없습니다. 항상 클라이언트가 먼저 물어봐야 합니다.

블로그 글을 읽거나 쇼핑몰에서 상품을 조회하는 정도의 서비스에서는 이게 아무 문제가 없습니다. 사용자가 클릭할 때마다 새 페이지를 요청하면 되니까요. 하지만 실시간으로 데이터가 계속 바뀌는 서비스에서는 이 방식이 한계에 부딪힙니다.

HTTP로 실시간을 흉내내는 방법과 그 한계

WebSocket이 나오기 전에도 실시간 비슷한 경험을 만들려는 시도가 있었습니다.

가장 단순한 방법은 폴링(Polling)입니다. 클라이언트가 일정 간격으로 서버에 반복 요청을 보내는 겁니다. 예를 들어 3초마다 “새 메시지 있어?”라고 물어보는 식입니다. 구현은 간단하지만 문제가 많습니다. 새 데이터가 없어도 계속 요청을 보내기 때문에 서버에 불필요한 부하가 걸립니다. 3초 간격이면 실시간이라고 보기도 어렵습니다. 간격을 짧게 하면 서버 부담이 더 커집니다.

롱 폴링(Long Polling)이라는 개선된 방식도 있습니다. 클라이언트가 요청을 보내면 서버가 바로 응답하지 않고 새 데이터가 생길 때까지 연결을 유지합니다. 데이터가 생기면 그때 응답을 보내고, 클라이언트는 응답을 받자마자 다시 요청을 겁니다. 폴링보다는 낫지만 매번 연결을 새로 맺어야 하고, HTTP 헤더가 계속 오가기 때문에 여전히 비효율적입니다.

이런 방법들이 근본적으로 비효율적인 이유는 HTTP 자체가 단방향 통신을 전제로 설계되었기 때문입니다. 양방향 실시간 통신을 하려면 아예 다른 방식이 필요했고, 그래서 나온 게 WebSocket입니다.

WebSocket이란 무엇인가

WebSocket은 클라이언트와 서버 사이에 하나의 연결을 열어두고, 그 연결을 통해 양쪽이 자유롭게 데이터를 주고받을 수 있는 통신 프로토콜입니다.

HTTP가 편지를 주고받는 것이라면, WebSocket은 전화 통화에 가깝습니다. 편지는 한쪽이 보내면 상대방이 읽고 답장을 써서 보내야 합니다. 매번 새 편지를 써야 하고, 상대방이 먼저 말을 걸 수 없습니다. 반면에 전화는 한번 연결되면 양쪽이 언제든 말할 수 있습니다. 상대방이 할 말이 생기면 바로 전달할 수 있고, 대화가 끝날 때까지 연결이 유지됩니다.

WebSocket도 마찬가지입니다. 처음에 연결을 맺으면 그 연결이 계속 유지되고, 서버도 클라이언트에게 먼저 데이터를 보낼 수 있습니다. 클라이언트가 요청하지 않아도 서버에서 새 데이터가 생기면 즉시 밀어넣을 수 있습니다. 이걸 전이중 통신(Full-Duplex)이라고 합니다.

WebSocket 연결이 맺어지는 과정

WebSocket 연결은 처음에 HTTP를 통해 시작됩니다. 이 과정을 핸드셰이크(Handshake)라고 합니다.

클라이언트가 서버에 HTTP 요청을 보내면서 “이 연결을 WebSocket으로 업그레이드하고 싶다”는 헤더를 포함합니다. 서버가 WebSocket을 지원하면 “좋다, 업그레이드하자”라고 응답합니다. 이 응답의 상태 코드는 101 Switching Protocols입니다. 이 순간부터 HTTP 연결이 WebSocket 연결로 전환되고, 이후부터는 HTTP가 아닌 WebSocket 프로토콜로 데이터를 주고받습니다.

핸드셰이크 요청을 간단하게 보면 이런 형태입니다.

GET /chat HTTP/1.1
Host: today-play.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

Upgrade: websocket이라는 헤더가 핵심입니다. 서버가 이걸 받아들이면 연결이 전환됩니다. 처음에 HTTP로 시작하기 때문에 기존의 웹 인프라(방화벽, 프록시 등)를 그대로 통과할 수 있다는 장점이 있습니다. 포트도 HTTP와 같은 80번, HTTPS와 같은 443번을 사용합니다. WebSocket의 URL 스킴은 ws와 wss인데, ws는 HTTP에 대응하고 wss는 HTTPS에 대응합니다.

HTTP와 WebSocket의 차이 정리

두 프로토콜의 차이를 항목별로 비교하면 이렇습니다.

통신 방향에서 HTTP는 클라이언트가 요청해야 서버가 응답하는 단방향 구조입니다. WebSocket은 연결이 맺어진 후 양쪽이 언제든 데이터를 보낼 수 있는 양방향 구조입니다.

연결 유지에서 HTTP는 기본적으로 요청-응답이 끝나면 연결이 종료됩니다. keep-alive로 연결을 재사용할 수는 있지만 통신 방식 자체가 바뀌는 건 아닙니다. WebSocket은 한번 연결되면 명시적으로 끊기 전까지 계속 유지됩니다.

오버헤드에서 HTTP는 매 요청마다 헤더 정보가 포함됩니다. 쿠키, 인증 정보, 콘텐츠 타입 같은 헤더가 매번 오갑니다. 간단한 데이터 하나 보내는데 헤더가 수백 바이트에서 수 킬로바이트까지 될 수 있습니다. WebSocket은 핸드셰이크 이후에는 프레임 단위로 데이터를 보내는데 프레임 헤더가 2바이트에서 14바이트 정도로 매우 작습니다. 작은 데이터를 자주 주고받아야 하는 상황에서 효율 차이가 큽니다.

실시간성에서 HTTP 폴링은 요청 간격만큼의 지연이 생깁니다. WebSocket은 데이터가 생기는 즉시 전달되기 때문에 사실상 지연이 없습니다.

WebSocket을 사용하는 대표적인 서비스들

채팅 애플리케이션이 가장 대표적입니다. 카카오톡이나 슬랙 같은 메신저에서 메시지가 실시간으로 오가는 건 WebSocket 덕분입니다. 상대방이 메시지를 보내면 서버가 바로 내 클라이언트에 전달합니다.

실시간 주식 차트나 가상화폐 거래소도 WebSocket을 많이 씁니다. 가격이 밀리초 단위로 변하기 때문에 폴링으로는 따라갈 수 없습니다. 서버에서 가격이 변할 때마다 연결된 모든 클라이언트에 즉시 전달합니다.

온라인 게임에서 플레이어들의 위치나 행동을 실시간으로 동기화하는 데도 사용됩니다. 협업 도구에서 여러 사람이 동시에 문서를 편집할 때 다른 사람의 커서 위치나 수정 내용이 바로 반영되는 것도 WebSocket 기반인 경우가 많습니다.

알림 시스템에도 적합합니다. 새 댓글이 달렸거나 주문 상태가 바뀌었을 때 서버가 먼저 클라이언트에 알려줄 수 있습니다.

간단한 WebSocket 코드 예시

JavaScript에서 WebSocket을 사용하는 코드는 생각보다 간단합니다.

클라이언트 쪽 코드입니다.

javascript

const socket = new WebSocket('wss://today-play.com/chat');

socket.onopen = function() {
    console.log('서버에 연결되었습니다');
    socket.send('안녕하세요');
};

socket.onmessage = function(event) {
    console.log('서버에서 받은 메시지:', event.data);
};

socket.onclose = function() {
    console.log('연결이 종료되었습니다');
};

new WebSocket으로 연결을 맺고, onopen에서 연결 성공 시 처리를 하고, send로 서버에 메시지를 보내고, onmessage에서 서버가 보낸 메시지를 받습니다. HTTP처럼 매번 요청을 보내는 게 아니라 이벤트 기반으로 동작합니다. 서버에서 메시지가 오면 onmessage가 자동으로 호출됩니다.

서버 쪽은 Node.js 환경에서 ws 라이브러리를 사용하면 이런 식입니다.

javascript

const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });

server.on('connection', function(socket) {
    console.log('클라이언트가 연결되었습니다');

    socket.on('message', function(message) {
        console.log('받은 메시지:', message);
        socket.send('메시지를 받았습니다: ' + message);
    });

    socket.on('close', function() {
        console.log('클라이언트 연결이 종료되었습니다');
    });
});

서버도 마찬가지로 이벤트 기반입니다. 클라이언트가 연결되면 connection 이벤트가 발생하고, 메시지가 오면 message 이벤트가 발생합니다. socket.send로 클라이언트에게 언제든 데이터를 보낼 수 있습니다.

WebSocket을 사용할 때 주의할 점

WebSocket 연결은 계속 유지되기 때문에 서버 입장에서는 연결된 클라이언트 수만큼 리소스를 잡고 있어야 합니다. 동시 접속자가 많아지면 서버의 메모리와 파일 디스크립터가 부족해질 수 있습니다. HTTP는 요청을 처리하고 바로 연결을 해제하기 때문에 같은 수의 사용자를 처리할 때 WebSocket이 더 많은 리소스를 사용합니다.

네트워크가 불안정하면 연결이 끊어질 수 있습니다. 모바일 환경에서 와이파이가 바뀌거나 지하철에서 신호가 약해지면 WebSocket 연결이 끊깁니다. 그래서 클라이언트 코드에 자동 재연결 로직을 넣어두는 게 일반적입니다. 연결이 끊어지면 일정 시간 후에 다시 연결을 시도하는 식입니다.

로드 밸런서를 사용하는 환경에서는 WebSocket 연결이 특정 서버에 고정되어야 합니다. HTTP는 요청마다 다른 서버로 보내도 상관없지만, WebSocket은 연결이 유지되는 동안 같은 서버와 통신해야 합니다. 이걸 스티키 세션(Sticky Session)이라고 하는데, 로드 밸런서 설정에서 별도로 처리해야 합니다.

보안 측면에서는 반드시 wss를 사용해야 합니다. ws는 암호화되지 않은 연결이고 wss는 TLS로 암호화된 연결입니다. HTTPS를 적용한 사이트에서 ws로 연결하려고 하면 브라우저가 차단합니다.

WebSocket 대신 쓸 수 있는 대안들

모든 실시간 통신에 WebSocket이 필요한 건 아닙니다.

SSE(Server-Sent Events)는 서버에서 클라이언트로 단방향으로 데이터를 보내는 방식입니다. 클라이언트가 서버에 데이터를 보낼 필요 없이 서버의 업데이트만 받으면 되는 경우에 적합합니다. 뉴스 피드 업데이트나 실시간 알림 같은 데에 쓸 수 있습니다. HTTP 위에서 동작하기 때문에 별도의 프로토콜이 필요 없고 구현이 WebSocket보다 간단합니다.

Socket.IO는 WebSocket을 감싼 라이브러리입니다. WebSocket을 기본으로 사용하되, WebSocket이 안 되는 환경에서는 자동으로 폴링으로 전환합니다. 자동 재연결, 룸 기능, 브로드캐스트 같은 편의 기능이 내장되어 있어서 실무에서 많이 사용됩니다. 다만 Socket.IO는 순수 WebSocket과 호환되지 않는 자체 프로토콜을 사용한다는 점을 알아둬야 합니다.

그러니까 양방향 실시간 통신이 필요하면 WebSocket, 서버에서 클라이언트로의 단방향 푸시만 필요하면 SSE, WebSocket에 편의 기능까지 필요하면 Socket.IO를 검토하면 됩니다.

마무리

WebSocket은 HTTP의 요청-응답 구조로는 해결할 수 없는 실시간 양방향 통신을 가능하게 해주는 프로토콜입니다. 한번 연결을 맺으면 서버와 클라이언트가 자유롭게 데이터를 주고받을 수 있고, 매 요청마다 헤더를 주고받는 오버헤드도 없습니다. 모든 웹 서비스에 WebSocket이 필요한 건 아니지만, 채팅이나 실시간 알림처럼 서버가 먼저 데이터를 보내야 하는 기능을 만들 때는 사실상 필수입니다. 다음 글에서는 HTTPS의 기반이 되는 SSL/TLS의 동작 원리를 좀 더 자세히 다뤄보겠습니다.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *