SuperSimpleTcp provides simple methods for creating your own TCP-based sockets application, enabling easy integration of connection management, sending, and receiving data.
- If you need integrated framing, please use WatsonTcp (https://github.com/jchristn/WatsonTcp)
- If you need discrete control over the number of bytes read from or written to a socket, please use CavemanTcp (https://github.com/jchristn/CavemanTcp)
I would highly encourage you to fully understand what message framing is and why it's important before using this library: https://blog.stephencleary.com/2009/04/message-framing.html
- Dependency update:
System.Diagnostics.DiagnosticSource10.0.12 (from 8.0.1) fornetstandard2.1,net461,net462, andnet48
- Fix: a
DataReceivedhandler that throws no longer stops asynchronous event dispatch; later data is still delivered and the exception is reported throughLoggerand telemetry. Catching exceptions in your own handler is still recommended
- Built-in observability: metrics and traces through a BCL
MeterandActivitySource, both namedSuperSimpleTcp, with no exporter dependency. Covers connections (active, limit, opened, closed by reason, rejected by reason, lifetime), accept, TLS handshake, connect and retries, send (including send-lock wait), bytes and segment sizes,DataReceivedhandler duration, async dispatch queue depth and wait, background monitors, and errors by operation and exception type - Per-instance
Settings.Telemetry(Enable,EnableMetrics,EnableTraces,InstanceName) on both server and client - All names are public constants in
SimpleTcpTelemetryNames; see TELEMETRY.md for the catalog, span topology, PromQL, and alerts
DataReceivedevents dispatched viaUseAsyncDataReceivedEvents(the default) now use a single dedicated worker, guaranteeing handlers execute one-at-a-time and in the exact order data was received. This preserves the receive-loop decoupling while eliminating the possibility of out-of-order or overlapping delivery that could corrupt message reassembly (thank you @aa53420, issue #236)
- Internal performance refactor to reduce send/receive allocations, remove unnecessary scheduler hops, and tighten connection teardown
- Async connect and retry paths now rely on a shared non-blocking core and async TLS handshake setup
- Server max-connection handling now keeps the listener open and rejects overflow connections instead of stop/sleep/restart churn
- Idle timeout tracking now uses monotonic timestamps and per-client state instead of dictionary churn
- Touchstone-based shared test suites with console, xUnit, and NUnit runners
- Included benchmark harness and
RunBenchmarks.batrunner that writes timestamped summaries underbenchmarks/
A special thanks to the community of people that have contributed to or otherwise improved this project!
@tinohager @u1035 @cmeeren @pha3z @opnop @kopkarmecoindo @simonhaines @matt1tk @lukeacat @exergist @maynardsi @sector13371 @loganwoodxyz @jwfxpr @IanPNewson @EGirardi @redrabbit007 @eatyouroats @joreg @CetinOzdil @tautvilis @ATS-CE @TheNybbler @huangjia2107 @zllvm @Energiz0r @Espen-Kalhagen-Element-Logic @MarkBreedveld @QTPah @olifer @KimEoJin @BrandenEK @Somfic @kszaq @rstelmokaitis @GoannaGuy @Sympatron
Need help or have feedback? Please file an issue here!
using SuperSimpleTcp;
void Main(string[] args)
{
// instantiate
SimpleTcpServer server = new SimpleTcpServer("127.0.0.1:9000");
// set events
server.Events.ClientConnected += ClientConnected;
server.Events.ClientDisconnected += ClientDisconnected;
server.Events.DataReceived += DataReceived;
// let's go!
server.Start();
// once a client has connected...
server.Send("[ClientIp:Port]", "Hello, world!");
Console.ReadKey();
}
static void ClientConnected(object sender, ConnectionEventArgs e)
{
Console.WriteLine($"[{e.IpPort}] client connected");
}
static void ClientDisconnected(object sender, ConnectionEventArgs e)
{
Console.WriteLine($"[{e.IpPort}] client disconnected: {e.Reason}");
}
static void DataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine($"[{e.IpPort}]: {Encoding.UTF8.GetString(e.Data.Array, 0, e.Data.Count)}");
}using SuperSimpleTcp;
void Main(string[] args)
{
// instantiate
SimpleTcpClient client = new SimpleTcpClient("127.0.0.1:9000");
// set events
client.Events.Connected += Connected;
client.Events.Disconnected += Disconnected;
client.Events.DataReceived += DataReceived;
// let's go!
client.Connect();
// once connected to the server...
client.Send("Hello, world!");
Console.ReadKey();
}
static void Connected(object sender, ConnectionEventArgs e)
{
Console.WriteLine($"*** Server {e.IpPort} connected");
}
static void Disconnected(object sender, ConnectionEventArgs e)
{
Console.WriteLine($"*** Server {e.IpPort} disconnected");
}
static void DataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine($"[{e.IpPort}] {Encoding.UTF8.GetString(e.Data.Array, 0, e.Data.Count)}");
}The ConnectWithRetries method on SimpleTcpClient can be used instead of Connect to continually attempt to establish connections with the server for a given period of time. Like Connect, ConnectWithRetries will throw a TimeoutException if it is unable to successfully establish a connection.
client.ConnectWithRetries(10000); // try for up to 10000 millisecondsBoth SimpleTcpClient and SimpleTcpServer have settable values for:
Logger- method to invoke to send log messages from either SimpleTcpClient or SimpleTcpServerSettings.MutuallyAuthenticate- only used if SSL is enabled, demands that both client and server mutually authenticateSettings.AcceptInvalidCertificates- accept and allow certificates that are invalid or cannot be validatedKeepalive- to enable/disable keepalives and set specific parameters (disabled by default)
SimpleTcpServer also has:
Settings.IdleClientTimeoutMs- automatically disconnect a client if data is not received within the specified number of milliseconds
Additionally, both SimpleTcpClient and SimpleTcpServer offer a statistics object under SimpleTcpClient.Statistics and SimpleTcpServer.Statistics. These values (other than start time and uptime) can be reset using the Statistics.Reset() API.
IMPORTANT
- If you specify
127.0.0.1as the listener IP address, it will only be able to accept connections from within the local host. - To accept connections from other machines:
- Use a specific interface IP address, or
- Use
null,*,+, or0.0.0.0for the listener IP address (requires admin privileges to listen on any IP address)
- Make sure you create a permit rule on your firewall to allow inbound connections on that port
- If you use a port number under 1024, admin privileges will be required
A certificate named simpletcp.pfx is provided for simple testing. It should not expire for a really long time. It's a self-signed certificate and you should NOT use it in production. Its export password is simpletcp.
SuperSimpleTcp emits metrics and traces on a Meter and an ActivitySource named SuperSimpleTcp. Nothing is exported until your application subscribes a collector, and the cost is negligible when nothing is listening. Subscribe with Radiant, the OpenTelemetry SDK, or any MeterListener/ActivityListener:
// Radiant
RadiantSettings settings = new RadiantSettings("my-service");
settings.Sources.AddMeter(SimpleTcpTelemetryNames.MeterName);
settings.Sources.AddActivitySource(SimpleTcpTelemetryNames.ActivitySourceName);
using RadiantHost host = RadiantHost.Start(settings);
// OpenTelemetry SDK
using MeterProvider meters = Sdk.CreateMeterProviderBuilder().AddMeter("SuperSimpleTcp").AddOtlpExporter().Build();
using TracerProvider tracer = Sdk.CreateTracerProviderBuilder().AddSource("SuperSimpleTcp").AddOtlpExporter().Build();Name each server or client so their series can be told apart, and turn signals off per instance if needed:
server.Settings.Telemetry.InstanceName = "ingest"; // supersimpletcp.instance label
server.Settings.Telemetry.EnableTraces = false; // metrics onlyOperators get connection counts versus MaxConnections, rejections by reason (not_permitted, blocked, max_connections, tls_failed), close reasons (normal, kicked, timeout), connect/TLS/send latency with outcome and error.type, send-lock wait, the async DataReceived queue depth and wait, handler duration, and an error counter by operation. Spans cover accept, TLS handshake, connect (with retries), send, receive, handler processing (propagated across the async dispatch hand-off), and disconnect. The full catalog, label values, trace topology, PromQL, and recommended alerts are in TELEMETRY.md.
Run RunBenchmarks.bat from the repository root to build src/Test.PerformanceBenchmark and write a timestamped benchmark summary to benchmarks/Benchmark-YYYYMMDD-HHMMSS.txt.
The project TcpTest (https://github.com/jchristn/TcpTest) was built specifically to provide a reference for SuperSimpleTcp to handle a variety of disconnection scenarios. The disconnection tests for which SimpleTcp is evaluated include:
| Test case | Description | Pass/Fail |
|---|---|---|
| Server-side dispose | Graceful termination of all client connections | PASS |
| Server-side client removal | Graceful termination of a single client | PASS |
| Server-side termination | Abrupt termination due to process abort or CTRL-C | PASS |
| Client-side dispose | Graceful termination of a client connection | PASS |
| Client-side termination | Abrupt termination due to a process abort or CTRL-C | PASS |
| Network interface down | Network interface disabled or cable removed | Partial (see below) |
Additionally, as of v2.1.0, support for TCP keepalives has been added to SimpleTcp, primarily to address the issue of a network interface being shut down, the cable unplugged, or the media otherwise becoming unavailable. It is important to note that keepalives are supported in .NET Core and .NET Framework, but NOT .NET Standard. As of this release, .NET Standard provides no facilities for TCP keepalives.
TCP keepalives are disabled by default. To enable them:
server.Keepalive.EnableTcpKeepAlives = true;
server.Keepalive.TcpKeepAliveInterval = 5; // seconds to wait before sending subsequent keepalive
server.Keepalive.TcpKeepAliveTime = 5; // seconds to wait before sending a keepalive
server.Keepalive.TcpKeepAliveRetryCount = 5; // number of failed keepalive probes before terminating connectionSome important notes about TCP keepalives:
- Keepalives only work in .NET Core and .NET Framework
- Keepalives can be enabled on either client or server, but are implemented and enforced in the underlying operating system, and may not work as expected
Keepalive.TcpKeepAliveRetryCountis only applicable to .NET Core; for .NET Framework, this value is forced to 10- Your mileage may vary; please remember that these are managed by the underlying operating system and not by this library
.NET Core is the preferred environment for cross-platform deployment on Windows, Linux, and Mac. For those that use Mono, SimpleTcp should work well in Mono environments. It is recommended that you execute the containing EXE using --server and after using the Mono Ahead-of-Time Compiler (AOT).
mono --aot=nrgctx-trampolines=8096,nimt-trampolines=8096,ntrampolines=4048 --server myapp.exe
mono --server myapp.exe
Please refer to CHANGELOG.md.