📖

스토리텔링 형식으로 업데이트되었습니다! 실무 사례와 함께 더 쉽게 이해할 수 있어요.

이미지 로딩 중...

WebSocket과 Server-Sent Events 실시간 통신 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 11. 25. · 2 Views

WebSocket과 Server-Sent Events 실시간 통신 완벽 가이드

웹 애플리케이션에서 실시간 데이터 통신을 구현하는 핵심 기술인 WebSocket과 Server-Sent Events를 다룹니다. 채팅, 알림, 실시간 업데이트 등 현대 웹 서비스의 필수 기능을 구현하는 방법을 배워봅니다.


목차

  1. WebSocket_프로토콜_이해
  2. Socket.io_활용한_실시간_통신
  3. Server-Sent_Events_개념
  4. WebSocket_vs_SSE_vs_Long_Polling
  5. 실시간_채팅_구현_예제
  6. 연결_관리와_재연결_전략

1. WebSocket_프로토콜_이해

김개발 씨는 회사에서 실시간 주식 시세를 보여주는 대시보드를 개발하게 되었습니다. 처음에는 1초마다 서버에 HTTP 요청을 보내는 방식으로 구현했는데, 서버 담당자에게 연락이 왔습니다.

"서버 부하가 너무 심해요. 다른 방법 없을까요?"

WebSocket은 클라이언트와 서버 사이에 지속적인 양방향 통신 채널을 여는 프로토콜입니다. 마치 전화 통화처럼 한 번 연결하면 끊기 전까지 계속 대화할 수 있습니다.

HTTP가 매번 문을 두드리고 들어가는 방식이라면, WebSocket은 문을 열어두고 자유롭게 드나드는 방식입니다.

다음 코드를 살펴봅시다.

// WebSocket 연결 생성
const socket = new WebSocket('wss://api.example.com/realtime');

// 연결이 열리면 실행
socket.onopen = function(event) {
    console.log('WebSocket 연결 성공');
    socket.send(JSON.stringify({ type: 'subscribe', channel: 'stock' }));
};

// 서버로부터 메시지 수신
socket.onmessage = function(event) {
    const data = JSON.parse(event.data);
    console.log('받은 데이터:', data);
};

// 에러 처리
socket.onerror = function(error) {
    console.error('WebSocket 에러:', error);
};

김개발 씨는 입사 6개월 차 프론트엔드 개발자입니다. 이번에 맡은 프로젝트는 실시간 주식 시세 대시보드였습니다.

처음에는 익숙한 방식대로 setInterval을 사용해 1초마다 fetch로 데이터를 가져오도록 구현했습니다. 며칠 후 서버 담당자 박시니어 씨에게서 긴급 연락이 왔습니다.

"김개발 씨, 대시보드 사용자가 100명만 넘어가도 서버가 힘들어해요. 1초에 100번씩 HTTP 요청이 들어오니까요." 그렇다면 WebSocket이란 정확히 무엇일까요?

쉽게 비유하자면, 기존 HTTP 통신은 마치 편지를 주고받는 것과 같습니다. 궁금한 게 있을 때마다 편지를 써서 보내고, 답장을 기다려야 합니다.

반면 WebSocket은 전화 통화와 같습니다. 한 번 연결하면 끊기 전까지 실시간으로 대화할 수 있습니다.

WebSocket이 등장하기 전에는 어떻게 실시간 통신을 구현했을까요? 개발자들은 **폴링(Polling)**이라는 방식을 사용했습니다.

일정 시간마다 서버에 "새 데이터 있어요?"라고 물어보는 방식입니다. 이 방법은 불필요한 요청이 많아 서버에 부담을 주었습니다.

더 나은 방식으로 **롱 폴링(Long Polling)**이 있었지만, 여전히 매번 새로운 연결을 맺어야 했습니다. 바로 이런 문제를 해결하기 위해 2011년 WebSocket 프로토콜이 표준화되었습니다.

