Communication.Shared
2.7.0
dotnet add package Communication.Shared --version 2.7.0
NuGet\Install-Package Communication.Shared -Version 2.7.0
<PackageReference Include="Communication.Shared" Version="2.7.0" />
<PackageVersion Include="Communication.Shared" Version="2.7.0" />
<PackageReference Include="Communication.Shared" />
paket add Communication.Shared --version 2.7.0
#r "nuget: Communication.Shared, 2.7.0"
#:package Communication.Shared@2.7.0
#addin nuget:?package=Communication.Shared&version=2.7.0
#tool nuget:?package=Communication.Shared&version=2.7.0
DS_Communication
Connection-oriented transport layer library for .NET. It provides Session, Channel, Framing, and Pipeline, and leaves serialization entirely to the application via the IMessageConverter interface — the library ships no built-in serializer (pair it with DS_MessageProtocol or any IBufferWriter<byte>-based formatter). Targets .NET Standard 2.1 and is Unity compatible.
Two transports are implemented today:
- TCP —
NetworkStream/SslStreambyte streams with 4-byte little-endian length-prefix framing. Optional TLS viaSslStream. - RUDP — reliable/unordered/sequenced delivery over UDP (LiteNetLib 2.1.4, hidden behind library-owned types). Optional DTLS 1.2 (BouncyCastle) and optional CRC32c packet integrity.
Sister projects: DS_MessageProtocol (serialization) and DS_RPC (distributed RPC, built on both).
Packages
| Package | Contents | Dependencies |
|---|---|---|
Communication.Shared |
Session/ISession, MessagePipeline, IByteChannel/IMessageChannel contracts, LengthPrefixFramer/LengthPrefixFrameReader, SendOptions, DisconnectReason |
none |
Communication.Network.TCP.Shared |
TcpSession, StreamByteChannel, TcpTransportOptions, TcpTlsOptions, SocketKeepAliveOptions |
Shared |
Communication.Network.TCP.Server |
TcpListener accept loop |
TCP.Shared |
Communication.Network.TCP.Client |
TcpConnector connect |
TCP.Shared |
Communication.Network.RUDP.Shared |
RudpSession, RudpMessageChannel, RudpSendOptions/RudpDeliveryMethod, RudpTransportOptions, RudpTlsOptions (internal LiteNetLib/BouncyCastle hosts) |
Shared, LiteNetLib 2.1.4, BouncyCastle.Cryptography 2.7.0 |
Communication.Network.RUDP.Server |
RudpListener accept loop |
RUDP.Shared |
Communication.Network.RUDP.Client |
RudpConnector connect |
RUDP.Shared |
Installation
dotnet add package Communication.Shared
dotnet add package Communication.Network.TCP.Server # TCP server apps
dotnet add package Communication.Network.TCP.Client # TCP client apps
dotnet add package Communication.Network.RUDP.Server # RUDP server apps
dotnet add package Communication.Network.RUDP.Client # RUDP client apps
Install only the side you need; Server and Client each pull in the matching .Shared package transitively. LiteNetLib and BouncyCastle are referenced only by Communication.Network.RUDP.Shared and their types never appear in the public API.
Quick Start
A converter and a handler are all you write. Serialization format is your choice — the example uses JSON for brevity.
using System.Buffers;
using System.Text.Json;
using Communication.Shared.Messages;
using Communication.Shared.Sessions;
public sealed class ChatMessage { public string Text { get; set; } = ""; }
// Injected by the app — the library has no built-in serializer.
public sealed class JsonChatConverter : IMessageConverter
{
public void Serialize(object message, IBufferWriter<byte> writer) =>
writer.Write(JsonSerializer.SerializeToUtf8Bytes(message, message.GetType()));
public object Deserialize(ReadOnlySpan<byte> message) =>
JsonSerializer.Deserialize<ChatMessage>(message) ?? new ChatMessage();
}
// Type-registered synchronous dispatcher.
public sealed class ChatHandler : MessageHandler
{
public ChatHandler(ISession session) : base(session) =>
Register<ChatMessage>(m => Console.WriteLine($"recv: {m.Text}"));
}
TCP server + client
using System.Net;
using Communication.Network.TCP;
using Communication.Shared.Sessions;
// Server — the accept loop hands out channels; the app creates sessions.
using var listener = new TcpListener(IPAddress.Any, 32000);
listener.Accepted += channel =>
{
var session = new TcpSession(channel, new JsonChatConverter(), s => new ChatHandler(s));
session.Disconnected += (_, e) => Console.WriteLine($"peer left: {e.Reason}");
};
listener.Start(new TcpTransportOptions { MaxConnections = 1000 });
// Client — ConnectAsync returns bool; the channel is exposed afterwards, session is the app's.
var connector = new TcpConnector();
if (!await connector.ConnectAsync("127.0.0.1", 32000)) return;
using var client = new TcpSession(connector.Channel!, new JsonChatConverter(), s => new ChatHandler(s));
client.Disconnected += (_, e) => Console.WriteLine($"disconnected: {e.Reason}");
await client.SendAndFlushAsync(new ChatMessage { Text = "hello" }); // waits until written to the wire
RUDP server + client
using System.Net;
using Communication.Network.RUDP;
using Communication.Shared.Sessions;
// Server — same ownership rule as TCP: the app creates sessions in Accepted.
// Accepted channels are IMessageChannel; create the session synchronously inside
// the callback (message channels do not buffer messages received before you subscribe).
using var listener = new RudpListener(IPAddress.Any, 32000);
listener.Accepted += channel =>
{
var session = new RudpSession(channel, new JsonChatConverter(), s => new ChatHandler(s));
session.Disconnected += (_, e) => Console.WriteLine($"client left: {e.Reason}");
};
listener.Start(new RudpTransportOptions { MaxConnections = 100, ConnectionKey = "my-app-key" });
Console.WriteLine($"listening on {listener.LocalPort}");
// Client
var connector = new RudpConnector();
if (!await connector.ConnectAsync("127.0.0.1", 32000,
new RudpTransportOptions { ConnectionKey = "my-app-key" })) return;
using var client = new RudpSession(connector.Channel!, new JsonChatConverter(), s => new ChatHandler(s));
// Per-message delivery — pass RudpSendOptions (shared instances, zero allocation).
// Omitting options sends as ReliableOrdered.
await client.SendAsync(new ChatMessage { Text = "chat" }, RudpSendOptions.ReliableOrdered);
await client.SendAsync(new ChatMessage { Text = "!" }, RudpSendOptions.Unreliable);
await client.SendAndFlushAsync(new ChatMessage { Text = "final" }); // default: ReliableOrdered
Runnable end-to-end samples live in Sandbox/Chat.TCP and Sandbox/Chat.RUDP (both support a --selftest in-process round-trip mode). TCP-specific behavior is detailed in TCP.md, RUDP in RUDP.md.
Features
Session lifecycle
- Connectors/listeners only open channels (
ConnectAsync→bool, thenChannel;Acceptedevent). The application creates every session (new TcpSession(channel, converter, handlerFactory)), injecting converter, handler factory, and queue options (ADR 0006). - Disconnect is observed through exactly one event:
Session.DisconnectedwithDisconnectReason—Local,Remote,Error,Timeout, orFlowControl(plusDisconnectedEventArgs.Exceptionwhere applicable). Fires exactly once per session; a subscriber added after the disconnect is replayed once immediately, so late subscription can never miss it. Subscriber exceptions are isolated. session.Disconnect()andDispose()produceDisconnected(Local). Sending on a disconnected (or not-yet-attached) session returns an already-faultedTask— it never throws synchronously and never silently drops.- No reconnect, no heartbeat. After
Disconnected, the app reconnects (ConnectAsync+ a new session; on the server side, a fresh session per accept) and implements ping/keep-alive at the application level. TCP keep-alive is available as a user-configured socket option.
Serialization injection (IMessageConverter)
void Serialize(object message, IBufferWriter<byte> writer)— writes into pooled buffers; no per-messagebyte[]on the send path.object Deserialize(ReadOnlySpan<byte> message)— span is valid only for the call.- You own the format and its safety: never use type-selecting serializers (
BinaryFormatter,TypeNameHandling.All, …) on untrusted input. See Security.
Framing and frame limits (byte channels)
- Wire format: 4-byte little-endian length prefix + payload (
LengthPrefixFramer). MessageQueueOptions.MaxFrameLength(default 4 MB, absolute ceiling 64 MB) — oversized frames are isolated on send and rejected with anErrordisconnect on receive. Message channels (RUDP) enforce the same limit before deserialization.- The receive buffer grows only with actually accumulated bytes (never pre-allocates from a declared length), defending against memory-amplification attacks.
MessageQueueOptions.FrameTimeout(default 30 s, from the first byte of a frame) — an incomplete frame disconnects withDisconnectReason.Timeout(slowloris defense). Fully idle connections are not affected; heartbeat remains an app concern.
Send queue, backpressure, and coalesce batching
- Sends are queued; at
MessageQueueOptions.MaxPendingMessages(default 10,000) the sender asynchronously waits for space — no drops, no exceptions. - Byte-channel sends are coalesced: queued frames are batched into a single channel write up to
CoalesceLimitBytes(default 65,536 bytes; a batch may overshoot by at most one frame). - A failing serialization or length check isolates only that item — its
SendAndFlushAsyncfaults, slots are returned, and the send loop keeps running. A send failure never tears down the session. SendAndFlushAsynccompletes only after the message is on the wire;SendAsyncis the fire-and-forget queueing path.
Receive path
- Byte channels: EOF at a frame boundary →
Disconnected(Remote); mid-frame EOF or malformed length (≤ 0, over limit) →Disconnected(Error). - Message channels: payloads arrive with boundaries preserved (no framer); the over-limit pending-receive guard force-closes with
DisconnectReason.FlowControlinstead of accumulating without bound. - Deserialized messages go to your
IMessageHandler. Handlers are synchronous (void HandleMessage(object));MessageHandlerprovides concurrent-safeRegister<T>()and falls back to the most specific registered base type/interface. Handler exceptions are traced and isolated — the receive loop survives. MessageQueueOptions.InlineDispatch(defaultfalse) runs handlers directly on the receive loop for hot paths; the message-channel (RUDP) path always queues because receive callbacks share the transport's polling thread.
Flow control
- Message-channel (RUDP) receive path counts slot-waits toward
MaxPendingMessagesand closes withFlowControlwhen exceeded. The byte-channel path applies backpressure by pausing reads. Either way, memory use stays inside declared limits.
Security overview
Transport security is optional and opt-in; plain text remains the default for backward compatibility.
- TCP TLS (
SslStream) — setTcpTransportOptions.Tls(TcpTlsOptions). The server setsServerCertificate; the TLS handshake completes before the channel reachesAccepted/Channel. Client certificate validation defaults to OS policy;TargetHostandRemoteCertificateValidationallow overrides (never install an always-true callback).HandshakeTimeoutdefaults to 15 s (slowloris defense); failed/timed-out handshakes close the connection without affecting the accept loop. - RUDP packet integrity (CRC32c) —
RudpTransportOptions.Crc32cEnabled(defaultfalse; both ends must match — wire-incompatible). Each packet carries a CRC32c checksum and checksum-violating packets (corrupted or forged) are discarded before protocol processing. Detection only: an unkeyed CRC can be recomputed by an active attacker; no confidentiality or authentication. - RUDP connection key —
RudpTransportOptions.ConnectionKeygates connection requests. The default value"DS_Communication.RUDP"is a public constant; starting a server with it logs a Trace warning. Replace it per app on public networks (it is a filter, not authentication). - RUDP DTLS — set
RudpTransportOptions.Tls(RudpTlsOptions, BouncyCastle, DTLS 1.2). The handshake runs on the established connection before the channel is delivered. The client must provide pinning (RemoteCertificateValidation, e.g. viaRudpTlsOptions.GetSha256Fingerprint, recommended) orTargetHostname matching — name matching alone now requires an explicit opt-in (AllowNameOnlyCertificateMatch = true, 2.7.0+; without it the certificate is rejected, and even when opted in the certificate validity period is enforced, since name-only matching cannot stop a self-signed MITM). With neither, the server certificate is rejected by default (fail-closed). Messages over 16,381 bytes travel only asReliableOrdered(internal chunking, 64 MB reassembly ceiling).
Details: Document/04-Guides/Security.md · TCP TLS: TCP.md · RUDP security: RUDP.md · ADRs 0008, 0009.
Documentation
| Document | Contents |
|---|---|
| TCP.md | TCP usage and behavior — listener, connector, options, TLS, lifecycle, full example |
| RUDP.md | RUDP usage and behavior — delivery methods, options, polling thread model, DTLS, full example |
| Document/03-Reference/Public-API.md | Full public API contract |
| Document/03-Reference/Configuration.md | Every option and default |
| Document/02-Architecture/Overview.md | Architecture — layers, channels, pipeline (Session, Pipeline, Channel, Handler) |
| Document/04-Guides/Getting-Started.md | More usage patterns (keep-alive, reconnect loop, heartbeat) |
| Document/04-Guides/Security.md | Converter safety constraints and production checklist |
| Document/05-Decisions/ | ADRs 0001–0009 — why the stack is shaped this way |
Repository layout
| Path | Contents |
|---|---|
Source/ |
The 7 library packages |
Test/Communication.Tests |
xUnit tests (dotnet test) |
Sandbox/Chat.TCP |
TCP chat sample (--selftest for in-process verification) |
Sandbox/Chat.RUDP |
RUDP chat sample (--selftest, --tls-selftest, --bench) |
Document/ |
Documentation vault — entry point Document/01-Overview/Home.md |
Legacy/ |
Archive of the previous stack; not maintained |
Release
GitHub Actions (nuget-publish.yml): a v* tag publishes Communication.Shared, tcp/v* the three TCP packages, rudp/v* the three RUDP packages (current version 2.6.0).
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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 was computed. 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 was computed. 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. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- No dependencies.
NuGet packages (6)
Showing the top 5 NuGet packages that depend on Communication.Shared:
| Package | Downloads |
|---|---|
|
Communication.Network.RUDP.Shared
Shared reliable-UDP transport layer for DS_Communication — no third-party types appear in the public API. Provides RudpSession, RudpMessageChannel with per-message RudpSendOptions/RudpDeliveryMethod (unreliable, reliable, reliable-ordered), and RudpTransportOptions with optional DTLS 1.2 encryption (BouncyCastle) and CRC32C integrity checking. One dedicated polling thread per host plus per-session dispatch queues keep load independent of connection count. Targets .NET Standard 2.1 — runs on modern .NET and Unity. |
|
|
DRPC.Shared
Shared runtime for DRPC — a distributed RPC library for .NET that runs over reliable UDP. Contains HubBase (pending-call table with per-call timeouts, cancellation tokens, and pending/incoming concurrency caps), the wire message model (one-way calls signal CallId 0), the structured error model surfaced as RpcFaultException with typed RpcErrorCode values, and incoming routing. Transport (RUDP with optional DTLS 1.2 encryption and certificate pinning) comes from DS_Communication; serialization from DS_MessageProtocol. Common foundation of the DRPC.Client and DRPC.Server packages — reference it alongside DRPC.Attribute in the contract project shared by both sides. Targets netstandard2.1 for modern .NET and Unity 2021.2+. |
|
|
Communication.Network.TCP.Shared
Shared TCP transport layer for DS_Communication: TcpSession, StreamByteChannel and TcpTransportOptions with optional TLS (SslStream, handshake timeout enforced). Transports expose only a byte channel — the application creates and owns sessions, and disconnection surfaces solely as Session.Disconnected(DisconnectReason). Targets .NET Standard 2.1 — runs on modern .NET and Unity. |
|
|
Communication.Network.TCP_IOCP.Shared
IOCP TCP 공유 메시지·세션 컴포넌트 (.NET Standard 2.1, Unity 호환). |
|
|
Communication.Network.TCP_IOCP.Server
IOCP 기반 TCP 서버 (.NET Standard 2.1, Unity 호환). |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.7.0 | 558 | 9/13/2026 |
| 2.6.0 | 262 | 9/13/2026 |
| 2.5.2 | 253 | 9/13/2026 |
| 2.5.1 | 550 | 9/9/2026 |
| 2.5.0 | 570 | 9/8/2026 |
| 2.4.1 | 270 | 9/7/2026 |
| 2.4.0 | 549 | 9/7/2026 |
| 2.3.1 | 267 | 9/7/2026 |
| 2.3.0 | 649 | 9/7/2026 |
| 2.2.1 | 358 | 9/7/2026 |
| 2.2.0 | 258 | 9/7/2026 |
| 2.1.0 | 269 | 9/7/2026 |
| 2.0.1 | 545 | 9/7/2026 |
| 2.0.0 | 463 | 9/3/2026 |
| 1.0.2 | 389 | 7/11/2026 |
| 1.0.1 | 378 | 7/11/2026 |
| 1.0.0 | 699 | 5/2/2026 |