Communication.Network.TCP.Shared
2.7.0
dotnet add package Communication.Network.TCP.Shared --version 2.7.0
NuGet\Install-Package Communication.Network.TCP.Shared -Version 2.7.0
<PackageReference Include="Communication.Network.TCP.Shared" Version="2.7.0" />
<PackageVersion Include="Communication.Network.TCP.Shared" Version="2.7.0" />
<PackageReference Include="Communication.Network.TCP.Shared" />
paket add Communication.Network.TCP.Shared --version 2.7.0
#r "nuget: Communication.Network.TCP.Shared, 2.7.0"
#:package Communication.Network.TCP.Shared@2.7.0
#addin nuget:?package=Communication.Network.TCP.Shared&version=2.7.0
#tool nuget:?package=Communication.Network.TCP.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
- Communication.Shared (>= 2.7.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Communication.Network.TCP.Shared:
| Package | Downloads |
|---|---|
|
Communication.Network.TCP.Server
TCP server for DS_Communication: TcpListener runs the accept loop and hands each connected channel to the application, which creates and owns sessions. Pairs with Communication.Network.TCP.Shared/Client. Targets .NET Standard 2.1 — runs on modern .NET and Unity. |
|
|
Communication.Network.TCP.Client
TCP client for DS_Communication: TcpConnector establishes the connection and exposes a byte channel; the application creates and owns sessions on top. Pairs with Communication.Network.TCP.Shared/Server. Targets .NET Standard 2.1 — runs on modern .NET and Unity. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.7.0 | 138 | 9/13/2026 |
| 2.6.0 | 144 | 9/13/2026 |
| 2.5.2 | 137 | 9/13/2026 |
| 2.5.1 | 144 | 9/9/2026 |
| 2.5.0 | 149 | 9/8/2026 |
| 2.4.1 | 162 | 9/7/2026 |
| 2.4.0 | 152 | 9/7/2026 |
| 2.3.1 | 148 | 9/7/2026 |
| 2.3.0 | 141 | 9/7/2026 |
| 2.2.1 | 141 | 9/7/2026 |
| 2.2.0 | 143 | 9/7/2026 |
| 2.1.0 | 148 | 9/7/2026 |
| 2.0.1 | 147 | 9/7/2026 |
| 2.0.0 | 154 | 9/3/2026 |
| 1.0.2 | 118 | 7/11/2026 |
| 1.0.1 | 109 | 7/11/2026 |
| 1.0.0 | 105 | 5/2/2026 |