연결 단계에서 발견된 간헐적 연결 문제

WebRTC는 RTCPeerConnection과 ICE 프로토콜을 통해 두 클라이언트 간 최적 경로를 탐색한다.

const peerConnection = new RTCPeerConnection({
  iceServers: [{ urls: "example.google.com:19302" }],
});

peerConnection.onicecandidate = (event) => {
  if (event.candidate) {
    socket.emit("ice-candidate", event.candidate);
  }
};

peerConnection
  .createOffer()
  .then((offer) => peerConnection.setLocalDescription(offer))
  .then(() => {
    socket.emit("offer", peerConnection.localDescription);
  });

해외 네트워크 환경을 사용하는 사용자들의 연결 기능에서 다음과 같은 현상이 간헐적으로 발생했다:

  • 영상이 연결되지 않거나 상대가 보이지 않음
  • 상태 동기화나 메시지 수신이 실패함
  • 네트워크 문제인지 브라우저 문제인지 식별하기 어려움
  • 사용자 입장에서는 연결 상태에 대한 명확한 피드백이 없어 혼란을 느낌

📌 VPN 사용 환경에서의 연결 실패

또한 일부 사용자들이 VPN을 활성화한 상태에서 접속하는 경우, WebRTC 연결이 실패하는 이슈가 존재했습니다.

이 경우 STUN 서버가 공인 IP를 제대로 파악하지 못하거나,

VPN이 UDP 및 P2P 트래픽을 제한하는 경우가 많아 ICE candidate 수집 자체가 실패할 수 있다.

// ICE candidate 로그 분석으로 VPN 의심 환경 확인
peerConnection.onicecandidate = (event) => {
  if (event.candidate) {
    const cand = event.candidate.candidate;
    if (cand.includes("relay")) {
      console.log(
        "VPN 또는 제한된 네트워크일 경우 설정에서 VPN 기능을 끄고 사용하시길 권장합니다.",
      );
    } else if (cand.includes("host")) {
      console.log("직접 연결(host candidate)");
    }
  } else {
    console.log("ICE candidate 수집 종료");
  }
};

프론트엔드에서는 이를 직접 감지할 수는 없지만,
연결 실패 시 “VPN이 켜져 있다면 확인해 주세요"라는 안내 메시지를 추가함으로써
사용자 혼란을 줄이는 데 도움이 되었다.

이 문제를 계기로, 연결 상태에 대한 진단 및 복원 전략을 설계하게 되었다.


전략: 테스트 영상 연결 시스템 구축

영상 연결 시작 전, 네트워크 상태를 점검할 수 있는 별도의 테스트 연결 시스템을 구축했다.

목표는 단순했다:

  • 사용자 측의 네트워크가 WebRTC 연결에 적합한지 사전 진단
  • 연결이 정상인지 아닌지를 눈으로 확인할 수 있는 구조
  • 실패 시, 관리자가 사전 대응(재접속 유도, 전화 연결 등)을 할 수 있도록

Socket.IO로 연결 상태 추적

WebRTC의 연결 여부 자체는 API만으로 감지하기 어렵기 때문에,
우리는 WebRTC 연결과 함께 Socket.IO를 통해 연결 상태를 판단했다.

// useSocket.ts

const socket = io(SOCKET_URL, {
  reconnection: true,
  reconnectionAttempts: 5, // 5회까지 재시도
  reconnectionDelay: 3000, // 3초 간격
  timeout: 8000, // 연결 시도 제한 시간 8초
});

useEffect(() => {
  socket.on("connect", () => {
    setStatus("connected");
  });

  socket.on("disconnect", () => {
    setStatus("disconnected");
  });

  socket.on("reconnecting", () => {
    setStatus("reconnecting");
  });

  socket.on("connect_error", () => {
    setStatus("error");
  });
}, []);

이 이벤트 흐름을 통해 연결이 끊겼는지, 재시도 중인지, 정상 연결되었는지를 클라이언트에서 실시간으로 인지할 수 있었다.


연결 상태를 UI로 시각화

우리는 단순한 문자열 표시가 아닌, 직관적인 색상 원형 표시를 사용해 네트워크 상태를 표현했다.

// Indicator.tsx
import React from "react";
import styled from "styled-components";

type Status = "connected" | "reconnecting" | "disconnected" | "error";

interface IIndicatorTypeProps {
  status: Status;
}

const statusColorMap: Record<Status, string> = {
  connected: "#4CAF50", // green
  reconnecting: "#FFEB3B", // yellow
  disconnected: "#F44336", // red
  error: "#9E9E9E", // gray
};

const Dot = styled.div<{ color: string }>`
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: ${({ color }) => color};
  margin-right: 8px;
`;

const Indicator: React.FC<IIndicatorTypeProps> = ({ status }) => {
  return <Dot color={statusColorMap[status]} />;
};

export default Indicator;

이러한 UI는 사용자 또는 관리자가 한눈에 상황을 파악할 수 있도록 해주었고,
사용자에게도 “지금 연결이 안 되고 있다"는 명확한 피드백을 제공해 UX 혼란을 줄일 수 있었다.


재연결 로직 및 수동 재시도

Socket.IO는 자동 reconnect 기능을 제공하지만,
간헐적인 실패에 대비하여 수동 재시도 버튼도 함께 제공했다.

{
  status === "disconnected" && (
    <button onClick={manualReconnect}>다시 연결 시도</button>
  );
}

이때 manualReconnect 함수는 내부적으로 소켓과 WebRTC 스트림을 초기화한 뒤, 화면을 다시 렌더링하며 재연결을 시도했다.


시스템 적용 이후의 변화

이 테스트 시스템 도입 후, 다음과 같은 개선이 있었다:

  • 연결 실패율이 감소했고, 실패 시 사전 감지가 가능해짐
  • 연결 문제에 대한 피드백이 시각적으로 제공되어 UX 혼란이 줄어듦
  • 담당자와 사용자 모두가 상태 인지 → 대처 흐름을 이해하기 쉬워짐

마무리하며

WebRTC는 네트워크 품질과 연결 상태에 큰 영향을 받는다.
특히 해외 사용자 대상의 시스템에서는 환경이 예측 불가했기 때문에,
연결 실패 자체보다도 실패했는지 조차 모르는 상황이 더 큰 문제였다.

Socket.IO와 간단한 연결 UI를 기반으로 한 이 테스트 시스템은
WebRTC 인프라를 보완하지 않고도 문제를 조기에 감지하고 대응할 수 있는 프론트엔드 측 해결책이었다.