WebSocket의 동작 방식을 살펴보겠습니다. 처음에는 HTTP 요청으로 시작합니다.

클라이언트가 "WebSocket으로 업그레이드하고 싶습니다"라고 요청하면, 서버가 "좋습니다, 업그레이드합시다"라고 응답합니다. 이것을 **핸드셰이크(Handshake)**라고 부릅니다.

핸드셰이크가 완료되면 HTTP 연결이 WebSocket 연결로 업그레이드됩니다. 이제 클라이언트와 서버 모두 자유롭게 데이터를 보낼 수 있습니다.

서버가 먼저 데이터를 보낼 수도 있고, 클라이언트가 보낼 수도 있습니다. 위의 코드를 살펴보겠습니다.

먼저 new WebSocket()으로 연결을 생성합니다. 주소가 wss://로 시작하는데, 이것은 보안이 적용된 WebSocket을 의미합니다.

HTTP와 HTTPS의 관계처럼 ws://와 wss://가 있습니다. onopen 이벤트는 연결이 성공하면 호출됩니다.

이때 서버에 구독 메시지를 보내 원하는 데이터 채널을 등록합니다. onmessage는 서버로부터 데이터가 올 때마다 호출되어 실시간 업데이트를 처리합니다.

실제 현업에서 WebSocket은 채팅 애플리케이션, 실시간 알림, 온라인 게임, 협업 도구 등 다양한 곳에서 사용됩니다. Slack, Discord, Google Docs 같은 서비스들이 모두 WebSocket을 활용합니다.

하지만 주의할 점도 있습니다. WebSocket 연결은 서버 자원을 지속적으로 점유합니다.

연결이 많아지면 서버 메모리 사용량이 늘어납니다. 또한 네트워크가 불안정하면 연결이 끊어질 수 있어 재연결 로직이 필수입니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. WebSocket으로 변경한 후 서버 부하가 크게 줄었습니다.

박시니어 씨가 웃으며 말했습니다. "이제 1000명이 접속해도 걱정 없겠네요!"

실전 팁

💡 - wss:// 프로토콜을 사용하여 보안 연결을 유지하세요

  • 연결 상태를 항상 확인하고 끊어지면 재연결하는 로직을 구현하세요
  • 대용량 데이터는 분할해서 전송하는 것이 좋습니다

2. Socket.io_활용한_실시간_통신

김개발 씨가 WebSocket을 적용하고 기뻐한 것도 잠시, 테스트 중에 문제를 발견했습니다. 일부 사용자의 브라우저에서 WebSocket이 작동하지 않았고, 네트워크가 끊겼을 때 자동으로 재연결되지 않았습니다.

박시니어 씨가 조언했습니다. "Socket.io를 한번 살펴보세요."

Socket.io는 WebSocket을 기반으로 한 실시간 통신 라이브러리입니다. 마치 자동차의 자동 변속기처럼, 복잡한 부분은 알아서 처리해주고 개발자는 비즈니스 로직에만 집중할 수 있게 해줍니다.

자동 재연결, 방(Room) 기능, 브라우저 호환성 등 실무에서 필요한 기능을 모두 제공합니다.

다음 코드를 살펴봅시다.

// 서버 측 코드 (Node.js)
const io = require('socket.io')(server);

io.on('connection', (socket) => {
    console.log('사용자 연결됨:', socket.id);

    // 특정 방에 참여
    socket.join('chat-room-1');

    // 메시지 수신 및 브로드캐스트
    socket.on('chat-message', (data) => {
        io.to('chat-room-1').emit('new-message', {
            user: data.user,
            message: data.message,
            timestamp: new Date()
        });
    });

    socket.on('disconnect', () => {
        console.log('사용자 연결 해제:', socket.id);
    });
});

김개발 씨는 순수 WebSocket으로 구현한 채팅 기능을 테스트하던 중 여러 문제에 부딪혔습니다. 네트워크가 잠깐 끊겼다가 다시 연결되면 채팅이 작동하지 않았습니다.

