
A high-performance gRPC server library in C
gRPC is a lightweight, high-performance gRPC server library in C23 for Linux. It is built on libuv and nghttp2, and exposes a simple poll-and-handle API: no hidden threads, no code generator, no callback soup. You decid…
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
gRPC is a lightweight, high-performance gRPC server library in C23 for Linux. It is built on libuv and nghttp2, and exposes a simple poll-and-handle API: no hidden threads, no code generator, no callback soup. You decide how requests are scheduled, how memory is allocated, and how many cores to use.
Poll-based API: one call (grpc_server_poll()) runs the event loop and returns a batch of events (connections, messages, cancellations, deadlines) for you to handle.
All 4 RPC modes: unary, server streaming, client streaming and bidirectional streaming.
TLS with ALPN: HTTP/2 over TLS 1.2 and 1.3, negotiating h2. Seven TLS libraries are supported: OpenSSL, LibreSSL, BoringSSL, AWS-LC, wolfSSL, GnuTLS and rustls (see TLS backends).
Protobuf-C helpers: optional protobuf-c integration (GRPC_PB_UNPACK, grpc_pb_create_response).
FlatBuffers helpers: optional flatcc integration with verified, zero-copy reads (GRPC_FB_UNPACK, grpc_fb_create_response).
Interceptors: pre-call hooks run with grpc_server_intercept_pre(), post-call hooks with grpc_server_intercept_post(). A built-in Bearer token interceptor is available (grpc_interceptor_create_bearer_auth).
Health checking: the standard /grpc.health.v1.Health/Check service with per-service status.
Server reflection: grpc.reflection.v1 and v1alpha ServerReflectionInfo.
Metadata: read request headers, send initial metadata and trailers, and Base64 *-bin values.
Deadlines: grpc-timeout is parsed. Expired streams are ended with DEADLINE_EXCEEDED and reported as GRPC_EVENT_TIMEOUT.
Message compression: gzip and deflate through zlib, plus optional Zstandard and Snappy codecs. Select the codec and compression level per message.
Rich error model: google.rpc.Status with Any details (e.g. ErrorInfo) sent in grpc-status-details-bin.
Pluggable allocators: libc malloc, mimalloc, jemalloc, or your own allocator set at runtime.
Architecture
You register service paths, then poll for events and answer them:
struct grpc_event { union { grpc_event_message message; // Received gRPC messages on a stream grpc_event_dataloss dataloss; // Truncated message at end of stream grpc_event_cancel cancel; // Stream cancelled by the client (RST_STREAM) grpc_event_timeout timeout; // Stream deadline exceeded }; grpc_event_type type; // GRPC_EVENT_CONNECT, _DISCONNECT, _MESSAGE, ... grpc_session_uuid session_uuid; // Identifies the client connection void *userdata; // The pointer you registered with the path };
I/O model
Each grpc_server owns one libuv loop, driven by grpc_server_poll():
A libuv loop runs on one thread. To use more cores, create one server per thread on the same port. The listening sockets use SO_REUSEPORT, so the kernel spreads incoming connections across the loops. Health and reflection state is shared and thread-safe. The example server does this when started with THREADS=N.
A single HTTP/2 connection always stays on one loop, so multiple loops only help when clients open multiple connections.
Set the initial threshold and optional output stream in grpc_config, then change the threshold at runtime with grpc_server_set_log_level():
grpc_config cfg = { .port = 50051, .log_level = GRPC_LOG_WARNING, .log_file = stderr, }; grpc_server server = grpc_server_new(&cfg); / Later, from any thread while the server is alive: */ grpc_server_set_log_level(server, GRPC_LOG_DEBUG);
Each server has its own threshold and output stream. GRPC_LOG_DEFAULT selects info; GRPC_LOG_OFF disables server logs. Logging uses the ulog backend.
Measured on localhost with ghz sending unary SayHello calls to the Release example server, built with the default libc allocator. The server was pinned to CPUs 0–7 and the client to CPUs 8–15 on an Intel Core i9-9900K (8 cores / 16 threads). Each result is the median of three runs; each run warmed up with 5,000 calls, then sent 300,000 calls and excluded the first 5,000 from the reported rate.
“Before” is the revision immediately before the receive-buffer, request-header, and message-compression optimizations; “After” includes those changes. Both used the same compiler, build options, server/client CPU affinity, payload ({"name":"benchmark_user"}), and ghz settings. The results are close to run-to-run variation, so treat them as updated reference measurements rather than a clear throughput gain. Results depend on hardware and client capacity; set --connections high enough to keep the server busy.
Platform: Linux.
Compiler: GCC 11+ or Clang 14+ with C23 support (-std=c23, or -std=c2x on older compilers).
Build system: CMake 3.20 or newer.
Logger dependency: CMake fetches the pinned ulog source at configure time. For offline or local development, set FETCHCONTENT_SOURCE_DIR_ULOG to an existing checkout.
Required libraries:
libuv (event loop)
libnghttp2 (HTTP/2)
libuuid (session identifiers)
pthread
Optional libraries (detected automatically when installed):
A TLS library for TLS: OpenSSL by default, or one of the other backends
zlib for gzip and deflate compression
libzstd for Zstandard compression
libsnappy for Snappy compression
protobuf-c for the Protobuf-C helpers
flatcc for the FlatBuffers helpers (no distribution package: build it with scripts/build-flatcc.sh)
mimalloc or jemalloc as the allocator (selected at build time)
On Debian / Ubuntu:
sudo apt-get update
sudo apt-get install -y
build-essential pkg-config libuv1-dev libnghttp2-dev uuid-dev
zlib1g-dev libzstd-dev libsnappy-dev libssl-dev libprotobuf-c-dev protobuf-c-compiler
libmimalloc-dev libjemalloc-dev # optional allocators
The Python integration tests also need grpcio, grpcio-tools and protobuf (pip install grpcio grpcio-tools protobuf).
Configure and build the static library, shared library and available examples:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build --parallel
Run the registered unit and integration tests with:
ctest --test-dir build --output-on-failure
The CMake targets are grpc_static and grpc_shared. The grpc::grpc alias selects the static library and carries its required link dependencies.
The previous implementation provided gzip helpers only. Gzip and deflate both use zlib; deflate uses the zlib-wrapped DEFLATE stream used by gRPC. Zstandard (zstd) and Snappy (snappy) are detected at build time when their development libraries are installed. Set -DGRPC_ZSTD=OFF or -DGRPC_SNAPPY=OFF to disable either optional codec.
Compression remains explicit and per message. The server sends the corresponding grpc-encoding header when the first response message is compressed, and advertises the codecs enabled in that build. For example:
grpc_message *response = grpc_message_new(payload, payload_len); grpc_message *compressed = grpc_message_compress( response, GRPC_COMPRESS_ZSTD, 5); // Zstandard level 5 grpc_message_free(response); if (compressed != nullptr) grpc_call_send(call, compressed); // call_send takes ownership
Use GRPC_COMPRESS_GZIP or GRPC_COMPRESS_DEFLATE with zlib levels 0 through 9; use GRPC_COMPRESSION_LEVEL_DEFAULT for the library default. Zstandard accepts levels supported by the installed Zstandard library. Snappy has no level control and requires GRPC_COMPRESSION_LEVEL_DEFAULT. The legacy grpc_message_compress_gzip() and raw gzip functions remain available.
Zstandard and Snappy are library-specific gRPC encodings. A peer must support the same encoding token to interoperate; standard gRPC implementations commonly support gzip and identity, while support for deflate, Zstandard and Snappy varies.
Add -lprotobuf-c when the Protobuf-C helpers are built, -lflatccrt when the FlatBuffers helpers are, and the allocator library (-lmimalloc or -ljemalloc) when one is selected.
CMake setting or command Description
cmake --build build --target check Builds and runs registered unit and integration tests
-DGRPC_ALLOCATOR=mimalloc Uses mimalloc; also accepts jemalloc or default
-DGRPC_TLS_BACKEND=<backend> Selects AUTO, openssl, libressl, boringssl, awslc, wolfssl, gnutls, rustls or none
-DGRPC_TLS_PREFIX=<dir> Searches <dir> for the selected TLS library
-DGRPC_PROTOBUF_C=OFF Disables Protobuf-C helpers; AUTO detects them, ON requires them
-DGRPC_FLATCC=OFF Disables FlatBuffers helpers; AUTO detects them, ON requires them
-DGRPC_FLATCC_PREFIX=<dir> Searches <dir>/bin, include and lib for flatcc
-DGRPC_ZLIB=OFF, -DGRPC_ZSTD=OFF, -DGRPC_SNAPPY=OFF Disable optional codecs; each accepts AUTO, ON or OFF
-DGRPC_DEBUG_LOGS=ON Enables debug logging and debug symbols
-DGRPC_TRACE=ON Enables trace logging of every HTTP/2 frame
-DGRPC_ASAN=ON Builds with AddressSanitizer
-DGRPC_BUILD_INTEGRATION_TESTS=OFF Skips Python client integration tests
-DGRPC_BUILD_EXAMPLES=OFF Skips example server targets
cmake --build build --target format Formats C sources with clang-format
cmake --build build --target format-check Checks C formatting with clang-format
cmake --build build --target clean Removes build output
TLS backends
Exactly one TLS library is compiled in, selected with GRPC_TLS_BACKEND. Use a separate build directory when switching backends. The versions listed are the ones the test suite has been run against.
LibreSSL, BoringSSL and AWS-LC install the same openssl/*.h headers as OpenSSL, so they are used from their own prefix. scripts/build-tls-backend.sh downloads a pinned, checksum-verified release, builds it, and installs it into a directory of your choice:
scripts/build-tls-backend.sh awslc "$HOME/tls/awslc" cmake -S . -B build-awslc -DGRPC_TLS_BACKEND=awslc -DGRPC_TLS_PREFIX="$HOME/tls/awslc" cmake --build build-awslc --parallel
The build fails if the headers it finds don't belong to the selected library, and the TLS unit test checks that the library linked in is the one requested. At runtime, grpc_tls_backend() and grpc_tls_backend_version() report the library in use.
All backends behave the same way: they load a PEM certificate chain and private key, check that the key matches the certificate, allow TLS 1.2 and 1.3 only, and select h2 with ALPN. A client that offers no ALPN still connects. For settings grpc doesn't expose, grpc_tls_ctx_native() returns the library's own context (SSL_CTX *, WOLFSSL_CTX *, gnutls_certificate_credentials_t or const struct rustls_server_config *).
- A complete server
#include <stdio.h> #include <string.h> #include "grpc.h"
static void handle_message(grpc_server *server, grpc_event event) { / the request messages; this server ignores their content */ grpc_message *request; while ((request = grpc_messages_pop(event->message.messages)) != nullptr) grpc_message_free(request);
/* a unary call answers once the client has finished sending */ if (!event->message.end_stream) return;
const char reply[] = "Hello from grpc!"; grpc_messages *response = grpc_messages_new(nullptr); grpc_messages_push(response, grpc_message_new(0, (void *)reply, strlen(reply)));
/* both calls only queue frames; the next poll writes them */ grpc_server_session_stream_send(server, event->session_uuid, event->message.stream_id, response); grpc_server_session_stream_end(server, event->session_uuid, event->message.stream_id, GRPC_GRPC_STATUS_OK); grpc_messages_free(response); }
int main(void) { grpc_global_init(0);
grpc_server server = grpc_server_new("0.0.0.0", 19009, / IPv4 / "::", 19009, / IPv6 / 1024, / listen backlog / nullptr, / server settings / nullptr); / HTTP/2 settings */ if (server == nullptr) return 1;
/* optional / / grpc_server_enable_tls(server, "server.crt", "server.key"); */ grpc_health_register(server); grpc_reflection_register(server);
/* the userdata pointer comes back in every event for this path */ static int say_hello; grpc_server_register_path(server, "/helloworld.Greeter/SayHello", &say_hello, 0, 0);
grpc_events events = grpc_events_new(nullptr); for (;;) { / writes queued responses, then waits up to 500 ms for new events */ if (grpc_server_poll(server, events, 500) < 0) break;
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。