https://github.com/ulala-x/net-zmq
High-performance ZeroMQ bindings for .NET with cppzmq-style API
https://github.com/ulala-x/net-zmq
csharp dotnet libzmq messaging networking nuget zeromq zmq
Last synced: 6 months ago
JSON representation
High-performance ZeroMQ bindings for .NET with cppzmq-style API
- Host: GitHub
- URL: https://github.com/ulala-x/net-zmq
- Owner: ulala-x
- License: mit
- Created: 2025-12-15T06:52:30.000Z (7 months ago)
- Default Branch: main
- Last Pushed: 2025-12-30T10:29:52.000Z (6 months ago)
- Last Synced: 2026-01-13T19:51:03.242Z (6 months ago)
- Topics: csharp, dotnet, libzmq, messaging, networking, nuget, zeromq, zmq
- Language: C#
- Homepage: https://www.nuget.org/packages/Net.Zmq
- Size: 3.26 MB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# Net.Zmq
[](README.md) [](README.ko.md)
[](https://github.com/ulala-x/net-zmq/actions/workflows/build.yml)
[](https://www.nuget.org/packages/Net.Zmq)
[](https://opensource.org/licenses/MIT)
[](https://ulala-x.github.io/net-zmq/)
[](CHANGELOG.md)
A modern .NET 8+ binding for ZeroMQ (libzmq) with cppzmq-style API.
## Features
- **Modern .NET**: Built for .NET 8.0+ with `[LibraryImport]` source generators (no runtime marshalling overhead)
- **cppzmq Style**: Familiar API for developers coming from C++
- **Type Safe**: Strongly-typed socket options, message properties, and enums
- **Cross-Platform**: Supports Windows, Linux, and macOS (x64, ARM64)
- **Safe by Default**: SafeHandle-based resource management
## Installation
```bash
dotnet add package Net.Zmq
```
## Quick Start
### REQ-REP Pattern
```csharp
using Net.Zmq;
// Server
using var ctx = new Context();
using var server = new Socket(ctx, SocketType.Rep);
server.Bind("tcp://*:5555");
var request = server.RecvString();
server.Send("World");
// Client
using var client = new Socket(ctx, SocketType.Req);
client.Connect("tcp://localhost:5555");
client.Send("Hello");
var reply = client.RecvString();
```
### PUB-SUB Pattern
```csharp
using Net.Zmq;
// Publisher
using var ctx = new Context();
using var pub = new Socket(ctx, SocketType.Pub);
pub.Bind("tcp://*:5556");
pub.Send("topic1 Hello subscribers!");
// Subscriber
using var sub = new Socket(ctx, SocketType.Sub);
sub.Connect("tcp://localhost:5556");
sub.Subscribe("topic1");
var message = sub.RecvString();
```
### Router-to-Router Pattern
```csharp
using System.Text;
using Net.Zmq;
using var ctx = new Context();
using var peerA = new Socket(ctx, SocketType.Router);
using var peerB = new Socket(ctx, SocketType.Router);
// Set explicit identities for Router-to-Router
peerA.SetOption(SocketOption.Routing_Id, "PEER_A"u8.ToArray());
peerB.SetOption(SocketOption.Routing_Id, "PEER_B"u8.ToArray());
peerA.Bind("tcp://127.0.0.1:5555");
peerB.Connect("tcp://127.0.0.1:5555");
// Peer B sends to Peer A (first frame = target identity)
peerB.Send("PEER_A"u8, SendFlags.SendMore);
peerB.Send("Hello from Peer B!");
// Peer A receives (first frame = sender identity)
Span idBuffer = stackalloc byte[64];
int idLen = peerA.Recv(idBuffer);
var senderId = idBuffer[..idLen];
var message = peerA.RecvString();
// Peer A replies using sender's identity
peerA.Send(senderId, SendFlags.SendMore);
peerA.Send("Hello back from Peer A!");
```
### Polling
```csharp
using Net.Zmq;
// Create Poller instance
using var poller = new Poller(capacity: 2);
// Add sockets and store their indices
int idx1 = poller.Add(socket1, PollEvents.In);
int idx2 = poller.Add(socket2, PollEvents.In);
// Poll for events
if (poller.Poll(timeout: 1000) > 0)
{
if (poller.IsReadable(idx1)) { /* handle socket1 */ }
if (poller.IsReadable(idx2)) { /* handle socket2 */ }
}
```
### Message API
```csharp
using Net.Zmq;
// Create and send message
using var msg = new Message("Hello World");
socket.Send(msg);
// Receive message
using var reply = new Message();
socket.Recv(reply);
Console.WriteLine(reply.ToString());
```
## Socket Types
| Type | Description |
|------|-------------|
| `SocketType.Req` | Request socket (client) |
| `SocketType.Rep` | Reply socket (server) |
| `SocketType.Pub` | Publish socket |
| `SocketType.Sub` | Subscribe socket |
| `SocketType.Push` | Push socket (pipeline) |
| `SocketType.Pull` | Pull socket (pipeline) |
| `SocketType.Dealer` | Async request |
| `SocketType.Router` | Async reply |
| `SocketType.Pair` | Exclusive pair |
## API Reference
### Context
```csharp
var ctx = new Context(); // Default
var ctx = new Context(ioThreads: 2, maxSockets: 1024); // Custom
ctx.SetOption(ContextOption.IoThreads, 4);
var threads = ctx.GetOption(ContextOption.IoThreads);
var (major, minor, patch) = Context.Version; // Get ZMQ version
bool hasCurve = Context.Has("curve"); // Check capability
```
### Socket
```csharp
var socket = new Socket(ctx, SocketType.Req);
// Connection
socket.Bind("tcp://*:5555");
socket.Connect("tcp://localhost:5555");
socket.Unbind("tcp://*:5555");
socket.Disconnect("tcp://localhost:5555");
// Send
socket.Send("Hello");
socket.Send(byteArray);
socket.Send(message, SendFlags.SendMore);
bool sent = socket.Send(data, SendFlags.DontWait); // false if would block
// Receive
string str = socket.RecvString();
int bytesRead = socket.Recv(buffer);
socket.Recv(message);
bool received = socket.TryRecvString(out string result);
bool gotData = socket.TryRecv(buffer, out int size);
// Options
socket.SetOption(SocketOption.Linger, 0);
int linger = socket.GetOption(SocketOption.Linger);
```
## Performance
### Recommended Message Strategies
Net.Zmq provides multiple message buffer strategies to accommodate different performance requirements:
**Available Strategies:**
- **Basic Message**: Simple `Message` object for general use
- **ArrayPool**: Uses `ArrayPool.Shared` for buffer reuse (manual return required)
- **MessageZeroCopy**: Zero-copy messages using `Marshal.AllocHGlobal` for large data
**Recommendations by Message Size:**
- **Small messages (≤1KB)**: **`ArrayPool.Shared`** - Best performance with minimal GC pressure
- **Large messages (≥64KB)**: **`MessageZeroCopy`** - Zero-copy semantics with minimal GC overhead
**Receive Mode:**
- Single socket → Blocking
- Multiple sockets → `Poller`
```csharp
// Small messages: ArrayPool (recommended)
var buffer = ArrayPool.Shared.Rent(size);
try
{
// Fill buffer with data
socket.Send(buffer.AsSpan(0, size));
}
finally
{
ArrayPool.Shared.Return(buffer);
}
// Large messages: MessageZeroCopy
nint nativePtr = Marshal.AllocHGlobal(dataSize);
unsafe
{
var nativeSpan = new Span((void*)nativePtr, dataSize);
sourceData.CopyTo(nativeSpan);
}
using var message = new Message(nativePtr, dataSize, ptr =>
{
Marshal.FreeHGlobal(ptr); // Called when libzmq is done
});
socket.Send(message);
// Receive: Blocking with Message
using var msg = new Message();
socket.Recv(msg);
// Process msg.Data
```
For fine-tuning options and detailed benchmarks, see [docs/benchmarks.md](docs/benchmarks.md).
## Supported Platforms
| OS | Architecture |
|----|--------------|
| Windows | x64, ARM64 |
| Linux | x64, ARM64 |
| macOS | x64, ARM64 |
## Documentation
Complete API documentation is available at: [https://ulala-x.github.io/net-zmq/](https://ulala-x.github.io/net-zmq/)
The documentation includes:
- API Reference for all classes and methods
- Usage examples and patterns
- Performance benchmarks
- Platform-specific guides
## Requirements
- .NET 8.0 or later
- Native libzmq library (automatically provided via Net.Zmq.Native package)
## License
MIT License - see [LICENSE](LICENSE) for details.
## Related Projects
- [libzmq](https://github.com/zeromq/libzmq) - ZeroMQ core library
- [cppzmq](https://github.com/zeromq/cppzmq) - C++ binding (API inspiration)
- [libzmq-native](https://github.com/ulala-x/libzmq-native) - Native binaries