사용자가 직접 새로고침을 해야 했습니다. 또한 IE 11을 사용하는 고객사에서 연락이 왔습니다.

"채팅이 아예 안 됩니다." 알고 보니 일부 구형 브라우저에서는 WebSocket 지원이 완벽하지 않았습니다. 박시니어 씨가 Socket.io를 추천한 이유가 바로 여기에 있었습니다.

Socket.io를 비유하자면, 순수 WebSocket이 수동 변속기라면 Socket.io는 자동 변속기입니다. 수동 변속기도 잘 다루면 좋은 성능을 낼 수 있지만, 자동 변속기는 운전자가 신경 쓸 부분을 크게 줄여줍니다.

Socket.io가 제공하는 핵심 기능을 살펴보겠습니다. 첫 번째는 자동 재연결입니다.

네트워크가 끊어져도 Socket.io가 알아서 재연결을 시도합니다. 개발자가 별도로 구현할 필요가 없습니다.

두 번째는 폴백(Fallback) 메커니즘입니다. WebSocket을 지원하지 않는 환경에서는 자동으로 롱 폴링으로 전환합니다.

사용자는 차이를 느끼지 못하고 서비스를 이용할 수 있습니다. 세 번째는 방(Room) 기능입니다.

특정 사용자 그룹에게만 메시지를 보내고 싶을 때 유용합니다. 채팅방, 게임 로비, 알림 그룹 등을 쉽게 구현할 수 있습니다.

위의 코드에서 socket.join()은 소켓을 특정 방에 참여시킵니다. io.to()는 해당 방에 있는 모든 소켓에게 메시지를 보냅니다.

이렇게 간단한 코드로 그룹 채팅 기능을 구현할 수 있습니다. Socket.io의 이벤트 기반 구조도 눈여겨볼 만합니다.

on()으로 이벤트를 수신하고 emit()으로 이벤트를 발신합니다. 이 패턴은 직관적이어서 코드 가독성이 좋습니다.

실무에서 Socket.io는 정말 많이 사용됩니다. 특히 빠르게 MVP를 만들어야 할 때 유용합니다.

복잡한 저수준 처리를 라이브러리에 맡기고 비즈니스 로직에 집중할 수 있기 때문입니다. 다만 Socket.io에도 단점은 있습니다.

순수 WebSocket보다 약간의 오버헤드가 있고, Socket.io 프로토콜을 사용하므로 서버와 클라이언트 모두 Socket.io를 사용해야 합니다. 다른 WebSocket 서버와는 바로 연결할 수 없습니다.

김개발 씨는 Socket.io로 전환한 후 안정적인 실시간 통신을 구현할 수 있었습니다. 네트워크가 불안정한 모바일 환경에서도 문제없이 작동했습니다.

실전 팁

💡 - 클라이언트와 서버의 Socket.io 버전을 맞춰주세요

  • 네임스페이스를 활용하면 기능별로 소켓을 분리할 수 있습니다
  • 프로덕션에서는 Redis 어댑터를 사용해 여러 서버 간 소켓을 공유하세요

3. Server-Sent_Events_개념

새 프로젝트가 시작되었습니다. 이번에는 실시간 알림 시스템을 만들어야 했습니다.

서버에서 클라이언트로 일방적으로 알림을 보내기만 하면 되는 상황이었습니다. 박시니어 씨가 물었습니다.

"양방향 통신이 필요 없다면 SSE가 더 적합할 수도 있어요. 들어보셨어요?"

**Server-Sent Events(SSE)**는 서버에서 클라이언트로 단방향 실시간 데이터 스트림을 보내는 기술입니다. 마치 라디오 방송처럼 서버가 일방적으로 데이터를 송출하고, 클라이언트는 수신만 합니다.

HTTP 기반이라 설정이 간단하고, 브라우저가 자동 재연결을 처리해줍니다.

다음 코드를 살펴봅시다.

