DEV Community

바람의평온
바람의평온

Posted on

WebRTC 홈캠 앱: WebSocket 시그널링 서버 구축 경험

WebRTC 홈캠 앱: WebSocket 시그널링 서버 구축 경험

WebRTC는 P2P 영상 통신을 위한 강력한 도구입니다. 하지만 처음 연결을 맺는 시그널링 과정은 별도 서버의 몫입니다. 영상 데이터는 두 장치 간에 직접 흐르지만, 그 연결을 위한 정보 교환은 중간 서버가 담당해야 했습니다. 가족용 홈캠 앱을 만들면서 이 부분을 직접 구현한 경험을 공유합니다. 이번 작업으로 WebRTC 연결의 핵심인 시그널링 서버의 역할과 그 구현 방식을 명확히 이해할 수 있었네요.

이런 분께 — WebRTC로 P2P 영상 앱을 처음 만들며 시그널링 문제에 부딪힌 개발자 · 난이도는 중급 정도

여기서 확인할 것

WebRTC에서 시그널링 서버가 왜 필요한지 알 수 있습니다.
WebSocket을 활용한 시그널링 서버 구축 방법을 파악합니다.
WebRTC 연결을 위한 SDP 및 ICE 후보 교환 과정을 이해합니다.
Node.js와 Flutter에서 시그널링 서버와 연동하는 방식을 배웁니다.
시그널링 서버가 영상 트래픽과 무관하게 가볍게 운영되는 이유를 확인합니다.

WebRTC 영상은 P2P, 연결은 시그널링 서버가

WebRTC는 미디어 데이터를 피어 투 피어(P2P)로 직접 주고받는 기술입니다. 그래서 영상 품질이 아무리 높아져도 서버의 부하에는 영향을 주지 않습니다. 하지만 이 P2P 연결을 시작하기 전에 두 피어가 서로의 접속 정보, 즉 SDP(Session Description Protocol) 오퍼/앤서와 ICE(Interactive Connectivity Establishment) 후보들을 교환해야 합니다. 이 초기 정보 교환을 담당하는 것이 바로 '시그널링 서버'입니다. WebRTC 표준은 미디어 연결(PeerConnection)만 정의하고, 이 시그널링 방식은 개발자에게 맡깁니다. 홈캠 앱을 만들면서 이 부분을 간과하기 쉬웠는데, 결국 별도의 시그널링 서버를 구축해야 한다는 결론에 도달했습니다. 서버는 영상 데이터를 직접 처리하는 일 없이, 오직 작은 연결 정보만 중개하는 역할을 맡게 됩니다.

시그널링 채널에 양방향 통신이 필요한 이유

홈캠처럼 카메라와 뷰어가 항상 동시에 온라인 상태가 아닐 수 있습니다. 뷰어가 특정 카메라에 연결을 요청할 때, 카메라가 오프라인일 수도 있고, 온라인이라도 서버가 뷰어의 요청을 카메라에 전달해줘야 합니다. 단순한 HTTP 요청-응답 방식으로는 이러한 '누가 누구에게 통화를 거는지' 라우팅이나 비동기적인 상태 변화 알림을 처리하기 어렵습니다. 양쪽에서 필요할 때마다 메시지를 주고받을 수 있는 양방향 통신 채널이 필수적이더군요. WebSocket은 이러한 요구사항에 적합한 기술입니다. 한 번 연결되면 클라이언트와 서버가 자유롭게 데이터를 주고받을 수 있어, 피어의 온라인/오프라인 상태 변화를 실시간으로 알리거나, 통화 요청을 즉시 전달하기에 좋았습니다.

Node.js와 WebSocket으로 중개 서버 만들기

저는 Node.js 환경에서 ws 라이브러리를 활용해 WebSocket 시그널링 서버를 구축했습니다. 특정 경로(/ws)로 접속하는 클라이언트들을 관리하도록 구성했습니다. 각 기기가 서버에 접속하면 register 메시지로 자신의 deviceId를 등록하고, 서버는 이 정보를 바탕으로 누가 온라인 상태인지 관리하고 특정 피어에게 메시지를 전달하는 라우팅 역할을 수행합니다. 서버는 다양한 제어 메시지 중 미리 정의된 몇 가지 타입만 그대로 전달하도록 했습니다. 이는 서버의 역할을 최소화하고 불필요한 메시지 처리 부하를 줄이기 위함입니다. 아래는 서버 초기 설정과 메시지 타입 필터링 코드입니다.

const wss = new WebSocketServer({ server, path: '/ws' });
wss.on('connection', (ws, req) => { /* register / relay */ });

const RELAY_TYPES = new Set([
'call-request','call-accept','call-reject',
'offer','answer','ice','hangup','switch-camera',
]);

이처럼 서버는 오직 정해진 제어 메시지만 중계하며, 미디어 데이터는 직접 관여하지 않습니다.

Flutter 클라이언트와 전체 연결 흐름

클라이언트 앱은 Flutter로 개발했으며, WebRTC 기능은 flutter_webrtc 패키지를, 시그널링 서버와의 통신은 web_socket_channel 패키지를 사용했습니다. pubspec.yaml 파일에 아래와 같이 의존성을 추가했습니다.

flutter_webrtc: ^0.12.5 # PeerConnection / 미디어
web_socket_channel: ^3.0.2 # 시그널링 채널

전체 연결 흐름은 다음과 같습니다. 뷰어 앱이 카메라에 call-request를 보내면 서버가 이를 카메라에 전달합니다. 카메라 앱은 call-accept와 함께 SDP offer를 생성하여 서버를 통해 뷰어에게 보냅니다. 뷰어는 이 offer를 받아 SDP answer를 만들고 다시 서버를 통해 카메라에 전달합니다. 이후 양쪽은 서로의 ICE candidate를 서버를 통해 교환하며 최종적으로 P2P 연결을 맺습니다. 이 과정이 완료되면 영상 데이터는 두 기기 사이에서 직접 흐르기 시작합니다.

가벼운 시그널링 서버의 역할 확인

구현 후 실제로 카메라 앱에서 영상 송출을 시작하고 뷰어 앱에서 해당 카메라를 선택하자, offer와 answer, 그리고 ICE candidate 교환 로그가 순서대로 콘솔에 출력되는 것을 확인했습니다. 곧이어 뷰어 화면에 원격 영상이 정상적으로 나타나더군요. 한쪽 앱을 종료하면 상대방에게 peer-offline 메시지가 전달되어 UI가 즉시 '오프라인' 상태로 바뀌는 것도 보았습니다. 가장 중요하게 확인한 점은 시그널링 서버의 트래픽이었습니다. 서버는 영상 데이터를 전혀 처리하지 않고, 오직 작은 JSON 형태의 제어 메시지만 중계합니다. 따라서 영상의 화질이 높아지거나 연결된 피어의 수가 늘어나도 시그널링 서버 자체의 부하는 크게 증가하지 않는 것을 관찰할 수 있었습니다. 이는 WebRTC 아키텍처의 중요한 장점 중 하나입니다.

마치며

WebRTC에서 영상은 P2P로 흐르지만, 그 연결의 시작은 반드시 시그널링 서버가 책임진다는 점을 명확히 기억하는 것이 중요합니다. 이 서버는 영상 데이터를 건드리지 않고, 오직 작은 제어 메시지만 중계하므로 가볍게 설계해야 합니다.

Top comments (0)