최근 본 글

    웹소켓과 HTTP 통신의 차이

    채팅 메시지는 어떻게 새로고침 없이 바로 뜰까?

    웹소켓(WebSocket)과 HTTP 통신의 차이점 및 실시간 데이터 처리 원리

    카카오톡 웹 버전에서 친구가 보낸 메시지가 새로고침도 안 했는데 바로 뜨고, 주식 앱에서는 가격이 쉬지 않고 바뀌고, 온라인 게임에서는 다른 사람의 움직임이 실시간으로 보이죠. 당연하게 느껴지지만, 사실 웹의 기본 통신 방식인 HTTP만으로는 이런 걸 만들기가 꽤 까다로워요.

    이걸 가능하게 해주는 대표적인 기술이 바로 웹소켓(WebSocket)이에요. 저도 처음 채팅 기능을 만들어볼 때 HTTP로 어떻게든 해보려다가 한참 헤맨 뒤에야 웹소켓을 알게 됐어요. 이 글에서는 HTTP와 웹소켓이 어떻게 다른지, 그리고 실시간 데이터가 실제로 어떤 원리로 오가는지 차근차근 정리해 볼게요.

    HTTP 통신은 어떻게 동작할까?

    HTTP는 요청-응답(Request-Response) 방식이에요. 항상 클라이언트(브라우저)가 먼저 “이거 주세요”라고 요청해야 서버가 응답할 수 있어요. 서버가 먼저 말을 거는 건 원칙적으로 불가능해요.

    식당에 비유하면 이해가 쉬워요. 손님(브라우저)이 직원(서버)을 불러서 주문하면 직원이 음식을 가져다줘요. 그런데 주방에서 새로운 메뉴가 나와도 직원이 먼저 와서 알려주지는 않아요. 손님이 궁금하면 다시 불러서 물어봐야 하죠.

    또 HTTP는 기본적으로 무상태(Stateless)예요. 각 요청이 독립적이라서, 서버는 이전 요청을 기억하지 않아요. 그래서 요청할 때마다 필요한 정보(헤더, 쿠키 등)를 매번 함께 보내야 해요. 웹페이지를 불러오는 일반적인 상황에서는 이 방식이 단순하고 효율적이지만, 실시간으로 계속 데이터가 바뀌어야 하는 상황에서는 한계가 드러나요.

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

    웹소켓이 나오기 전에는 HTTP로 실시간처럼 보이게 하려고 여러 방법을 썼어요. 지금도 상황에 따라 쓰이니 알아두면 좋아요.

    폴링(Polling)

    일정 시간마다 서버에 “새로운 거 있어요?”라고 계속 물어보는 방식이에요. 예를 들어 3초마다 요청을 보내는 거죠. 구현은 정말 쉽지만, 새 데이터가 없어도 요청이 계속 오가니 낭비가 심해요. 게다가 3초 간격이라면 최대 3초까지 늦게 알게 되니 진짜 실시간이라고 보기도 어려워요.

    롱 폴링(Long Polling)

    요청을 보내면 서버가 바로 응답하지 않고, 새 데이터가 생길 때까지 잠시 붙잡고 기다렸다가 응답하는 방식이에요. 응답을 받으면 클라이언트는 곧바로 다시 요청을 보내요. 폴링보다 훨씬 실시간에 가깝지만, 데이터를 받을 때마다 연결을 새로 맺어야 하고 매번 HTTP 헤더가 붙어서 여전히 비효율적이에요.

    SSE(Server-Sent Events)

    연결을 한 번 열어두고 서버가 클라이언트에게 데이터를 계속 흘려보내는 방식이에요. 뉴스 속보, 알림, AI 챗봇이 답변을 한 글자씩 보여주는 것처럼 서버에서 클라이언트로 한 방향으로만 보내면 되는 경우에 잘 맞아요. 다만 클라이언트가 서버로 보내는 건 별도의 HTTP 요청으로 처리해야 해요.

    웹소켓은 무엇이 다를까?

    웹소켓은 2011년 RFC 6455로 표준화된 프로토콜로, 클라이언트와 서버 사이에 한 번 연결을 맺으면 계속 유지되는 양방향 통로를 만들어요. 식당으로 치면 손님 테이블과 주방 사이에 전화기를 하나 설치해둔 거예요. 손님이 원할 때 주문할 수 있고, 주방도 원할 때 바로 전화해서 “요리 나왔어요”라고 알려줄 수 있죠.

    핵심 특징은 세 가지예요. 첫째, 연결이 계속 유지돼서 매번 새로 연결할 필요가 없어요. 둘째, 전이중(Full-duplex) 통신이라 양쪽이 동시에 자유롭게 데이터를 주고받을 수 있어요. 셋째, 연결된 뒤에는 데이터를 작은 프레임 단위로 보내는데, 프레임 헤더가 몇 바이트에 불과해서 매번 수백 바이트의 헤더가 붙는 HTTP보다 훨씬 가벼워요.

    [HTTP 폴링]                         [웹소켓]
    클라이언트          서버             클라이언트          서버
       |--- 새 거 있어? --->|               |=== 연결 요청 ====>|
       |<-- 없음 ----------|               |<=== 연결 수락 ====|
       |--- 새 거 있어? --->|               |                   |
       |<-- 없음 ----------|               |<--- 새 메시지 ----|
       |--- 새 거 있어? --->|               |---- 답장 ------->|
       |<-- 새 메시지 -----|               |<--- 새 메시지 ----|
      (매번 새 요청과 헤더)                  (한 번 연결, 자유롭게 주고받기)

    한눈에 보는 HTTP와 웹소켓 비교

    구분 HTTP 웹소켓
    통신 방향클라이언트가 요청해야 응답양쪽 모두 언제든 전송
    연결 방식요청마다 처리 후 종료 (무상태)한 번 연결 후 계속 유지
    오버헤드요청마다 헤더가 붙어 무거움작은 프레임 헤더로 가벼움
    주소 형식http://, https://ws://, wss://(암호화)
    캐싱브라우저, CDN 캐싱 가능캐싱 개념 없음
    잘 맞는 곳웹페이지, 게시판, REST API채팅, 게임, 실시간 시세, 협업 도구

    실시간 데이터 처리 원리: 웹소켓 연결 과정

    재미있는 점은 웹소켓 연결이 HTTP로 시작한다는 거예요. 처음부터 완전히 다른 통신을 하는 게 아니라, HTTP로 “우리 웹소켓으로 갈아탈래요?”라고 제안한 뒤 통로를 바꾸는 방식이에요. 이 과정을 핸드셰이크(Handshake)라고 해요.

    1단계. 클라이언트의 업그레이드 요청

    브라우저가 일반 HTTP 요청에 “프로토콜을 웹소켓으로 업그레이드하고 싶다”는 헤더를 붙여서 보내요.

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

    2단계. 서버의 승인 응답

    서버가 웹소켓을 지원하면 101 Switching Protocols라는 상태 코드로 응답해요. “좋아요, 지금부터 웹소켓으로 이야기해요”라는 뜻이에요. 이때 서버는 클라이언트가 보낸 키를 정해진 방식으로 계산한 값(Sec-WebSocket-Accept)을 돌려줘서, 제대로 된 웹소켓 서버라는 걸 확인시켜 줘요.

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

    3단계. 프레임으로 데이터 주고받기

    핸드셰이크가 끝나면 같은 연결 위에서 더 이상 HTTP가 아닌 웹소켓 프레임으로 데이터를 주고받아요. 텍스트(보통 JSON)나 바이너리 데이터를 보낼 수 있고, 어느 쪽이든 먼저 보낼 수 있어요. 새 메시지가 생기면 서버가 즉시 밀어주기 때문에 폴링 같은 지연이 없어요.

    4단계. 연결 유지와 종료

    연결이 오래 열려 있다 보니 중간에 끊겼는지 확인하는 장치도 있어요. 서버와 클라이언트가 가끔 핑(Ping)을 보내고 퐁(Pong)으로 답하면서 “아직 살아있어요”를 확인하는 거죠. 대화가 끝나면 한쪽이 종료 프레임을 보내고 상대가 응답하면서 깔끔하게 연결을 닫아요.

    간단한 코드로 보는 웹소켓

    브라우저에는 웹소켓 기능이 기본으로 들어 있어서, 별도 라이브러리 없이 몇 줄이면 연결할 수 있어요.

    // 브라우저(클라이언트) 코드
    const socket = new WebSocket("wss://example.com/chat");
    
    socket.addEventListener("open", () => {
      socket.send(JSON.stringify({ type: "hello", name: "철수" }));
    });
    
    socket.addEventListener("message", (event) => {
      const data = JSON.parse(event.data);
      console.log("새 메시지:", data);
    });
    
    socket.addEventListener("close", () => {
      console.log("연결이 끊겼어요");
    });

    서버는 Node.js라면 ws 같은 라이브러리로 간단히 만들 수 있어요. 아래는 한 사람이 보낸 메시지를 접속한 모든 사람에게 전달하는, 가장 기본적인 채팅 서버예요.

    // Node.js 서버 코드 (npm install ws)
    import { WebSocketServer, WebSocket } from "ws";
    
    const wss = new WebSocketServer({ port: 8080 });
    
    wss.on("connection", (ws) => {
      ws.on("message", (message) => {
        // 접속한 모든 클라이언트에게 전달
        wss.clients.forEach((client) => {
          if (client.readyState === WebSocket.OPEN) {
            client.send(message.toString());
          }
        });
      });
    });

    웹소켓을 쓸 때 주의할 점

    서버 자원을 계속 차지해요. 접속자마다 연결이 계속 열려 있으니, 동시 접속자가 많아질수록 서버 메모리와 연결 수 관리가 중요해져요.

    서버를 여러 대로 늘릴 때 복잡해져요. A 서버에 연결된 사람이 보낸 메시지를 B 서버에 연결된 사람에게 전달하려면, 서버끼리 메시지를 공유하는 구조(예: Redis 같은 메시지 브로커를 이용한 Pub/Sub)가 필요해요. 로드밸런서도 웹소켓 연결을 지원하도록 설정해야 하고요.

    재연결 처리가 필수예요. 모바일 환경에서는 와이파이에서 LTE로 바뀌거나 터널에 들어가는 등 연결이 끊기는 일이 흔해요. 끊겼을 때 자동으로 다시 연결하고, 그사이 놓친 메시지를 어떻게 채울지 미리 설계해 두는 게 좋아요. Socket.IO 같은 라이브러리는 이런 기능을 기본으로 제공해요.

    보안은 wss://로. 실제 서비스에서는 반드시 암호화된 wss://를 사용하세요. 앞선 글에서 다룬 HTTPS와 같은 원리로 데이터를 보호해 줘요. 또 연결할 때 사용자 인증을 확인하고, 받은 메시지는 항상 검증하는 습관이 중요해요.

    무조건 웹소켓이 정답은 아니에요

    게시글 목록 불러오기나 회원가입처럼 한 번 요청하고 끝나는 기능은 HTTP가 훨씬 단순하고 캐싱 같은 이점도 누릴 수 있어요. 서버에서 알림만 보내면 되는 경우라면 SSE가 더 간단할 수 있고요. 양방향으로 자주, 빠르게 주고받아야 할 때 웹소켓을 선택하는 게 좋아요.

    마치며

    정리하면, HTTP는 손님이 부를 때만 오는 식당 직원이고, 웹소켓은 테이블과 주방을 연결한 전화기예요. HTTP는 요청이 있을 때만 응답하는 단순하고 효율적인 방식이라 대부분의 웹에 잘 맞고, 웹소켓은 한 번 연결을 맺은 뒤 양쪽이 자유롭게 데이터를 주고받을 수 있어서 채팅이나 게임 같은 실시간 서비스에 딱 맞아요.

    처음엔 개념이 헷갈릴 수 있는데, 위의 짧은 코드를 직접 실행해서 브라우저 두 개로 메시지를 주고받아 보면 금방 감이 올 거예요. 궁금한 점은 댓글로 남겨주세요!

    ※ 예제 코드는 원리를 설명하기 위한 최소한의 코드예요. 실제 서비스에는 인증, 에러 처리, 재연결 로직을 반드시 추가해 주세요.

    댓글 0

    댓글 남기기