// 클라이언트 측 코드
const eventSource = new EventSource('/api/notifications');

// 기본 메시지 이벤트
eventSource.onmessage = function(event) {
    const notification = JSON.parse(event.data);
    console.log('새 알림:', notification);
    showNotification(notification);
};

// 커스텀 이벤트 수신
eventSource.addEventListener('urgent', function(event) {
    const data = JSON.parse(event.data);
    console.log('긴급 알림:', data);
    showUrgentAlert(data);
});

// 에러 처리 (자동 재연결됨)
eventSource.onerror = function(error) {
    console.log('연결 끊김, 자동 재연결 중...');
};

김개발 씨는 알림 시스템의 요구사항을 분석했습니다. 서버에서 새 알림이 생기면 클라이언트에 실시간으로 표시해야 합니다.

하지만 클라이언트에서 서버로 보낼 데이터는 없습니다. 알림 확인은 별도의 REST API로 처리하면 됩니다.

이런 상황에서 WebSocket은 과한 선택일 수 있습니다. 양방향 채널을 열어놓고 한 방향만 사용하는 셈이니까요.

Server-Sent Events, 줄여서 SSE는 바로 이런 상황을 위한 기술입니다. SSE를 비유하자면 라디오 방송과 같습니다.

방송국(서버)에서 프로그램(데이터)을 송출하면, 청취자(클라이언트)는 라디오를 켜고 듣기만 합니다. 청취자가 방송국에 직접 말을 걸 수는 없습니다.

요청할 게 있다면 전화(별도의 HTTP 요청)를 해야 합니다. SSE의 가장 큰 장점은 단순함입니다.

일반 HTTP 연결을 그대로 사용합니다. 특별한 프로토콜 업그레이드가 필요 없습니다.

서버에서는 Content-Type을 text/event-stream으로 설정하고 데이터를 보내기만 하면 됩니다. 브라우저의 EventSource API는 정말 편리합니다.

코드에서 보듯이 new EventSource()로 연결하고 onmessage로 데이터를 받으면 끝입니다. 연결이 끊어지면 브라우저가 자동으로 재연결을 시도합니다.

SSE는 커스텀 이벤트도 지원합니다. 서버에서 event: urgent라고 보내면 클라이언트에서 addEventListener('urgent', ...)로 받을 수 있습니다.

알림 종류에 따라 다르게 처리하고 싶을 때 유용합니다. 실무에서 SSE가 적합한 경우를 정리하면 이렇습니다.

실시간 알림, 뉴스 피드 업데이트, 주식 시세 표시, 로그 스트리밍, 진행 상황 표시 등 서버가 클라이언트에 일방적으로 데이터를 푸시하는 모든 경우에 적합합니다. 최근에는 AI 챗봇 서비스에서 SSE를 많이 사용합니다.

ChatGPT 같은 서비스에서 응답이 글자 단위로 스트리밍되는 것을 보셨을 것입니다. 이것이 바로 SSE입니다.

SSE의 제한사항도 알아두어야 합니다. 브라우저당 도메인별로 동시에 열 수 있는 SSE 연결 수가 제한되어 있습니다.

HTTP/1.1에서는 보통 6개입니다. HTTP/2에서는 이 제한이 완화됩니다.

김개발 씨는 알림 시스템을 SSE로 구현했습니다. WebSocket보다 훨씬 간단했고, 서버 측 코드도 단순했습니다.

박시니어 씨가 말했습니다. "적재적소에 맞는 기술을 선택하는 것이 중요해요."

실전 팁

💡 - HTTP/2 환경에서는 SSE의 연결 수 제한이 거의 없습니다

  • Last-Event-ID 헤더를 활용하면 끊어진 지점부터 데이터를 이어받을 수 있습니다
  • 클라이언트에서 서버로 데이터를 보내야 한다면 별도의 fetch 요청을 사용하세요

4. WebSocket_vs_SSE_vs_Long_Polling

