Intro
WebRTC는 CodeArena라는 팀 프로젝트를 진행하면서 처음 사용해보았던 기술입니다.
그 때 당시 과제로 WebRTC 기술을 적용해야 하였고, 저희 팀이 선택한 서비스는 “화면 공유 스트리밍” 서비스 였습니다.
해당 서비스에 대해 배포 환경 및 엔드포인트 구축은 제가 맡아 처리하게 되었습니다.
또한 최근 직무 면접 컨설팅을 받는 도중 한 프로젝트에서 “WebRTC와 WebSocket을 둘 다 사용하였던데, 그 이유가 있나요? 하나의 기술로 나머지 서비스를 대체할 수 있지 않나요” 라는 질문에 제대로된 답변을 못한 것이 이번 포스팅을 통해 학습을 하는 큰 이유 중의 하나입니다..
우선 WebRTC에 대해서 간단하게 짚고 넘어가려 합니다.
WebRTC란?
WebRTC는 Web Real-Time Communication의 줄임말로, 웹 브라우저 간에 플러그인의 도움 없이 서로 통신할 수 있도록 설계된 API입니다. 음성 통화, 영상 통화, P2P 파일 공유 등으로 활용될 수 있습니다.
간단히 말하면 웹에서 실시간 통신을 도와주는 프로토콜입니다.
비교되는 또 다른 기술로는 WebSocket이 있습니다.
WebRTC 프로토콜의 경우 크게 두 가지 기술, 미디어 캡처 기기와 P2P 연결을 다룹니다.
Peer-to-Peer network(P2P 통신)이란?
애플리케이션의 보통인 클라이언트-서버 구조로 하나의 서버가 모든 데이터의 흐름을 처리하는 것이 아닌 중앙 서버 없이 클라이언트들이 서로 서버와 클라이언트가 되어 서로를 연결지어 데이터를 주고받는 것입니다.
P2P 통신이 WebSocket과 WebRTC의 큰 차이점 중 하나입니다.
피어 연결을 위한 구성요소
- NAT(Network Address Translation)
- NAT는 사설 네트워크의 IP 주소를 공인 IP 주소로 변환하여 여러 기기가 하나의 공인 IP 주소를 공유할 수 있게 하는 기술입니다.
- ICE(Interactive Connectivity Establishment)
- P2P 특성 상 Peer의 Public IP를 알고 있어야 하지만, NAT에 의해 매 요청 시 마다 변경되기 때문에 일관성이 무너진다. 이를 해결하기 위해 사용되는 프레임워크로써 두 클라이언트 간에 P2P연결을 도와준다. 또한 ICE는 STUN 및 TURN 서버를 활용하여 여러 후보 경로를 시도하여 최적의 경로를 선택합니다.
- STUN(Session Traversal Utilities for NAT)
- STUN 서버는 클라이언트가 공인 IP 주소와 포트를 알아내는 데 사용됩니다. 클라이언트가 NAT 뒤에 있다고 가정한다면, 이 때 클라이언트의 공인 주소를 얻어 상대 클라이언트에게 제공하는 역할을 수행합니다.
- TURN(Traversal Using Relays around NAT)
- 클라이언트 간의 P2P 연결이 불가능할 경우, 중계 서버로서 역할을 수행합니다.
WebRTC는 이러한 서버들로 구성되어 있으며, STUN 및 TURN 서버를 통해 Peer들간의 원활한 통신을 이룰 수 있습니다.
P2P 연결을 위해 어떤 흐름이 발생하는지?
우선 NAT는 사설 IP 주소를 공인 IP 주소로 변환하는 기술입니다.NAT는 여러 장점이 있지만, WebRTC와 같은 P2P(Peer-to-Peer) 통신에서는 문제가 될 수 있습니다. 왜냐하면 NAT를 통해 변환된 공인 IP 주소와 포트를 상대방이 알아야 직접 연결이 가능하기 때문입니다.
이 때 자신의 공인 IP와 포트를 알기 위해 STUN 서버를 활용합니다. STUN서버에 요청을 보내 자신의 공인 IP와 포트를 획득한 후 연결을 위한 다른 클라이언트들에게 직접 연결을 시도합니다.
만약 Symmetric NAT의 뒤에 있는 호스트의 경우에는 클라이언트의 외부 IP와 포트가 매번 달라지므로, STUN서버를 사용해도 올바른 공용 주소를 얻기 어렵습니다. 이를 위해 TURN서버를 사용하기도 합니다.
TURN 서버는 STUN 서버만으로 해결할 수 없는 NAT 및 방화벽 문제를 중계 서버를 통해 해결하는 데 사용됩니다.
클라이언트들은 TURN서버에 연결 후 클라이언트-서버 형식으로 데이터를 중계 및 양방향 통신을 지속할 수 있습니다.
프로젝트에서의 활용
프로젝트에서 WebRTC를 통해 구현해야 하는 스펙은 다음과 같았습니다.
두 명의 플레이어가 현재 화면 공유를 진행하면, 두 플레이어를 비롯하여 현재 게임을 관전하고 있는 관전자들에게도 화면이 공유가 되어야 합니다. 즉 twitch와 같은 라이브 화면 스트리밍 서비스입니다.
기본적으로 P2P인 WebRTC는 대부분의 미디어 처리가 클라이언트에서 이루어지게 되는데, 서비스 특성 상 이를 위해 WebRTC를 사용하기로 했고, OpenVidu라는 오픈소스를 활용하여 구현을 진행했습니다.
OpenVidu?
OpenVidu는 WebRTC 기반의 비디오 회의, 스트리밍, 녹화 등을 쉽게 구현할 수 있도록 도와주는 상위 레벨의 라이브러리입니다.
https://docs.openvidu.io/en/2.30.0/openvidu3/
해당 공식문서에서 자세한 부분들을 참조할 수 있습니다.
OpenVidu 오픈소스를 활용하면 WebRTC기반의 화상 회의 기능을 매우 쉽게 구축이 가능합니다.
지난번 프로젝트에서는 AWS EC2 상에서의 클라우드 환경으로 배포를 진행하였습니다.
https://docs.openvidu.io/en/stable/deployment/ce/on-premises
OpenVidu의 아키텍처에서 Application Server 부분에 해당하는 WAS에서는 OpenVidu에서 제공하는 2개의 엔드포인트를 구축하였습니다.
@Tag(name = "Openvidu 사용을 위한 컨트롤러", description = "Openvidu 세션 생성 및 연결요청 API")
@Slf4j
@RestController
@RequestMapping("/vidu")
public class OpenviduController {
@Value("${OPENVIDU_URL}")
private String OPENVIDU_URL;
@Value("${OPENVIDU_SECRET}")
private String OPENVIDU_SECRET;
private OpenVidu openvidu;
@PostConstruct
public void init() {
this.openvidu = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET);
}
/**
* @param params The Session properties
* @return The Session ID
*/
@Operation(summary = "방 생성", description = "파라메터로 받은 customSessionId 값을 기준으로 방 생성")
@Parameter(name = "customSessionId", description = "생성하고자 하는 방의 커스텀 세션 ID값")
@PostMapping("/sessions")
public ResponseEntity<String> initializeSession(@RequestBody(required = false) Map<String, Object> params)
throws OpenViduJavaClientException, OpenViduHttpException {
SessionProperties properties = SessionProperties.fromJson(params).build();
log.info(String.valueOf(properties));
try {
Session session = openvidu.createSession(properties);
return new ResponseEntity<>(session.getSessionId(), HttpStatus.OK);
}
catch (Exception e) {
log.error("Exception Msg", e);
return new ResponseEntity<>("이미 존재하는 게임방 ID입니다.", HttpStatus.OK);
}
}
/**
* @param sessionId The Session in which to create the Connection
* @param params The Connection properties
* @return The Token associated to the Connection
*/
@Operation(summary = "방 입장", description = "해당하는 세션ID를 가진 방에 입장")
@Parameters(value = {
@Parameter(name = "sessionId", description = "PathVariable"),
@Parameter(name = "Connection Properties", description = "연결에 필요한 설정값")
})
@ApiResponse(responseCode = "200", description = "The Token associated to the Connection")
@PostMapping("/sessions/{sessionId}/connections")
public ResponseEntity<String> createConnection(@PathVariable("sessionId") String sessionId,
@RequestBody(required = false) Map<String, Object> params)
throws OpenViduJavaClientException, OpenViduHttpException {
Session session = null;
try {
session = openvidu.getActiveSession(sessionId);
}
catch (Exception e) {
log.info("세션을 가져오는 과정에서 문제가 발생했습니다.");
log.error("Exception Msg", e);
return new ResponseEntity<>("입장코드를 통해 세션을 불러오는 과정에서 예기치 못한 오류가 발생했습니다.", HttpStatus.OK);
}
if (session == null) {
return new ResponseEntity<>("해당하는 방이 존재하지 않습니다.", HttpStatus.NOT_FOUND);
}
ConnectionProperties properties = ConnectionProperties.fromJson(params).build();
Connection connection = null;
try {
connection = session.createConnection(properties);
}
catch (Exception e) {
log.info("스트리밍 세션에 접속하는 과정에서 예기치 못한 오류가 발생했습니다.");
log.error("Exception Msg", e);
return new ResponseEntity<>("스트리밍 세션에 접속하는 과정에서 예기치 못한 오류가 발생했습니다.", HttpStatus.OK);
}
return new ResponseEntity<>(connection.getToken(), HttpStatus.OK);
}
}
initializeSession의 경우 게임 매칭이 성사될 경우 방을 생성함과 동시에 세션을 생성하는 엔드포인트입니다.
createConnection의 경우 게임 플레이어 및 관전자들이 방에 참여함과 동시에 화면 송출 및 수신을 할 수 있도록 해당하는 세션에 참여하는 엔드포인트입니다.
이 후 화면공유, 비디오 송/수신에 관련해서는 프론트분께서 처리를 해주었습니다.
[본론] WebRTC vs WebSocket ?
저의 개발방식에 대해 간단히 설명을 하자면, 두 유저의 게임 매칭이 성사 되었을 경우 클라이언트는 총 2번의 서버와의 연결이 필요합니다.
- OpenVidu 세션 생성 및 연결 (화면 공유 및 스트리밍)
- WebSocket 소켓 연결 (실시간 채팅 및 게임 로직 처리)
위와 같은 내용에서 최근 직무 면접 컨설팅에서 오갔던 대화는 다음과 같습니다.
컨설턴트 : “두 번의 연결을 WebRTC 프로토콜만 사용함으로써 한번의 연결로 줄일 수 있지 않나요? 왜 두 가지 기술을 모두 사용했나요?”
나 : “시간 제약으로 인해 OpenVidu라는 잘 활성화 되어있는 오픈소스를 통해 간단한 엔드포인트만을 구현해서 하나의 프로토콜로 모든 로직을 처리할 수 있을지 잘 모르겠습니다. 추후 학습을 통해 두 프로토콜에 대한 분석을 해보고 어떤 게 더 효율적일지 고민을 해봐야할것 같습니다.”
해당 컨설팅을 통해 개발이 끝난 시점이 좀 오래되었고 학습할 시간도 있었지만 하지 않은 점에 대해 반성할 수 있었습니다.
그래서 이번 기회로 두 프로토콜에 대한 특성을 파악하고, 비교 후 어떻게 구현하는게 좀 더 효율적인가!에 대해 찾아보았습니다.
우선 두 프로토콜 모두 실시간 통신을 지원하는 기술입니다.
두 프로토콜의 큰 차이점으로는 P2P vs Client-Server 그리고 그에따른 UDP vs TCP 입니다.
(WebRTC의 경우 기본적으로 낮은 지연시간 및 빠른 전송시간을 위해 UDP 통신을 사용하지만 UDP 사용이 불가한 클라이언트에 한해서 TCP로 유동적인 변경이 이루어진다고는 합니다.)
그 외의 제가 생각하는 주요 차이점은 다음과 같습니다.
WebSocket
- 모든 데이터가 서버를 통과해 다른 클라이언트에게 전송된다. (TCP)
- 이 과정에서 서버는 해당 데이터를 통해 DB I/O, 특정 로직 처리 등이 가능
- 텍스트 및 이진 데이터 전송에 특화되어 있다.
- 메시지가 프레임 구조로 형성
WebRTC
- Peer(클라이언트)들끼리 연결 후 데이터를 주고 받는다. (UDP)
- 서버 통신이 생략되기에 속도는 빠르지만 추가적인 로직 처리가 까다로움
- 오디오 및 비디오 데이터 전송에 특화되어 있다.
- UDP를 기반으로 하는 SRTP를 사용
본론으로 돌아와서, 주요 논점은 “WebSocket이 아닌 WebRTC로 채팅 기능까지 구현해도 되지 않나?”라는 질문에 대한 답변을 정리해야할 것 같습니다.
WebRTC로 채팅과 화면 스트리밍을 구현했을 경우의 장단점
장점같은 경우엔 클라이언트 관점에서 한 번의 연결과정으로 감소되는 부분입니다. 추가적인 웹소켓 연결이 없어지기 때문에 서버의 부하 또한 줄어들테구요. 또한 전체 게임방 관리라는 입장에서 볼 때 관리 또한 편해질 것으로 예상됩니다. 하나의 세션을 통해 추적/업데이트가 가능하기 때문예요.
반대로 단점으로는 UDP 프로토콜의 단점을 그대로 가져가게 되는 것 같습니다. 속도와 반비례하여 신뢰성이 낮기 때문에 화면보다는 채팅 데이터가 유실될 경우 사용자 경험이 많이 내려갈 것으로 예상됩니다. 또한 서버의 추가적인 로직(DB I/O 등)을 위한 별도의 HTTP Request가 다량 발생할 것입니다.
결론
1. 각각의 데이터 타입에 맞는 특화된 기술을 사용하는게 맞다.
데이터 타입에 맞지 않게 하나의 프로토콜로 두 가지 모두 활용할 시 성능적인 이슈가 생길 수 있으며, UDP 특성 상 많은 데이터의 유실로 이어질 수 있다.
2. 프로젝트 요구사항을 고려해야 한다.
프로젝트 요구사항으로 WebSocket을 통한 실시간 채팅뿐만 아니라 플레이어의 게임 로직을 처리를 비롯한 메시지를 통한 DB I/O 과정이 상당 수 존재했습니다. 그 결과 WebRTC 단독 프로토콜 사용은 P2P연결로 인한 중앙 서버의 부재가 되게 크게 다가올 것으로 예상됩니다.
3. 팀의 기술 숙련도와 트레이드 오프를 고려
팀원 모두 해당 서비스를 구현해본 경험이 없었습니다. 물론 WebSocket과 WebRTC에 대한 지식도 없었습니다. 그래서 많은 레퍼런스를 찾아보았고, 그 결과 비교적 쉽게 개발이 가능한 각각의 프로토콜을 사용하게 되었던 것 같습니다.
또한 한 가지 프로토콜을 통한 두 가지 기능을 개발하기에 소요되는 학습시간 대비 효율이 나오지 않았을거라, 그리고 개발 마감에 맞출 수 있었을까에 대한 의문이 예상되기도 합니다.
물론 정답은 아닙니다. 하지만 제가 선택하고 사용한 기술에 대해 의문을 가지는 사람에게 제가 답변을 못해드리는건 너무 아이러니한 상황이라고 생각되네요.
이제껏 Hello World!부터 시작해서 완성된 기능에 대해 흥미를 느끼며 개발을 진행해오고 있었습니다.
하지만 실무를 준비하는 단계에서는 구현 이상으로 효율성을 비롯하여 다각도로 고민을 하며 개발을 해야한다고 생각됩니다.
고민을 통해 한층 성장하는 개발자가 되도록 열심히!
'Develop > Project' 카테고리의 다른 글
[해커톤] 인생 첫 해커톤, 시작부터 수상까지. (8) | 2024.09.14 |
---|---|
[프로젝트 회고] 나의 첫 팀 프로젝트 - CodeArena (0) | 2024.07.17 |
개발 기술 블로그, Dev
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!