English | 简体中文
Lynn_tcp is a lightweight, high-performance asynchronous TCP framework built on Tokio.
It adopts a DDD (Domain-Driven Design) + Onion Architecture, separating core domain logic from infrastructure concerns for better maintainability and extensibility.
Keywords
- Lightweight: Concise code that is easy to learn and use
- Concurrent & Performance: Built on Tokio's excellent async runtime for high-concurrency multi-user connections
- Low Latency: Read-write separation design for minimal latency
- Security: Strong typing and memory safety guarantees of Rust
- Production Ready: Prometheus metrics, connection limiting, rate limiting, and heartbeat management built-in
Tips: Lynn_tcp is designed for message forwarding and long-lived TCP game servers.
Quickly develop business scenarios with the framework — customize message parsing, encryption, routing, and more.
Architecture
Lynn_tcp v2.0 is organized into three clean layers following the Onion Architecture pattern:
┌─────────────────────────────────────┐
│ Interface Layer │ ← Public API (no breaking changes)
│ (lynn_server / lynn_client / ...) │
├─────────────────────────────────────┤
│ Application Layer │ ← Orchestration: server/client startup
│ (LynnServer / LynnClient) │
├─────────────────────────────────────┤
│ Domain Layer │ ← Pure business logic
│ (Router / Handler / Model) │
├─────────────────────────────────────┤
│ Infrastructure Layer │ ← Concrete implementations
│ (TCP / Metrics / Validation) │
└─────────────────────────────────────┘
Quick Start
Dependencies
Add to your Cargo.toml:
Full features (recommended):
[]
= "2"
= { = "1", = ["macros", "net", "rt-multi-thread"] }
= "0.3"
Server only:
[]
= { = "2", = ["server"] }
= { = "1", = ["macros", "net", "rt-multi-thread"] }
= "0.3"
Client only:
[]
= { = "2", = ["client"] }
= { = "1", = ["macros", "net", "rt-multi-thread"] }
= "0.3"
Minimal Server
use ;
async
pub async
pub async
pub async
Server with Custom Config
use ;
async
pub async
Minimal Client
use ;
async
Examples
All examples are located in the examples/ directory. Run them with:
| Example | Command | Description |
|---|---|---|
basic_server |
cargo run --example basic_server |
Default server with 3 handler signatures |
custom_config_server |
cargo run --example custom_config_server |
Server with custom Builder configuration |
custom_protocol |
cargo run --example custom_protocol |
Custom message header/tail marks |
echo_server_client |
cargo run --example echo_server_client |
Full request-response cycle (Client ↔ Server) |
multi_route_service |
cargo run --example multi_route_service |
Multi-route dispatch and verification |
custom_protocol_full |
cargo run --example custom_protocol_full |
Custom protocol with client support |
state_example |
cargo run --example state_example |
Global state injection (AppState<T>) |
tls_example |
cargo run --example tls_example --features tls |
TLS 1.3 encrypted server ↔ client |
metrics_example |
cargo run --example metrics_example --features metrics |
Prometheus metrics integration |
The echo_server_client and multi_route_service examples are the best starting points for understanding Client ↔ Server communication.
Features
| Feature | Description | Default |
|---|---|---|
server |
TCP server with multi-route async handlers, heartbeat, connection management | ✅ |
client |
TCP client for sending/receiving messages, automatic reconnection | ✅ |
metrics |
Prometheus integration (17 production metrics, HTTP /metrics endpoint) | ✅ (via server) |
tls |
TLS 1.3 transport encryption for server & client (rustls/ring) | ❌ opt-in |
seaorm |
Built-in SeaORM support: with_db(...) + DbConn state handle |
❌ opt-in |
Note:
metricsis automatically enabled whenserveris selected. To use it independently:features = ["metrics"].tlsandseaormare disabled by default and must be enabled explicitly.
Available Configuration (LynnServerConfigBuilder)
| Method | Description | Default |
|---|---|---|
with_addr() |
Server listen address | 0.0.0.0:9177 |
with_server_max_connections() |
Max concurrent connections | 100_000 |
with_server_max_taskpool_size() |
Async task pool size (throughput) | 512 |
with_server_single_processs_permit() |
Max concurrent processing tasks | 1024 |
with_server_check_heart_interval() |
Heartbeat check interval (s) | 60 |
with_server_check_heart_timeout_time() |
Heartbeat timeout (s) | 180 |
with_tcp_nodelay() |
TCP_NODELAY (disable Nagle) | true |
with_tcp_keepalive_enabled() |
TCP keep-alive | true |
with_tcp_keepalive_time_secs() |
Keep-alive interval (s) | 120 |
with_message_header_mark() |
Custom message header mark | 0x23D9 |
with_message_tail_mark() |
Custom message tail mark | 0x1E27 |
with_max_connections_per_ip() |
Max connections per IP | 100 |
with_connection_rate_limit() |
Connection rate (per second) | 0 (disabled) |
with_read_timeout_secs() |
Read timeout (s, 0 = disabled) | 0 |
with_write_timeout_secs() |
Write timeout (s, 0 = disabled) | 0 |
with_recv_buffer_size() |
Receive buffer (bytes) | 65535 |
with_send_buffer_size() |
Send buffer (bytes) | 65535 |
with_tls() (feature tls) |
Enable TLS 1.3 with a TlsServerConfig |
off |
with_tls_cert_paths() (feature tls) |
Enable TLS 1.3 from PEM cert/key paths | off |
Available Configuration (LynnClientConfigBuilder)
| Method | Description | Default |
|---|---|---|
with_server_addr() |
Server address to connect to | — |
with_server_single_channel_size() |
Per-connection channel capacity | 64 |
with_server_check_heart_interval() |
Heartbeat send interval (s) | 5 |
with_message_header_mark() / with_message_tail_mark() |
Custom message marks | 9177 / 7719 |
with_reconnect_max_attempts() |
Connect attempts per session (initial connect & each reconnect) | 3 |
with_reconnect_interval_secs() |
Delay between attempts (s) | 1 |
with_connect_timeout_secs() |
Per-attempt connect/TLS-handshake timeout (s) | 3 |
with_tls() (feature tls) |
Enable TLS 1.3 with a TlsClientConfig (CA, SNI, mTLS) |
off |
TLS 1.3 Encryption (feature tls)
TLS is disabled by default. Enable the tls feature and attach certificates — TLS 1.3 only (rustls + ring):
[]
= { = "2", = ["tls"] }
use ;
// Server: opt in with a certificate chain + private key (PEM files).
let config = new
.with_addr?
.with_tls_cert_paths // or: .with_tls(TlsServerConfig::new(...))
.build;
// Mutual TLS: TlsServerConfig::new("cert.pem", "key.pem").with_client_ca("client_ca.pem")
use ;
// Client: verify the server against a CA trust anchor.
let config = new
.with_server_addr?
.with_tls?
.build;
Miss-configured TLS fails fast (missing files, invalid pairs) and handshake failures simply drop the offending connection. Run the runnable demo: cargo run --example tls_example --features tls.
Global State (Dependency Injection)
Register any Send + Sync + 'static value once and extract it in handler parameters through AppState<T> (axum-style). One value per type; several types can coexist. SeaORM users can enable the seaorm feature and use with_db(...) + DbConn:
use ;
async
// `repo` derefs to &UserRepo — no global statics, no manual Arc plumbing.
async
State is resolved per request, so with_state may be called before or after add_router — just register before start(). Run the runnable demo: cargo run --example state_example.
Client Automatic Reconnection
The client supervises its own connection: when it drops, it reconnects automatically — 3 attempts by default, 1s apart (configurable). User-facing channels survive reconnections, stale queued frames are discarded, and the live state is one call away:
let mut client = new_with_addr.await.start.await;
if !client.is_connected
Road Map
✅ Core Features (v1.0.0+)
- ✅ TCP Server with multi-route async handler dispatch
- ✅ TCP Client with message send/receive
- ✅ Custom message header/tail marks
- ✅ Automatic client heartbeat & cleanup
- ✅ Asynchronous task routing service
- ✅ Prometheus + Grafana monitoring (v1.2.5)
- ✅ DDD + Onion Architecture refactoring (v2.0.0)
- ✅ 7 runnable examples covering all scenarios
- ✅ TLS 1.3 transport encryption for server & client (v2.0.0)
- ✅ Client automatic reconnection with configurable attempts (v2.0.0)
- ✅ Global state injection (
AppState<T>) + built-in SeaORM support (v2.0.0)
🔜 Planned
| Feature | Target Version | Status |
|---|---|---|
| Middleware support | v2.2.0 | 📝 Design |
| Scheduled tasks | TBD | 💡 Idea |
Flow Chart
v2.x — DDD + Onion Architecture
Release Notes
See docs/version.md for full version history, and per-version changelogs under docs/update_logs/.
Benchmarks
Starting with v2.0.0-rc.3, Lynn_tcp ships a standardized, reusable benchmark harness (benches/benchmark.rs + the standalone bench_echo_server binary). The server runs as an independent process (like a real deployment), every concurrency level starts from a fresh server process, and each cell measures a fixed window after a discarded warmup. The pre-2.0 tables were produced by an unrecoverable one-off script and have been retired.
Traffic models
- Model 1 — request/response (ping-pong): each client keeps exactly one request in flight and measures every round-trip; reports throughput and RTT percentiles.
- Model 2 — concurrent send/receive (pipelined): each client runs one sending and one receiving task with a bounded in-flight window (8); reports sustained throughput.
Standard matrix: models 1+2 × client concurrency 64 / 256 / 1024 / 4096 (logarithmic 4× steps, covering the latency-bound and saturation regimes), echo payload 128 B, 3 s warmup + 10 s measured per cell.
Reproduce
# customize: --model all|1|2 --clients 64,256 --duration 15 --warmup 3 --payload 128
Standard run for a new version / machine (maintainers)
&&
Before running, make sure the file-descriptor limit allows the highest concurrency level (ulimit -n 65535; the harness also auto-raises it up to the hard limit). Archive the JSON under docs/benchmark/ named v<version>-<machine>.json, then update the results table in both READMEs in the same commit (see AGENTS.md §10.3).
| Model | Clients | Payload (B) | Throughput (resp/s) | Avg RTT (ms) | p50 (ms) | p95 (ms) | p99 (ms) |
|---|---|---|---|---|---|---|---|
| 1 (ping-pong) | 64 | 128 | 135,473 | 0.472 | 0.439 | 0.791 | 1.046 |
| 1 (ping-pong) | 256 | 128 | 142,735 | 1.793 | 1.634 | 3.295 | 4.435 |
| 1 (ping-pong) | 1024 | 128 | 147,568 | 6.938 | 5.827 | 15.200 | 22.410 |
| 1 (ping-pong) | 4096 | 128 | 138,073 | 29.640 | 28.416 | 35.220 | 64.218 |
| 2 (pipelined) | 64 | 128 | 151,644 | — | — | — | — |
| 2 (pipelined) | 256 | 128 | 191,607 | — | — | — | — |
| 2 (pipelined) | 1024 | 128 | 143,569 | — | — | — | — |
| 2 (pipelined) | 4096 | 128 | 31,956 | — | — | — | — |
📊 Numbers are loopback, single-machine measurements where client load generation shares the CPU with the server — treat them as relative comparisons between versions, not absolute capacity. Model-1 RTT grows with concurrency as queues form (classic queueing behavior). Model-2 peaks near 256 clients on this machine; its 4096-client cell was degraded by connection-phase retries (see the requests ≫ responses gap in the JSON), so read that point as a lower bound rather than steady state.
| Model | Clients | Payload (B) | Throughput (resp/s) | Avg RTT (ms) | p50 (ms) | p95 (ms) | p99 (ms) |
|---|---|---|---|---|---|---|---|
| 1 (ping-pong) | 64 | 128 | 128,206 | 0.499 | 0.232 | 0.988 | 5.099 |
| 1 (ping-pong) | 256 | 128 | 140,197 | 1.826 | 0.835 | 3.504 | 35.898 |
| 1 (ping-pong) | 1024 | 128 | 140,187 | 7.303 | 2.602 | 45.081 | 55.153 |
| 1 (ping-pong) | 4096 | 128 | 130,003 | 31.445 | 6.305 | 111.846 | 167.948 |
| 2 (pipelined) | 64 | 128 | 35,277 | — | — | — | — |
| 2 (pipelined) | 256 | 128 | 142,922 | — | — | — | — |
| 2 (pipelined) | 1024 | 128 | 360,433 | — | — | — | — |
| 2 (pipelined) | 4096 | 128 | 7,125 | — | — | — | — |
📊 Notable: Model-1 throughput is flat from 64 up to 4096 clients on just two cores (128k–140k), though the RTT tail grows sharply once queues form. Model-2 bursts to 360k @1024, but its 4096-client cell collapsed (7,125 resp/s, connection-phase retries) — on a 2 GB host that level is bounded by memory (kernel socket buffers + connection state), so read it as a host resource limit rather than a framework regression.
The v1.x numbers were produced by an unrecoverable one-off script on Debian 12 (4-core / 4 GB server); the v2.0 numbers come from the standardized harness above on Apple M1 Pro (8 logical cores / 16 GB). Hardware, OS and methodology all differ — read the tables as trends, not absolutes.
Model 1 — request/response (resp/s)
| Clients | v1.1.x | v1.2.x | v2.0.0-rc.3 |
|---|---|---|---|
| 256 | 182,879 | 64,630 | 142,735 |
| 1024 | 232,861 | 163,307 | 147,568 |
| 4096 | 160,318 | 124,645 | 138,073 |
Model 2 — concurrent send/receive (resp/s)
| Clients | v1.1.x | v1.2.x | v2.0.0-rc.3 |
|---|---|---|---|
| 256 | 80,499 | 492,889 | 191,607 |
| 1024 | 23,143 | 158,056 | 143,569 |
| 4096 | 13,557 | 52,163 | 31,956 |
Takeaways
- Model-1 stays in the same order of magnitude as v1.x — no recognizable regression — and again degrades least at 4096 clients (peak→4096: −6% versus −36% / −32% for v1.x).
- Model-2 holds 143k–192k through 1024 clients, where v1.1.x had already collapsed to 23k (6.2×). Its 4096-client point in the archived run was degraded by connection-phase retries (requests ≫ responses in the JSON), so read it as a lower bound; the v1.x numbers at that level came from unbounded bursts and are not directly comparable.
- Model-2 semantics differ (v1.x appears to burst without in-flight limits; v2.0 uses a fixed 8-deep pipeline), so treat that table as directional.
- v2.0 additionally reports RTT percentiles, which v1.x never measured.
FAQ
Q: Is v2.0.0 backward compatible with v1.x?
A: Yes. All public API signatures remain unchanged. You only need to update the version in Cargo.toml: lynn_tcp = "2".
Q: Do I need to change my code after upgrading?
A: No. The refactoring was purely structural — no behavior was modified. Your existing code will compile and run as before.
Q: Why jump from v1.2.x to v2.0.0?
A: The DDD + Onion Architecture restructuring represents a significant architectural improvement. The major version bump reflects this depth of change, even though the public API is fully compatible.
Q: How do I run the examples?
A: See the Examples section above. Each example can be run with cargo run --example <name>.
Q: How do I enable metrics?
A: Metrics are automatically enabled with the server feature. To run the metrics example: cargo run --example metrics_example --features metrics. See METRICS.md for detailed documentation.
Q: How do I enable TLS?
A: Enable the tls feature and call .with_tls_cert_paths("cert.pem", "key.pem") on the server builder and .with_tls(...) on the client builder — TLS 1.3 only, off by default. See the TLS 1.3 Encryption section.
Q: How do I share a database handle with handlers?
A: Call .with_state(db) (or .with_db(db) with the seaorm feature) on LynnServer and take an AppState<T> parameter in the handler. See Global State.
Q: Does the client reconnect automatically?
A: Yes. After a disconnect it retries 3 times (1s apart) by default, configurable via with_reconnect_max_attempts / with_reconnect_interval_secs. Check the live state with client.is_connected().
License
This project is dual-licensed under either of:
- MIT license (LICENSE)
- Apache License, Version 2.0 (LICENSE-APACHE)
at your option.
Contribution
- Please read AGENTS.md first — it is the project's development constitution, defining commit conventions, CI standards, formatting/lint rules, testing requirements (coverage ≥ 80% via
cargo-llvm-cov), and the release workflow. All pull requests and code reviews are checked against it. - Before submitting, run locally:
cargo fmt --all,cargo clippy --all-targets -- -D warnings,cargo test. - Keep
README.mdandREADME_ZH.mdin sync when changing documentation.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Lynn_tcp by you shall be licensed as MIT OR Apache-2.0, without any additional terms or conditions.