팀 회의에서 신입 개발자 이주니어 씨가 질문했습니다. "WebSocket이랑 SSE랑 롱 폴링이랑 뭐가 다른 건가요?

언제 뭘 써야 하는지 헷갈려요." 김개발 씨는 자신도 처음에 그랬던 기억이 났습니다. 박시니어 씨가 화이트보드 앞으로 갔습니다.

WebSocket, SSE, Long Polling은 모두 실시간 통신을 위한 기술이지만 각각의 특성이 다릅니다. WebSocket은 양방향 통신이 필요할 때, SSE는 서버에서 클라이언트로의 단방향 스트리밍에, Long Polling은 레거시 환경 지원이 필요할 때 적합합니다.

다음 코드를 살펴봅시다.

// Long Polling 구현 예제
async function longPolling() {
    try {
        // 서버에 요청, 서버는 새 데이터가 있을 때까지 응답을 보류
        const response = await fetch('/api/updates?timeout=30000');
        const data = await response.json();

        // 데이터 처리
        console.log('새 업데이트:', data);
        processUpdate(data);

        // 즉시 다음 요청 시작
        longPolling();
    } catch (error) {
        console.error('폴링 에러:', error);
        // 에러 시 잠시 후 재시도
        setTimeout(longPolling, 3000);
    }
}

// 폴링 시작
longPolling();

박시니어 씨가 화이트보드에 세 가지 기술을 그림으로 설명하기 시작했습니다. 먼저 Long Polling입니다.

이것은 실시간 통신의 원조라고 할 수 있습니다. 클라이언트가 서버에 요청을 보내면, 서버는 새 데이터가 생길 때까지 응답을 보류합니다.

데이터가 생기거나 타임아웃이 되면 응답하고, 클라이언트는 즉시 다시 요청합니다. 비유하자면 Long Polling은 식당에서 "자리 나면 불러주세요"하고 기다리는 것과 같습니다.

자리가 나면 직원이 알려주고, 다시 대기 명단에 이름을 올립니다. Long Polling의 장점은 모든 브라우저에서 작동한다는 것입니다.

단점은 매번 HTTP 연결을 새로 맺어야 해서 오버헤드가 있습니다. 다음으로 SSE입니다.

앞서 배웠듯이 서버에서 클라이언트로의 단방향 스트리밍입니다. HTTP 연결을 열어두고 서버가 지속적으로 데이터를 보냅니다.

SSE는 라디오 방송과 같습니다. 한 번 채널을 맞추면 계속 방송을 들을 수 있습니다.

간단하고 효율적이지만 클라이언트에서 서버로는 데이터를 보낼 수 없습니다. 마지막으로 WebSocket입니다.

양방향 전이중 통신을 지원합니다. 클라이언트와 서버 모두 자유롭게 데이터를 보낼 수 있습니다.

WebSocket은 전화 통화와 같습니다. 서로 동시에 말하고 들을 수 있습니다.

채팅, 게임, 협업 도구처럼 양쪽에서 활발하게 데이터를 주고받는 상황에 적합합니다. 이주니어 씨가 물었습니다.

"그럼 언제 뭘 써야 하나요?" 박시니어 씨가 정리해주었습니다. 양방향 통신이 필요하면 WebSocket입니다.

채팅, 온라인 게임, 실시간 협업 도구가 대표적입니다. 서버에서 클라이언트로만 데이터를 보내면 SSE가 적합합니다.

알림, 뉴스 피드, AI 응답 스트리밍 등이 있습니다. 구현이 단순하고 HTTP 인프라를 그대로 활용할 수 있습니다.

Long Polling은 특별한 경우에만 사용합니다. 아주 오래된 브라우저를 지원해야 하거나, WebSocket과 SSE 모두 사용할 수 없는 환경일 때입니다.

성능 면에서는 WebSocket이 가장 효율적이고, 그다음이 SSE, Long Polling 순입니다. 하지만 복잡도는 반대입니다.

