SuperSimpleTcp: a thin TCP socket wrapper for .NET with TLS
Simple wrapper for TCP client and server in C# with SSL support
At a glance
- What is it?
- SuperSimpleTcp gives a C# application a TCP client and server with connection events, send and receive callbacks and optional SSL, without any framing. The README is emphatic that you should understand message framing before using it, and points to two sibling libraries when you need framing or byte-level control.
- Who is it for?
- SuperSimpleTcp fits a .NET developer who needs a TCP endpoint quickly and can accept that the stream boundary is not the message boundary, and it fits an existing codebase that already has its own framing layer. It is the wrong choice if you need framing, because the README sends you to WatsonTcp for that, and the wrong choice if you need control over exact read and write sizes, because that is CavemanTcp.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The library's main warning is about framing, not about sockets
SuperSimpleTcp provides simple methods for creating your own TCP-based sockets application, enabling easy integration of connection management, sending and receiving data. That is the whole sales pitch, and the README immediately qualifies it. If you need integrated framing, the README sends you to WatsonTcp, and if you need discrete control over the number of bytes read from or written to a socket, it sends you to CavemanTcp. The author then says he would highly encourage you to fully understand what message framing is and why it's important before using this library, with a link to a well-known post on the subject. This is the right way to frame the project. SuperSimpleTcp is a transport, and it will hand you bytes in whatever chunks the network delivers them, so a protocol layer on top is your responsibility. The audience is a developer who either already has that layer or does not need one, such as a line-oriented protocol where the newline delimits the message.
The client and server examples are twenty lines each
The README's examples are deliberately small. The server:
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();
}And the client:
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();
}The shape is the same on both sides. You construct the object with a host and port string, subscribe to events on an `Events` collection, and start it. On the server side the events are ClientConnected, ClientDisconnected and DataReceived, and on the client side they are Connected, Disconnected and DataReceived. The `Send` overload that takes a client identifier and a payload, as in `server.Send("[ClientIp:Port]", "Hello, world!")`, is how the server targets one connected client. Both examples end with `Console.ReadKey()`, which is a hint about the intended shape of a first program: it runs until you press a key. There is no configuration object, no dependency injection and no async ceremony in the basic path.
Retries are a method, not a policy
One extra feature is called out separately in the README. 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, and like `Connect` it throws a `TimeoutException` if it cannot establish the connection:
client.ConnectWithRetries(10000); // try for up to 10000 millisecondsThat is the whole mechanism. There is no backoff strategy, no jitter and no retry counter exposed in the example, so if you need exponential backoff you would wrap the call yourself rather than configure it. The important detail is that the failure mode stays an exception rather than becoming a silent disconnect, which means a caller that wants to fail fast can catch it and a caller that wants to keep trying can loop.
v3.2.3 changed how receives work, and that has a compatibility consequence
The most recent section of the README describes v3.2.3, and the change is about thread-pool pressure rather than features. Idle clients no longer occupy a thread-pool worker per connection, because receives now await transport I/O directly instead of repeatedly calling `Socket.Poll`, credited to a contributor and issue 237. The second bullet is the one to note carefully: `ReadTimeoutMs` is retained for compatibility but is not used by background asynchronous receives, and you should use `IdleServerTimeoutMs` to disconnect from an inactive server, whose deadline resets when data is received. In other words, a setting that exists in the API no longer does what its name suggests, and the behaviour is now on the server-side idle timeout rather than on the client's read timeout. The same release expanded the regression tests to cover idle worker usage, delayed and ordered delivery, TCP and TLS teardown, peer FIN and reset, and idle timeout reset behaviour, which is a reasonable place to look for what the library considers its own edge cases.
Event delivery ordering was the bug that mattered
The v3.1.1 note explains a change that shows what the author thinks is important. `DataReceived` events dispatched via `UseAsyncDataReceivedEvents`, which is the default, now use a single dedicated worker, guaranteeing that handlers execute one-at-a-time and in the exact order the data was received. The stated purpose is to preserve the decoupling of the receive loop while eliminating the possibility of out-of-order or overlapping delivery that could corrupt message reassembly. That is a bug in your own protocol layer rather than in the transport, but it is exactly the kind of thing that a library can fix for you if it owns the dispatch. The v3.2.1 note covers the other half of event handling: a `DataReceived` handler that throws no longer stops asynchronous event dispatch, so later data is still delivered, and the exception is reported through `Logger` and telemetry. The README still recommends catching exceptions in your own handler.
Metrics and traces came through the BCL, with no exporter dependency
The v3.2.0 release added built-in observability, and the design choice is worth noting. Metrics and traces are exposed through a BCL `Meter` and an `ActivitySource`, both named `SuperSimpleTcp`, with no exporter dependency, which means you plug in OpenTelemetry or whatever else you already use rather than adopting a specific stack. The coverage is broad: connections with active count, limit, opened, closed by reason, rejected by reason and lifetime, plus accept, TLS handshake, connect and retries, send including send-lock wait, bytes and segment sizes, the duration of the `DataReceived` handler, async dispatch queue depth and wait, background monitors, and errors by operation and exception type. Control is per instance through `Settings.Telemetry`, with `Enable`, `EnableMetrics`, `EnableTraces` and `InstanceName`, on both the server and the client. All the names are public constants in `SimpleSimpleTcpTelemetryNames`, and TELEMETRY.md holds the catalog, the span topology, PromQL and alerts.
Where SuperSimpleTcp is the wrong tool
Three limits are concrete, and the README names two of them itself. The first is framing. If your protocol sends discrete messages, TCP will not deliver them at message boundaries, and a reader that assumes a read returns one message will corrupt your data. The README's answer is to use WatsonTcp instead, which integrates framing. The second is byte-level control, where CavemanTcp is the answer, for cases where you need to read or write an exact number of bytes rather than whatever arrives. The third is that the release information in the README and the release information in the repository metadata disagree. The README documents changes through v3.2.3, while the only listed release is 1.0.3 from 2019, which is not the same line of development, so before you depend on the telemetry or the v3.1.1 ordering guarantee you need to check what version you actually get from NuGet and what its changelog says.
The alternative is a built-in framework, and the difference is the API surface
There are two reasonable alternatives and they differ in where the socket code comes from. The first is to use .NET's own `TcpClient` and `TcpListener` directly, which gives you the same socket the library wraps with none of the wrapper around it. The difference in approach is that SuperSimpleTcp exists to move the common cases, connection events, send to a specific client, a data received callback, TLS setup and idle timeouts, into a shape you can wire in a few lines, and the cost is a dependency plus an API you have to learn. The second alternative is a higher-level message-oriented library such as WatsonTcp, where the difference is not style but semantics. WatsonTcp frames messages for you, so your handler receives a whole message, while SuperSimpleTcp gives you a byte array whose boundaries are whatever the network produced. If your protocol is line-based or fixed-length, that difference may not matter to you. If it is not, it is the whole design.
Editorial conclusion
SuperSimpleTcp fits a .NET developer who needs a TCP endpoint quickly and can accept that the stream boundary is not the message boundary, and it fits an existing codebase that already has its own framing layer. It is the wrong choice if you need framing, because the README sends you to WatsonTcp for that, and the wrong choice if you need control over exact read and write sizes, because that is CavemanTcp. Before you adopt it, read the framing article the README links, and note that the README covers up to v3.2.3 while the tagged release listed is 1.0.3, so check which version you actually get from NuGet.
Frequently asked questions
Does SuperSimpleTcp include message framing?
No. The README says that if you need integrated framing you should use WatsonTcp, and it strongly encourages you to understand what message framing is and why it matters before using SuperSimpleTcp.
How do I connect a SuperSimpleTcp client with retries?
Call `ConnectWithRetries(10000)` on SimpleTcpClient, which attempts to connect for up to 10000 milliseconds and throws a TimeoutException if it cannot establish the connection.
What telemetry does SuperSimpleTcp expose?
Version 3.2.0 added metrics and traces through a BCL `Meter` and `ActivitySource` both named `SuperSimpleTcp`, with no exporter dependency. Control is per instance through `Settings.Telemetry`, and the catalog is in TELEMETRY.md.
What happens if my DataReceived handler throws an exception?
Since v3.2.1 a throwing handler no longer stops asynchronous event dispatch, so later data is still delivered, and the exception is reported through `Logger` and telemetry. The README still recommends catching exceptions in your own handler.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jchristn-supersimpletcp)Was this useful?