[HN Gopher] Show HN: BinaryRPC - Lightweight WebSocket-based RPC...
___________________________________________________________________
Show HN: BinaryRPC - Lightweight WebSocket-based RPC framework in
modern C++
Hi HN, I'm a recent CS graduate. During the past few months I
wrote BinaryRPC, an open-source RPC framework in modern C++20
focused on low-latency, binary WebSocket messaging. Why I built it
* Wanted first-class session support, pluggable QoS levels and a
simple middleware chain (global, specific, multi handler) without
extra JSON/XML parsing. * Easy developer experience A quick
feature list * Binary WebSocket frames - minimal overhead * Built-
in session layer (login / reconnect / heartbeat) * QoS1 / QoS2 with
automatic ACK & retry * Plugin system - rooms, msgpack, etc. can be
added in one line * Thread-safe core: RAII + folly Still early
(solo project), so any feedback on design, concurrency model or
missing must-have features would help a lot. Thanks for reading!
also see "Chat Server in 5 Minutes with BinaryRPC":
https://medium.com/@efecanerdem0907/building-a-chat-server-i...
Author : efecan0
Score : 60 points
Date : 2025-07-12 16:32 UTC (6 hours ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| jayd16 wrote:
| My immediate reaction is why websocket based design and TCP (?)
| over gRPC with http/3 and UDP and multiplexing and such?
| efecan0 wrote:
| I started with WebSocket over TCP for practical reasons:
|
| * Works everywhere today (browsers, LB, PaaS) with zero extra
| setup. * One upgrade -> binary frames; no gRPC/proto toolchain
| or HTTP/3 infra needed. * Simple reliability: TCP handles
| ordering; I add optional QoS2 on top. * Lets me focus on
| session/room/middleware features first; transport is swappable
| later.
|
| QUIC / gRPC-HTTP/3 is on the roadmap once the higher-level API
| stabilises.
| inetknght wrote:
| I'm not the author but off the top of my head:
|
| - gRPC is not a library I would trust with safety or privacy.
| It's used a lot but isn't a great product. I have personally
| found several fuckups in gRPC and protobuf code resulting in
| application crashes or risks of remote code execution. Their
| release tagging is dogshit, their implementation makes you
| think the standard library and boost libraries are easy to read
| and understand, and neither takes SDLC lifecycles seriously
| since there aren't sanitizer builds nor fuzzing regime nor
| static analysis running against new commits last time I
| checked.
|
| - http/3 using UDP sends performance into the crater, generally
| requiring _every_ packet to reach the CPU in userspace instead
| of being handled in the kernel or even directly by the network
| interface hardware
|
| - multiplexing isn't needed by most websocket applications
| efecan0 wrote:
| Thank you for the extra information!
|
| I am a recent CS graduate and I work on this project alone. I
| chose WebSocket over TCP because it is small, easy to read,
| and works everywhere without extra tools. gRPC + HTTP/3 is
| powerful but adds many libraries and more code to learn.
|
| When real users need QUIC or multiplexing, I can change the
| transport later. Your feedback helps me a lot.
| reactordev wrote:
| The point people are beating around the bush at here is
| that a binary RPC framework has no such need for HTTP
| handling, even for handshaking, when a more terse protocol
| of your own design would/could/might? be better.
|
| I totally understand your reasoning behind leaning on
| websockets. You can test with a data channel in a browser
| app. But if we are talking low-latency, Superman fast,
| modern C++, RPC and forgeddaboutit. Look into handling an
| initial payload with credential negotiation outside of HTTP
| 1.1.
| efecan0 wrote:
| You're right: HTTP adds an extra RTT and headers we don't
| strictly need.
|
| My current roadmap is:
|
| 1. Keep WebSocket as the "zero-config / browser-friendly"
| default. 2. Add a raw-TCP transport with a single-frame
| handshake: [auth-token | caps] - ACK - binary stream
| starts. 3. Later, test a QUIC version for mobile / lossy
| networks.
|
| So users can choose: * plug-and-play (WebSocket) * ultra-
| low-latency (raw TCP)
|
| Thanks for the nudge this will go on the transport
| roadmap.
| tgma wrote:
| > I have personally found several fuckups in gRPC and
| protobuf code resulting in application crashes or risks of
| _remote code execution_.
|
| Would be great if you report such remote code executions to
| the authors/Google. I am sure they handle CVEs etc. There has
| been a security audit like https://github.com/grpc/grpc/tree/
| master/doc/grpc_security_a...
|
| > there aren't sanitizer builds nor fuzzing regime nor static
| analysis running against new commits last time I checked.
|
| Are you making shit up as you go? I randomly picked a
| recently merged commit and this is the list of test suites
| ran on the pull request. As far as I recall, this has been
| the practice for at least 8 years+ (note the MSAN, ASAN, TSAN
| etc.)
|
| I can see various fuzzers in the code base so that claim is
| also unsubstantiated https://github.com/grpc/grpc/tree/f5c26a
| ec2904fddffb70471cbc... Android (Internal CI)
| Kokoro build finished Basic Tests C Windows Kokoro
| build finished Basic Tests C# Linux Kokoro build
| finished Basic Tests C# MacOS Kokoro build finished
| Basic Tests C# Windows Kokoro build finished Basic
| Tests C++ iOS Kokoro build finished Basic Tests C/C++
| Linux [Build Only] Kokoro build finished Basic Tests
| ObjC Examples Kokoro build finished Basic Tests ObjC
| iOS Kokoro build finished Basic Tests PHP Linux Kokoro
| build finished Basic Tests PHP MacOS Kokoro build
| finished Basic Tests Python Linux Kokoro build finished
| Basic Tests Python MacOS Kokoro build finished Bazel
| Basic Tests for Python (Local) Kokoro build finished
| Bazel Basic build for C/C++ Kokoro build finished Bazel
| C/C++ Opt MacOS Kokoro build finished Bazel RBE ASAN
| C/C++ Kokoro build finished Bazel RBE Build Tests
| Kokoro build finished Bazel RBE Debug C/C++ Kokoro
| build finished Bazel RBE MSAN C/C++ Kokoro build
| finished Bazel RBE Opt C/C++ Kokoro build finished
| Bazel RBE TSAN C/C++ Kokoro build finished Bazel RBE
| Thready-TSAN C/C++ Kokoro build finished Bazel RBE
| UBSAN C/C++ Kokoro build finished Bazel RBE Windows Opt
| C/C++ Kokoro build finished Bloat Diff Kokoro build
| finished Bloat Difference Bloat Difference Clang
| Tidy (internal CI) Kokoro build finished Distribution
| Tests C# Linux Kokoro build finished Distribution Tests
| C# MacOS Kokoro build finished Distribution Tests C#
| Windows Kokoro build finished Distribution Tests Linux
| (standalone subset) Kokoro build finished Distribution
| Tests PHP Linux Kokoro build finished Distribution
| Tests PHP MacOS Kokoro build finished Distribution
| Tests Python Linux Arm64 Kokoro build finished
| Distribution Tests Ruby MacOS Kokoro build finished
| Distribution Tests Windows (standalone subset) Kokoro build
| finished EasyCLA EasyCLA check passed. You are
| authorized to contribute. Grpc Examples Tests CPP
| Kokoro build finished Memory Difference Memory
| Difference Memory Usage Diff Kokoro build finished
| Mergeable Mergeable Run has been Completed! Migration
| Test MacOS Sonoma Kokoro build finished ObjC Bazel Test
| Kokoro build finished Portability Tests Linux [Build
| Only] (internal CI) Kokoro build finished Portability
| Tests Windows [Build Only] (internal CI) Kokoro build
| finished Sanity Checks (internal CI) Kokoro build
| finished Tooling Tests Python Linux Kokoro build
| finished Windows clang-cl with strict warnings [Build
| Only] Kokoro build finished
| efecan0 wrote:
| Interesting discussion. My current goal isn't to replace
| gRPC but to offer a lighter option for simple real-time
| apps. I'll keep following the thread; the security links
| are useful, thanks.
| cherryteastain wrote:
| gRPC's C++ interfaces have horrible design if you want async
| behaviour. Tons of unsafe and bad practices like the need to
| call delete this [1]
|
| [1] https://grpc.io/docs/languages/cpp/callback/
| jeffbee wrote:
| Ironically this library is much closer to what Google uses
| internally than grpc is.
| efecan0 wrote:
| Interesting point, thanks!
| efecan0 wrote:
| Hi everyone, thanks for checking out BinaryRPC!
|
| I built this project because I needed a simple but fast
| WebSocket-based RPC layer for my own real-time side projects.
| Existing options felt heavy or JSON-only, so I wrote something
| binary-focused and plugin-friendly.
|
| I'd really appreciate any feedback on:
|
| * Overall architecture / design smells * Concurrency model
| (thread-pool vs async IO) * "Must-have" features before this is
| production-ready
|
| Design notes and a 5-minute chat-server demo are in this short
| post: https://medium.com/@efecanerdem0907/building-a-chat-
| server-i...
|
| Any comments, suggestions or PRs are welcome. Thanks again!
| jeffbee wrote:
| Breezy claims of "exactly once" are a red flag for me. Aside from
| that I think this framework looks fairly promising.
| efecan0 wrote:
| Good catch--let me clarify what QoS 2 in BinaryRPC really does.
|
| It follows the MQTT-style 2-step handshake:
|
| 1. Sender - `PUBLISH(id, data)` 2. Receiver - `PUBREC(id)` //
| stored as "seen but not completed" 3. Sender - `PUBREL(id)` 4.
| Receiver - `PUBCOMP(id)` // marks id as done, then passes data
| to the app layer
|
| While an id is in "seen" state the receiver drops duplicates,
| so the message is delivered to user code exactly once per
| session even if the socket retries.
|
| If the client reconnects with the same session-key, the server
| reloads the in-flight id table, so duplicates are still
| filtered. If the session is lost (no session-key) we fall back
| to at-least-once because there is no common store.
|
| So: "exactly once within a persisted session; effectively once"
| as long as the application is idempotent. I'll update the docs
| to state this more precisely. Thanks for pointing it out!
| denizdoktur wrote:
| Lightweight, well-designed, and solves a real need. Impressive.
| efecan0 wrote:
| Thanks!
| sahinemirhan wrote:
| Very good
| dailker wrote:
| nice I loved it dude. I hope you get succesful on this.
| MuffinFlavored wrote:
| > None, AtLeastOnce, ExactlyOnce with retries, ACKs & two-phase
| commit, plus pluggable back-off strategies & per-session TTL.
|
| Sounds like RabbitMQ/AMQP/similar over WebSocket?
| efecan0 wrote:
| It looks similar on the surface, but scope and goals are
| different:
|
| * BinaryRPC = direct request/response calls with optional QoS
| (per session). - No exchanges/queues, no routing keys. - One
| logical stream, messages mapped to handlers.
|
| * RabbitMQ / AMQP = full message-broker with persistent queues,
| fan-out, topic routing, etc.
|
| So you could say BinaryRPC covers the transport/QoS part of
| AMQP, but stays lightweight and broker-less. If an app later
| needs full queueing we can still bridge to AMQP, but the core
| idea here is "RPC first, minimal deps".
| sarpistan wrote:
| Good job
| sph87 wrote:
| Modules my guy. The words "modern" and "C++" don't go together
| while using headers. Also your most basic implementation requires
| me to write 200+ LOC and add a dozen headers. Then it's a ton of
| boiler plate code duplication for every function registered.
|
| Basically what I am saying is - you need to place more
| abstraction between your code and the end-user API.
|
| Take this line:
|
| std::string sayMessage = payload["message"].template
| get<std::string>();
|
| Why not make a templated getString<"message"> that pulls from
| payload? So that would instead just be:
|
| auto sayMessage = payload["message"].as_string() or
|
| auto sayMessage = payload.getString<"message">() or
|
| std::string sayMessage = payload["message"] //We infer type from
| the assignment!!
|
| It's way cleaner. Way more effective. Way more intuitive.
|
| When working on this kind of stuff end-developer experience
| should always drive the process. Look at your JSON library. Well
| known and loved. Imagine if instead of:
|
| message["code"] = "JOIN"; it was instead something like:
|
| message.template set<std::string, std::string>("CODE", "JOIN");
|
| Somehow I don't think the latter would have seen any level of
| meaningful adoption. It's weird, obtuse and overly complex. You
| need to hide all that.
| efecan0 wrote:
| Hi.
|
| Thank you for the detailed feedback--this is exactly the kind
| of input that helps the project grow.
|
| You're right: developer experience needs to be better. Right
| now there is too much boiler-plate and not enough abstraction.
| Your example std::string msg =
| payload["message"]; // type inferred
|
| is the direction I want to take. I'll add a thin wrapper so
| users can write `payload["key"].as_string()` or even rely on
| assignment type-inference. Refactoring the basic chat demo to
| be much shorter is now my next task.
|
| About C++20 modules: I agree they are the future. The single-
| header client was a quick MVP, but module support is on the
| roadmap as compiler tooling matures.
|
| If you have more DX ideas or want to discuss API design, please
| open an issue on GitHub I'd be happy to collaborate.
|
| Thanks again for the valuable feedback!
| const_cast wrote:
| On the topic of modules: a single-header template
| implementation is still the most practical and quick way to
| distribute a library. Module support is currently iffy - I
| wouldn't use them.
| sph87 wrote:
| I _love_ modules. Honestly. I advocate usage simply as a
| forcing function for upstream. Tooling support is iffy
| because usage is low. Usage is low because tooling is iffy.
| All of the major players in the build space have reasonably
| mature levels of support though. So it 's one of those
| things were compilers have outpaced IDE.
___________________________________________________________________
(page generated 2025-07-12 23:00 UTC)