상황에 맞는 기술을 선택하는 것이 중요합니다. 김개발 씨가 덧붙였습니다.

"저도 처음에는 무조건 WebSocket을 쓰려고 했는데, 알림 시스템 만들 때 SSE가 훨씬 간단하더라고요."

실전 팁

💡 - 양방향 통신이 필요 없다면 SSE를 먼저 고려하세요

  • Socket.io를 사용하면 WebSocket과 Long Polling 폴백을 자동으로 처리합니다
  • HTTP/2 환경에서는 SSE의 성능이 크게 향상됩니다

5. 실시간_채팅_구현_예제

이론을 충분히 배웠으니 이제 실전입니다. 팀에서 내부용 간단한 채팅 서비스를 만들기로 했습니다.

김개발 씨가 담당을 맡았습니다. Socket.io를 활용해 실시간 채팅을 처음부터 끝까지 구현해보겠습니다.

실시간 채팅은 WebSocket 또는 Socket.io의 대표적인 활용 사례입니다. 메시지 송수신, 사용자 입장/퇴장 알림, 타이핑 표시 등의 기능을 구현합니다.

실무에서 바로 활용할 수 있는 패턴을 익혀봅니다.

다음 코드를 살펴봅시다.

// 서버 측 (Node.js + Socket.io)
io.on('connection', (socket) => {
    // 사용자가 채팅방 입장
    socket.on('join-room', ({ roomId, username }) => {
        socket.join(roomId);
        socket.to(roomId).emit('user-joined', { username });
        socket.data = { roomId, username };
    });

    // 메시지 전송
    socket.on('send-message', ({ message }) => {
        const { roomId, username } = socket.data;
        io.to(roomId).emit('new-message', {
            id: Date.now(),
            username,
            message,
            timestamp: new Date().toISOString()
        });
    });

    // 타이핑 표시
    socket.on('typing', () => {
        const { roomId, username } = socket.data;
        socket.to(roomId).emit('user-typing', { username });
    });
});

김개발 씨는 채팅 서비스 구현을 시작했습니다. 먼저 필요한 기능을 정리했습니다.

채팅방 입장, 메시지 송수신, 다른 사용자의 입장/퇴장 알림, 타이핑 중 표시가 기본 요구사항이었습니다. Socket.io를 선택한 이유는 명확했습니다.

자동 재연결, 방 기능, 다양한 브라우저 지원을 직접 구현할 필요가 없기 때문입니다. 서버 코드부터 살펴보겠습니다.

connection 이벤트는 클라이언트가 연결될 때마다 발생합니다. 각 연결은 고유한 socket 객체로 관리됩니다.

join-room 이벤트는 사용자가 채팅방에 입장할 때 호출됩니다. socket.join(roomId)로 해당 방에 참여합니다.

socket.to(roomId).emit()은 같은 방의 다른 사용자들에게만 메시지를 보냅니다. 본인에게는 전송되지 않습니다.

socket.data에 사용자 정보를 저장해두면 이후 이벤트에서 활용할 수 있습니다. 매번 클라이언트에서 정보를 보내지 않아도 됩니다.

send-message 이벤트는 메시지 전송을 처리합니다. 여기서는 io.to(roomId).emit()을 사용합니다.

socket.to()와 달리 io.to()는 본인을 포함한 방의 모든 사용자에게 전송합니다. 본인이 보낸 메시지도 서버를 거쳐 다시 받아야 메시지 순서가 보장됩니다.

메시지 객체에는 고유 ID, 사용자명, 메시지 내용, 타임스탬프를 포함합니다. ID는 React 같은 프레임워크에서 리스트 렌더링 시 key로 사용됩니다.

typing 이벤트는 사용자가 입력 중일 때 호출됩니다. 다른 사용자들에게 "김개발 님이 입력 중입니다"라고 표시할 수 있습니다.

socket.to()를 사용해 본인에게는 보내지 않습니다. 클라이언트 측 구현도 중요합니다.

