HttpClient.IoUring
1.0.0
dotnet add package HttpClient.IoUring --version 1.0.0
NuGet\Install-Package HttpClient.IoUring -Version 1.0.0
<PackageReference Include="HttpClient.IoUring" Version="1.0.0" />
<PackageVersion Include="HttpClient.IoUring" Version="1.0.0" />
<PackageReference Include="HttpClient.IoUring" />
paket add HttpClient.IoUring --version 1.0.0
#r "nuget: HttpClient.IoUring, 1.0.0"
#:package HttpClient.IoUring@1.0.0
#addin nuget:?package=HttpClient.IoUring&version=1.0.0
#tool nuget:?package=HttpClient.IoUring&version=1.0.0
HttpClient.IoUring
A high-performance io_uring transport for SocketsHttpHandler that replaces the default socket I/O with Linux's io_uring interface. Up to 69% faster throughput on Linux.
Performance
On a 2-core VM (AMD EPYC 9V74, Linux 6.17, .NET 10):
| Concurrency | Socket | io_uring | Improvement |
|---|---|---|---|
| 1 | 9,339 req/s | 15,738 req/s | +69% |
| 16 | 17,412 req/s | 22,546 req/s | +29% |
| 64 | 19,553 req/s | 24,876 req/s | +27% |
| 128 | 18,649 req/s | 27,006 req/s | +45% |
| 256 | 19,435 req/s | 26,956 req/s | +39% |
See BENCHMARKS.md for detailed methodology.
Features
- Drop-in replacement — one
UseIoUring()call replaces the socket transport - Batched syscalls — a single
io_uring_entersubmits CONNECT + SEND + RECV across all connections - Zero-copy send (
SEND_ZC) for payloads >4KB — avoids kernel buffer copy - Registered file descriptors (
IOSQE_FIXED_FILE) — kernel skips fd lookup per SQE - Async connect via
IORING_OP_CONNECT— fully non-blocking connection establishment - Full SocketsHttpHandler features — HTTP/1.1, HTTP/2, HTTP/3, TLS, connection pooling, redirects, decompression — all work automatically
- Targets net8.0, net9.0, net10.0
Requirements
- Linux with kernel 5.1+ (for io_uring support)
- Kernel 6.0+ recommended (for zero-copy send, buffer rings)
- .NET 8 or later
Installation
dotnet add package HttpClient.IoUring
Quick Start
using HttpClient.IoUring.Extensions;
var handler = new SocketsHttpHandler();
using var transport = handler.UseIoUring(options =>
{
options.RingSize = 256;
});
using var client = new HttpClient(handler);
var response = await client.GetAsync("https://api.example.com/data");
Factory Method
using var handler = IoUringTransport.CreateHandler(options =>
{
options.RingSize = 256;
});
using var client = new HttpClient(handler);
Architecture
┌─────────────────────────────────────────────────────────────┐
│ HttpClient │
│ new HttpClient(handler) │
│ │ │
│ SocketsHttpHandler (HTTP/1.1+2+3, pooling, TLS, etc.) │
│ └── ConnectCallback = IoUringTransport.ConnectAsync │
│ │ │
│ IoUringTransport │
│ ├── IoUringConnector (IORING_OP_CONNECT + DNS) │
│ ├── IoUringStream (RECV/SEND via io_uring) │
│ └── IoUringClientLoop (shared Ring, IO thread) │
│ └── Ring → SQ/CQ → io_uring_enter │
└─────────────────────────────────────────────────────────────┘
How it works
All IoUringStream instances share a single io_uring Ring. When multiple connections read/write concurrently, their SQEs are batched into a single io_uring_enter syscall:
Thread A: conn1.ReadAsync() → enqueue RECV SQE
Thread B: conn2.WriteAsync() → enqueue SEND SQE
Thread C: conn3.Connect() → enqueue CONNECT SQE
│
IO Thread: io_uring_enter(3 SQEs) ←─┘ ← one syscall for 3 operations
Configuration
| Option | Default | Description |
|---|---|---|
RingSize |
256 | SQ/CQ ring depth (power of two) |
ConnectTimeout |
30s | TCP connect timeout |
EnableZeroCopySend |
false | Use SEND_ZC for large payloads |
ZeroCopySendThreshold |
4096 | Minimum payload size for zero-copy |
MaxRegisteredFiles |
256 | Registered fd table size (0 to disable) |
io_uring Features Used
| Feature | Kernel | Purpose |
|---|---|---|
IORING_OP_CONNECT |
5.1+ | Async TCP connect |
IORING_OP_RECV |
5.1+ | Receive response data |
IORING_OP_SEND |
5.1+ | Send request data |
IORING_OP_SEND_ZC |
6.0+ | Zero-copy send (opt-in) |
IORING_OP_CLOSE |
5.6+ | Async close |
IORING_REGISTER_FILES |
5.1+ | Registered file descriptors |
Project Structure
├── src/HttpClient.IoUring/ ← NuGet library (net8.0/net9.0/net10.0)
├── tests/HttpClient.IoUring.Tests/ ← xunit tests
├── benchmarks/ ← BenchmarkDotNet comparisons
└── samples/SampleApp/ ← Minimal usage example
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 103 | 3/20/2026 |
v1.0.0 — Initial release
• Drop-in io_uring transport via handler.UseIoUring()
• Batched syscalls across all connections (single io_uring_enter)
• Buffer rings for recv (eliminates per-recv memory pinning)
• SEND_ZC zero-copy send for large payloads (opt-in)
• Registered file descriptors (IOSQE_FIXED_FILE)
• Lock-free CompletionSlots for O(1) CQE dispatch
• 27–69% faster than default SocketsHttpHandler
• Targets net8.0, net9.0, net10.0