Show HN: BinaryRPC – 현대적인 C++ 기반의 경량 WebSocket RPC 프레임워크
요약
BinaryRPC는 uWebSockets를 기반으로 구축된 현대적인 C++ 기반 경량 WebSocket RPC 프레임워크입니다. 초저지연성과 고처리량을 목표로 설계되어 멀티플레이어 게임, 금융 데이터 스트림, IoT 대시보드 등 지연 시간에 민감한 애플리케이션에 최적화되어 있습니다.
핵심 포인트
- uWebSockets 기반의 초저지연 RPC 프레임워크
- 미들웨어 및 세션 로직을 지원하는 현대적 아키텍처
- 멀티플레이어 게임 및 금융 틱 스트림에 최적화
- C++를 활용한 저수준 시스템 제어와 높은 개발자 경험 제공
BinaryRPC
🧭 동기 (Motivation)
이전 직장에서 근무할 당시, Java로 WebSocket 서버 프로토타입을 개발한 적이 있습니다. 하지만 시간이 흐르면서 성능 문제가 발생하기 시작했습니다. 이로 인해 더 빠른 속도와 저수준 시스템 제어 (low-level system control)를 제공하는 언어인 C++로 눈을 돌리게 되었습니다. 조사 과정에서 uWebSockets가 성능 측면에서 가장 좋은 선택지 중 하나라는 것을 발견했고, 이 라이브러리를 사용하여 개발을 시작했습니다.
하지만 uWebSockets는 매우 "코어 (core)"적인 라이브러리이기 때문에, 많은 세부 사항을 직접 처리해야 했습니다. 단순한 기능을 하나 추가하는 것조차 많은 인프라 관리가 필요했습니다. 이 과정에서 C++ 생태계의 다른 라이브러리들을 탐색해 보았지만, 대부분 너무 무겁거나 개발자 경험 (developer experience)을 우선시하지 않았습니다.
과거에 Node.js와 Express.js를 사용하여 많은 프로젝트를 개발했던 경험을 바탕으로, 저는 다음과 같은 아이디어를 떠올렸습니다:
"Express.js처럼 미들웨어 (middleware)와 세션 로직 (session logic)을 지원하는 현대적인 RPC 프레임워크를 C++로 만들면 어떨까?"
이 아이디어를 염두에 두고, 세션 관리 (session management), 미들웨어 구조, 그리고 RPC 핸들러 아키텍처 (RPC handler architecture)를 포함하는 시스템을 설계하기 시작했습니다. 처음에는 그저 꿈에 불과했지만, 시간이 지나면서 그 꿈을 위한 구성 요소들을 하나씩 구축해 나갔습니다. 졸업 전에 이 프레임워크의 기반을 닦았고, 졸업 후에는 저 자신에게 주는 선물임과 동시에 개발자 커뮤니티에 대한 기여로서 이를 오픈 소스 (open source)로 공개하기로 결정했습니다.
때로는 C++가 제공하는 방대한 제어권 속에서 길을 잃기도 했지만, 한 단계씩 나아가 현재의 상태에 이르게 되었습니다. 개발자가 필요로 할 만한 필수적인 요소들을 포함하려고 노력했습니다. 이 프로젝트는 사실 갓 졸업한 엔지니어가 세상에 전하고 싶었던 진심 어린 기여이기에, 여러분이 문제없이 즐겁게 사용하시기를 바랍니다.
향후에는 이 아키텍처를 액터-메일박스 모델 (actor-mailbox model)로 진화시키는 것을 목표로 하고 있습니다. 하지만 현재의 구조를 먼저 더욱 공고히 할 필요가 있다고 믿습니다. 이 과정에 대해 제안이나 기여할 점이 있다면 언제든 편하게 연락해 주세요.
BinaryRPC는 uWebSockets를 기반으로 구축된 고처리량 (high-throughput) RPC 프레임워크입니다.
이 프레임워크는 멀티플레이어 게임, 금융 틱 스트림 (financial tick streams), IoT 대시보드와 같이 지연 시간에 민감한 (latency-sensitive) 애플리케이션을 위해 설계되었으며, 초저지연 (ultra-low latency)과 최소한의 오버헤드 (minimal overhead)를 제공합니다. 모듈형 아키텍처 (modular architecture)와 효율적인 네트워킹을 통해, BinaryRPC는 리소스 사용량과 개발자 경험 (developer experience) 측면 모두에서 경량 상태를 유지합니다.
✨ 주요 특징 (Highlights)
| 기능 | 설명 |
|---|---|
| ⚡ WebSocket 전송 (transport) | uWebSockets 및 epoll/kqueue를 통해 구동되는 매우 빠른 네트워킹. |
| ... | --- |
이 프로젝트는 여러 외부 의존성 (external dependencies)을 가진 현대적인 C++ RPC 프레임워크입니다. 이를 사용하려면 먼저 필요한 의존성(아래 참조)을 설치한 다음, 프로젝트를 빌드하고, 마지막으로 본인의 프로젝트에 링크해야 합니다. example_server 디렉토리는 전형적인 사용법을 보여줍니다.
1. 사전 요구 사항 (Prerequisites)
- CMake: 버전 3.16 이상.
- C++ 컴파일러 (C++ Compiler): C++20을 지원하는 컴파일러 (예: MSVC, GCC, Clang).
- Git: 저장소 클로닝 (cloning) 용.
2. 라이브러리 빌드 및 설치 (Building and Installing the Library)
이 과정은 라이브러리를 컴파일합니다.
# 1. 저장소 클로닝
git clone https://github.com/efecan0/binaryrpc-framework.git --recurse-submodules
cd binaryrpc-framework
...
3. 본인의 프로젝트에서 BinaryRPC 사용하기 (Using BinaryRPC in Your Own Project)
본인의 CMake 프로젝트에서 BinaryRPC를 사용하려면, CMakeLists.txt에 다음을 추가하기만 하면 됩니다:
# 설치된 binaryrpc 패키지 찾기
find_package(binaryrpc 0.1.0 REQUIRED)
...
🚪 커스텀 핸드셰이크 및 인증 (IHandshakeInspector)
모든 WebSocket 업그레이드가 가장 먼저 마주하는 것은 IHandshakeInspector입니다. 기본 구현체는 단순히 소켓을 수락하고 URL 쿼리 스트링 (query-string)으로부터 단순한 ClientIdentity를 생성합니다. 실제 운영 환경 (production)에서는 거의 항상 이를 상속 (sub-class) 하여 사용하게 됩니다.
아래는 참조용 **채팅 서버 (chat server)**에서 실제로 실행되는 CustomHandshakeInspector의 축약된 버전입니다:
class CustomHandshakeInspector : public IHandshakeInspector {
public:
std::optional<ClientIdentity> extract(uWS::HttpRequest& req) override {
...
연결 파라미터 (Connection Parameters)
위의 샘플 인스펙터(inspector)는 클라이언트가 WebSocket URL에 세 가지 쿼리 파라미터 (query parameters)를 제공할 것을 요구합니다:
clientId: 불투명한 사용자 문자열 (예: 이메일 또는 데이터베이스 ID).deviceId: 물리적 장치를 식별하는 정수.sessionToken(선택 사항): 이전 세션을 재개하기 위한 32자 16진수 문자열.
만약 extract()가 std::nullopt를 반환하면, 업그레이드는 400 Bad Request와 함께 거부됩니다. 만약 sessionToken이 없거나 유효하지 않은 경우, 보안 인스펙터는 새 토큰을 생성하고 연결 성공 후 클라이언트에게 이를 제공해야 합니다.
대안: JWT 기반 핸드셰이크 (JWT-based Handshake)
인증을 위해 JSON Web Token (JWT)와 같은 표준을 사용하는 것을 선호한다면, 쿼리 파라미터 대신 HTTP 헤더를 확인하는 인스펙터를 작성할 수 있습니다.
// 비즈니스 로직을 위한 전방 선언 (Forward declarations)
ClientIdentity makeClientFromJwt(const std::string& token);
bool verifyJwt(const std::string& token);
...
활성 세션 재사용 (Reusing an active session)
만약 인스펙터가 반환한 ClientIdentity의 sessionToken이 동일한 clientId + deviceId를 가진 활성 세션(즉, sessionTtlMs 이내의 세션)과 일치한다면, BinaryRPC는 다음과 같이 동작합니다:
- 새 소켓을 해당 세션에 연결 (Attach) 합니다.
- 아웃박스 (outbox)에서 여전히 대기 중인 모든 QoS-1/2 프레임을 재전송 (Replay) 하여, 클라이언트가 모든 오프라인 메시지를 볼 수 있도록 합니다.
FrameworkAPI를 통해 저장한 모든 커스텀 필드(예: 인벤토리, 로비, XP 등)를 유지합니다. 클라이언트는 연결이 끊긴 적이 없는 것처럼 계속 진행됩니다.
사용자 측에서 추가할 코드는 없습니다. 단지 인스펙터가 이전에 발급된 정확한 16바이트 토큰을 다시 전달하기만 하면 됩니다.
🚦 신뢰성 및 QoS (Reliability & QoS)
BinaryRPC는 MQTT에서 영감을 얻었지만 WebSocket에 맞게 조정된 **세 가지 전달 계층 (delivery tiers)**을 제공합니다:
| 레벨 | 보장 사항 | 프레임 흐름 |
|---|---|---|
QoSLevel::None | 최대 한 번 (At-most-once). 발사 후 망각 (Fire-and-forget) – 가장 낮은 지연 시간. | DATA → 클라이언트 (client) |
| ... |
sessionTtlMs가 중요한 이유 🤔
sessionTtlMs는 BinaryRPC가 연결이 끊긴 클라이언트의 세션(session)을 메모리 및 디스크 아웃박스(outbox)에 얼마나 오래 유지할지를 정의합니다:
-
원활한 재연결 (Seamless reconnects) – 모바일/Wi-Fi 사용자는 몇 초 동안 연결이 자주 끊깁니다. TTL(Time-To-Live) 이내에 다시 접속하기만 하면, 프레임워크가 새 소켓을 기존
Session에 결합하므로 누구도 다시 로그인하거나 다시 동기화할 필요가 없습니다. -
신뢰할 수 있는 오프라인 푸시 (Reliable offline push) – 그 사이에 대기 중이던 모든 QoS-1/2 프레임은 사용자가 다시 온라인 상태가 되는 즉시 플러시(flush)되어 순서가 보존됩니다.
-
중복 방지 (Duplicate shielding) – ExactlyOnce의 커밋 원장(commit ledger) 또한 유지되므로, 이전 소켓으로부터의 재시도(retry)를 인식하고 무시합니다.
키오스크/LAN 앱의 경우 RAM을 공격적으로 확보하기 위해 값을 작게(예: 5초) 설정하고, 불안정한 모바일 네트워크나 맵 로드 시 재연결되는 게임 로비의 경우 값을 크게(몇 분 단위) 설정하십시오.
ExactlyOnce를 사용하면 BinaryRPC는 외부로 나가는 프레임을 **세션 아웃박스 (session outbox)**에 영구 저장합니다. 소켓이 끊기더라도 **세션 (session)**이 살아있다면 (sessionTtlMs), 대기 중인 모든 프레임은 재연결 시 자동으로 다시 재생됩니다 – 무료로 오프라인 메시징 기능을 사용하는 셈입니다.
ReliableOptions
WebSocketTransport::setReliable(options)를 통해 세부 동작을 구성하십시오:
struct ReliableOptions {
...
커스텀 백오프 정책 (Custom back-off policy)
자체적인 IBackoffStrategy 구현체를 제공하십시오:
class FibonacciBackoff : public IBackoffStrategy {
...
사용자의 전략은 시도 인덱스 (attempt index) (0부터 시작)를 전달받으며, 다음 재시도 전까지의 지연 시간을 반환합니다. 이 전략은 상태가 없을 수도 있고, 지터(jitter)를 위해 난수 생성기(RNG)를 사용할 수도 있습니다.
🗄️ FrameworkAPI를 통한 세션 관리
FrameworkAPI는 전송 계층(transport)과 락 프리(lock-free) SessionManager를 결합합니다.
연결된(또는 최근에 연결이 끊긴!) 모든 클라이언트에게 데이터를 저장 (store), 검색 (search) 및 푸시 (push) 하는 데 사용하십시오.
using namespace binaryrpc;
...
생명주기 알림 (Lifecycle notifications) (계획 중)
SessionManager는 현재 내장된 onCreate/onDestroy 콜백을 제공하지 않습니다. 현재 접속 상태 확인(presence)이나 감사 로그(audit logging)가 필요한 경우, 다음 방법을 통해 구현하십시오:
-
미들웨어 (Middleware) – 전역 미들웨어를 추가합니다. 세션 레코드로부터 첫 번째 RPC가 오면 "온라인(online)"으로 처리하고, 타임아웃 후
fw.disconnect()를 호출하여 "오프라인(offline)"을 기록합니다. -
커스텀 플러그인 (Custom plugin) – 몇 초마다
app.getSessionManager().allSessions()를 폴링(poll)하여 리스트를 비교(diff)한 뒤 이벤트를 발생시킵니다.
네이티브 훅(Native hooks)은 로드맵에 포함되어 있습니다. 머지(merge)되면 app.run() 호출 전에 람다(lambda)를 등록할 수 있게 됩니다. 진행 상황은 리포지토리의 issue #42를 확인하세요.
indexed 플래그 – 언제 설정해야 하는가
indexed | 동작 (Behaviour) | 복잡도 (Complexity) |
|---|---|---|
false (기본값) | 값을 세션 블롭(session blob)에만 유지합니다. | 조회(Look-up) ⇒ O(N) 스캔 |
true | 값을 전역 해시 맵(hash-map)에도 저장합니다. | fw.findBy() ⇒ O(1) |
필터링 조건으로 사용하는 식별자(예: userId, roomId)에는 indexed=true를 사용하고, 일시적이거나 카디널리티(cardinality)가 높은 데이터는 인덱스를 설정하지 않은 상태로 유지하십시오.
Session 객체는 명시적으로 disconnect()를 호출하거나 전송 계층(transport)의 sessionTtlMs가 만료될 때까지 유지됩니다. 모든 QoS 전달 보장은 해당 TTL 내의 재연결(reconnects) 시에도 유지됩니다.
예시: 상태 유지 및 대상 사용자 지정
// 1️⃣ 상태 유지 (선택적으로 인덱스 설정 가능)
...
🗺️ 아키텍처 개요 (Architecture Overview)
+-----------------------------+
| raw bytes --> IProtocol |
| (parse) |
...
모든 사각형은 교체 가능합니다: 인터페이스를 구현하여 자신만의 것을 플러그인(plug) 하십시오.
🛠️ 커스텀 설정 치트 시트 (Customisation Cheat-Sheet)
| 변경하려는 항목 | 방법 |
|---|---|
| QoS 레벨 / 재시도 (retries) | WebSocketTransport::setReliable(ReliableOptions). 필드: level, baseRetryMs, maxRetry, maxBackoffMs, sessionTtlMs, backoffStrategy. |
| ... |
🔄 미들웨어 관리 (Middleware Management)
BinaryRPC는 애플리케이션에 미들웨어를 부착하는 세 가지 방법을 제공합니다:
1. 전역 미들웨어 (use)
모든 RPC 호출에 미들웨어를 부착합니다:
app.use([](Session& session, const std::string& method, std::vector<uint8_t>& payload, NextFunc next) {
// 모든 RPC 실행 전에 실행됨
LOG_DEBUG("Incoming request: " + method);
...
2. 단일 메서드 미들웨어 (useFor)
특정 RPC 메서드에 미들웨어를 부착합니다:
app.useFor("login", [](Session& session, const std::string& method, std::vector<uint8_t>& payload, NextFunc next) {
// "login" RPC 실행 전에만 실행됨
auto req = parseMsgPackPayload(payload);
...
3. 다중 메서드 미들웨어 (useForMulti)
여러 RPC 메서드에 미들웨어를 부착합니다:
app.useForMulti({"send_message", "join_room"}, [](Session& session, const std::string& method, std::vector<uint8_t>& payload, NextFunc next) {
// "send_message" 및 "join_room" RPC 실행 전에 실행됨
if (!session.isAuthenticated()) {
...
미들웨어 체인 실행 (Middleware Chain Execution)
- 미들웨어는 등록된 순서대로 실행됩니다.
- 각 미들웨어는 체인을 계속 진행하기 위해 반드시
next()를 호출해야 합니다. - 미들웨어에서 예외(Exception)가 발생하면 체인이 중단되고 에러가 클라이언트로 전송됩니다.
- 미들웨어는 RPC 실행 전후에
Session과payload를 수정할 수 있습니다.
일반적인 사용 사례 (Common Use Cases)
- 인증/인가 (Authentication/Authorization)
- 속도 제한 (Rate limiting)
- 요청 로깅 (Request logging)
- 입력 유효성 검사 (Input validation)
- 세션 관리 (Session management)
- 에러 처리 (Error handling)
전체 미들웨어 설정 예시:
// 전역 로깅
app.use([](Session& session, const std::string& method, std::vector<uint8_t>& payload, NextFunc next) {
LOG_INFO("Request: " + method);
...
즉시 사용 가능한 미들웨어 (Ready-to-Use Middleware)
직접 로직을 작성하는 것 외에도, BinaryRPC는 일반적인 시나리오를 위한 사전 구축된 모듈을 제공합니다.
속도 제한기 (rate_limiter.hpp)
일반적인 예시에서처럼 로직을 수동으로 구현하는 대신, 토큰 버킷 (Token Bucket) 알고리즘을 사용하는 전용 RateLimiter 모듈을 사용할 수 있습니다.
예시: 로그인 시도를 5초당 1회로 제한합니다.
#include <binaryrpc/middlewares/rate_limiter.hpp>
#include <binaryrpc/core/app.hpp>
...
사용자가 제한을 초과하면, 미들웨어 (middleware)는 ErrorObj를 던지며(throw), 클라이언트는 "Too many requests" 메시지를 받게 됩니다.
JWT 인증 (jwt_auth.hpp)
이 미들웨어 (middleware)는 JSON Web Token (JWT)을 사용하여 들어오는 RPC를 검증합니다.
예시: 페이로드 (payload)에서 토큰을 기대하도록 설정하여 "get_profile" RPC를 보호합니다.
#include <binaryrpc/middlewares/jwt_auth.hpp>
#include <binaryrpc/core/app.hpp>
#include <nlohmann/json.hpp>
...
📞 RPC 등록 및 응답
AI 자동 생성 콘텐츠
본 콘텐츠는 HN Design Systems의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기