입력창에 디바운스를 적용해서 타이핑 이벤트가 너무 자주 발생하지 않도록 합니다. 보통 300ms 정도의 딜레이를 줍니다.

실무에서 추가로 고려할 사항이 있습니다. 메시지 영속성을 위해 데이터베이스에 저장해야 합니다.

읽음 표시, 이미지 첨부, 멘션 기능 등도 필요할 수 있습니다. 김개발 씨는 기본 채팅 기능을 하루 만에 완성했습니다.

Socket.io 덕분에 저수준 구현에 시간을 쓰지 않고 비즈니스 로직에 집중할 수 있었습니다.

실전 팁

💡 - 클라이언트에서 메시지를 보낸 후 서버 응답을 기다려 화면에 표시하면 순서가 보장됩니다

  • 타이핑 이벤트에는 디바운스를 적용하여 불필요한 전송을 줄이세요
  • 연결이 끊어졌을 때 재연결되면 이전 대화 내역을 서버에서 다시 불러오세요

6. 연결_관리와_재연결_전략

채팅 서비스가 운영에 들어갔습니다. 그런데 사용자들로부터 불만이 들어오기 시작했습니다.

"갑자기 채팅이 안 돼요", "메시지가 안 보내져요". 로그를 확인해보니 네트워크 불안정으로 연결이 끊어지는 경우가 많았습니다.

안정적인 연결 관리가 필요한 시점이었습니다.

실시간 통신에서 연결 관리는 핵심입니다. 네트워크는 언제든 불안정해질 수 있고, 서버도 재시작될 수 있습니다.

연결 상태 모니터링, 하트비트, 지수 백오프를 적용한 재연결 전략을 통해 사용자 경험을 크게 개선할 수 있습니다.

다음 코드를 살펴봅시다.

// 지수 백오프를 적용한 재연결 전략
class ReconnectingWebSocket {
    constructor(url) {
        this.url = url;
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = 10;
        this.baseDelay = 1000; // 1초
        this.maxDelay = 30000; // 최대 30초
        this.connect();
    }

    connect() {
        this.socket = new WebSocket(this.url);

        this.socket.onopen = () => {
            console.log('연결 성공');
            this.reconnectAttempts = 0; // 성공 시 카운터 초기화
        };

        this.socket.onclose = () => {
            this.scheduleReconnect();
        };
    }

    scheduleReconnect() {
        if (this.reconnectAttempts >= this.maxReconnectAttempts) {
            console.error('최대 재연결 시도 횟수 초과');
            return;
        }
        // 지수 백오프: 1초, 2초, 4초, 8초... 최대 30초
        const delay = Math.min(
            this.baseDelay * Math.pow(2, this.reconnectAttempts),
            this.maxDelay
        );
        console.log(`${delay}ms 후 재연결 시도...`);
        setTimeout(() => this.connect(), delay);
        this.reconnectAttempts++;
    }
}

김개발 씨는 사용자 불만 로그를 분석했습니다. 대부분 모바일 환경에서 발생했습니다.

지하철에서 터널을 지나거나, 와이파이와 LTE 사이를 전환할 때 연결이 끊어졌습니다. 문제는 연결이 끊어진 후였습니다.

사용자가 직접 새로고침하기 전까지 채팅이 작동하지 않았습니다. 재연결 전략이 필요했습니다.

하지만 단순히 연결이 끊어지자마자 즉시 재연결을 시도하면 안 됩니다. 비유를 들어보겠습니다.

전화가 끊어졌을 때 바로 다시 거는 것은 자연스럽습니다. 하지만 상대방이 통화 중이라서 계속 끊어진다면 어떨까요?

1초마다 계속 전화하는 것은 좋은 방법이 아닙니다. **지수 백오프(Exponential Backoff)**는 이 문제를 해결합니다.

첫 번째 재연결은 1초 후, 두 번째는 2초 후, 세 번째는 4초 후... 이렇게 간격을 점점 늘려갑니다.

서버에 문제가 있어도 클라이언트들이 동시에 재연결을 시도해 부하를 주는 것을 방지합니다. 코드에서 Math.pow(2, reconnectAttempts)가 핵심입니다.

시도 횟수에 따라 2의 거듭제곱으로 대기 시간이 늘어납니다. maxDelay로 최대 대기 시간을 제한합니다.

연결에 성공하면 reconnectAttempts를 0으로 초기화합니다. 다음에 또 끊어지면 다시 짧은 간격부터 시작합니다.

**하트비트(Heartbeat)**도 중요한 개념입니다. 주기적으로 작은 메시지를 주고받아 연결이 살아있는지 확인합니다.

네트워크는 연결되어 있지만 상대방 서버가 죽은 경우를 감지할 수 있습니다. Socket.io는 이런 기능을 기본으로 제공합니다.

pingTimeout과 pingInterval 옵션으로 하트비트 간격을 설정할 수 있습니다. 순수 WebSocket을 사용한다면 직접 구현해야 합니다.

실무에서는 연결 상태 UI도 중요합니다. 사용자에게 "연결 중...", "연결됨", "연결 끊김, 재연결 시도 중..." 같은 상태를 표시해주면 사용자 경험이 크게 개선됩니다.

김개발 씨는 재연결 로직과 상태 표시를 추가했습니다. 이후 사용자 불만이 크게 줄었습니다.

연결이 끊어져도 자동으로 복구되고, 사용자는 상태를 알 수 있게 되었습니다. 박시니어 씨가 말했습니다.

"실시간 서비스에서 연결 관리는 기능만큼 중요해요. 네트워크는 항상 불안정하다고 가정하고 설계해야 합니다."

실전 팁

💡 - 지수 백오프에 약간의 랜덤 지터(jitter)를 추가하면 서버 부하를 더 분산시킬 수 있습니다

  • 사용자가 오프라인임을 감지하면 재연결 시도를 멈추고 온라인이 되면 재개하세요
  • 재연결 후에는 필요한 구독이나 인증을 다시 수행해야 합니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#JavaScript#WebSocket#SSE#Socket.io#RealTime#EventDriven#API,WebSocket,실시간

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

API 테스트 전략과 자동화 완벽 가이드

API 개발에서 필수적인 테스트 전략을 단계별로 알아봅니다. 단위 테스트부터 부하 테스트까지, 실무에서 바로 적용할 수 있는 자동화 기법을 익혀보세요.

효과적인 API 문서 작성법 완벽 가이드

API 문서는 개발자와 개발자 사이의 가장 중요한 소통 수단입니다. 이 가이드에서는 좋은 API 문서가 갖춰야 할 조건부터 Getting Started, 엔드포인트 설명, 에러 코드 문서화, 인증 가이드, 변경 이력 관리까지 체계적으로 배워봅니다.

API 캐싱과 성능 최적화 완벽 가이드

웹 서비스의 응답 속도를 획기적으로 개선하는 캐싱 전략과 성능 최적화 기법을 다룹니다. HTTP 캐싱부터 Redis, 데이터베이스 최적화, CDN까지 실무에서 바로 적용할 수 있는 핵심 기술을 초급자 눈높이에서 설명합니다.

OAuth 2.0과 소셜 로그인 완벽 가이드

OAuth 2.0의 핵심 개념부터 구글, 카카오 소셜 로그인 구현까지 초급 개발자를 위해 쉽게 설명합니다. 인증과 인가의 차이점, 다양한 Flow의 특징, 그리고 보안 고려사항까지 실무에 바로 적용할 수 있는 내용을 다룹니다.

JWT 기반 인증 구현하기 완벽 가이드

웹 애플리케이션에서 가장 널리 사용되는 JWT 인증 방식을 초급 개발자도 쉽게 이해할 수 있도록 설명합니다. 토큰의 구조부터 보안 베스트 프랙티스까지 실무에 바로 적용할 수 있는 내용을 담았습니다.