SocketIOClient, a Socket.IO client for .NET with polling and WebSocket
socket.io-client implemention for .NET
At a glance
- What is it?
- SocketIOClient is a C# implementation of the Socket.IO client, published on NuGet under the name SocketIOClient, that talks to Socket.IO servers v2, v3 and v4. It covers acknowledgement callbacks, binary payloads, pluggable JSON serialization and the Engine.IO version switch, and it starts on HTTP polling before upgrading.
- Who is it for?
- SocketIOClient suits teams whose server is already Socket.IO and whose application lives on .NET, especially when binary payloads or acknowledgement callbacks are needed and a Newtonsoft.Json stack is already in place. It is the wrong choice for a plain WebSocket endpoint, for Node server v2.x when connection credentials are required, or for teams that need a transport other than polling and WebSocket.
- 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 91 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 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why a .NET client for Socket.IO rather than a plain WebSocket
A Socket.IO server is not a WebSocket server, and this is the whole problem the project exists to solve. The wire protocol carries an event name plus a payload, acknowledgements with callbacks, and optional binary attachments, all negotiated through an Engine.IO handshake that names a protocol version before the real conversation starts. Hand rolling that over a raw WebSocket client means reimplementing the handshake, the framing and the callback plumbing for every project.
SocketIOClient, distributed on NuGet as SocketIOClient, is that implementation for C#. The README describes it as a client for .NET that supports Socket.IO server v2, v3 and v4, with both HTTP polling and WebSocket implemented. The repository is a Visual Studio solution, socket.io-client-csharp.sln, with src and tests directories, and the code is C# throughout.
The audience is therefore quite specific: a .NET team talking to a Node.js service that already speaks Socket.IO. If your server is a plain WebSocket endpoint, this library is more protocol than you need. The API surface reflects the Socket.IO event model rather than a generic message channel, with an On handler for incoming events and EmitAsync for outgoing ones.
The connection object and how event data is read
The quick start builds a SocketIO instance around a server URI, then registers a handler per event name. The handler receives a context whose RawText property exposes the wire form of the message, and whose GetValue method pulls typed items out of the payload by index.
var client = new SocketIO(new Uri("http://localhost:11400"), new SocketIOOptions
{
Query = new NameValueCollection
{
["user"] = "Alice"
},
// ...
});The README shows that the raw array for an event arrives as an event name followed by the data items, and that each item is addressed by position and by type. A string sits at index 0, an int at index 1, and a custom class at index 2, which is why the handler reads them with GetValue<string>, GetValue<int> and GetValue<User> rather than with a single typed argument. That positional model is a direct consequence of the Socket.IO wire format, and it means the shape of the payload is a contract you have to know in advance.
One detail the documentation leaves open: the quick start shows the constructor and the event registration, but it does not spell out the call that opens the connection or its ordering relative to the handler registration. Anyone adopting the library should confirm that sequence against the API surface in the package itself rather than assume it from the README.
Polling first, WebSocket second
Transport selection is deliberate and configurable. The Transport option defaults to Polling, and AutoUpgrade defaults to true, which means the connection starts over HTTP long polling and moves to WebSocket after the handshake if the server supports it. If the server offers only WebSocket, the client can be pointed at TransportProtocol.WebSocket directly.
The reason for the default is interoperability with older and more constrained servers, and the cost is latency on the first messages of a session. Long polling holds a request open until the server has something to send, which is acceptable for infrequent events and poor for chat-like traffic. Once AutoUpgrade completes, the WebSocket path removes that waiting.
Reconnection is handled as well. Reconnection defaults to true, ReconnectionAttempts defaults to 10, and ReconnectionDelayMax defaults to 5000 milliseconds, which bounds a random delay between attempts. Setting Reconnection to false makes the first failure final. ConnectionTimeout sets a 30 second limit on each attempt, which is worth noting for environments where a blackholed host should fail fast instead of hanging for half a minute.
Engine.IO version, credentials and headers
The EIO option decides which Engine.IO generation the handshake asks for, and it defaults to V4. The README states that for a Socket.IO server on v2.x you must set EIO = 3, so the version switch is the first thing to get right when pointing the client at an older server.
That switch has a consequence beyond the handshake. The Auth option configures connection credentials, and the options table states plainly that this feature is not supported when EIO = 3. On a v2.x server you therefore lose the ability to send credentials as part of the connection, and have to fall back on other mechanisms such as the query string.
Two other handshake-time knobs exist. Query carries values sent before the connection is established, as the example above shows with a user name. ExtraHeaders sends request headers with each request and is described as usable during the handshake phase, which is where per request headers typically do their work in a Socket.IO deployment behind a gateway.
The Path option, /socket.io by default, has to match the server mount point. It is the classic mismatch when a client library works against a development server and fails behind a reverse proxy that rewrites the path.
Acknowledgements in both directions
Socket.IO acknowledgements are request and response pairs around an event, and SocketIOClient implements both ends. On the client side, EmitAsync takes an optional callback that the server invokes when it has finished processing, and the callback receives the acknowledgement payload with the same RawText and GetValue accessors as an event handler.
await client.EmitAsync("hi", ["Hi, I'm Client"], ack =>
{
Console.WriteLine(ack.RawText);
// RawText: ["Hi, I'm Client","Hi, I'm Server"]
var message1 = ack.GetValue<string>(0)!;
Console.WriteLine(message1); // Hi, I'm Client
var message2 = ack.GetValue<string>(1)!;
Console.WriteLine(message2); // Hi, I'm Server
return Task.CompletedTask;
});The server counterpart is a plain callback as the last argument of the event handler:
socket.on('hi', (m1, fn) => {
fn(m1, 'Hi, I\'m Server');
});The reverse direction works too. A handler can send acknowledgement data back to a server that emitted an event with a callback, which is what SendAckDataAsync is for:
client.On("add", async ctx =>
{
var a = ctx.GetValue<int>(0);
var b = ctx.GetValue<int>(1);
var c = a + b;
await ctx.SendAckDataAsync([c]);
});The server side of that pair echoes the result back through the callback it received. In practice this is the mechanism to use when a .NET service must wait for a Node side computation to finish before it continues.
Binary payloads and the serializer split
Binary support is a first class part of the client. Because System.Text.Json is used by default for serialization and deserialization, a byte array inside a payload object travels as JSON with a placeholder marker.
class FileDTO
{
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("mimeType")]
public string MimeType { get; set; }
[JsonPropertyName("bytes")]
public byte[] Bytes { get; set; }
}Emitting the object and reading it back on the other end uses the same event mechanism as text:
await client.EmitAsync("1:emit", [
new FileDTO
{
Name = "template.html",
MimeType = "text/html",
Bytes = Encoding.UTF8.GetBytes("<div>test</div>")
}
]);client.On("new files", ctx =>
{
// RawText: ["new files", {"name":"template.html","mimeType":"text/html","bytes":{"_placeholder":true,"num":0}}]
var result = ctx.GetValue<FileDTO>();
Console.WriteLine(Encoding.UTF8.GetString(result.Bytes))
});The serializer itself is configurable through a service collection, which is how naming policies and other JsonSerializerOptions reach the client:
var client = new SocketIO(new Uri("http://localhost:11400"), services =>
{
services.AddSystemTextJson(new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
});If your application already depends on Newtonsoft.Json, the README points to a separate package for it, SocketIOClient.Serializer.NewtonsoftJson. That is an extra dependency to install rather than a switch to flip, and it is the practical cost of the pluggable design.
A related limitation is visible in the raw text of the binary example: the bytes are represented as a placeholder with a numeric marker. Large binary transfers therefore deserve a measurement in your own environment rather than an assumption that they behave like a browser client.
Maintenance, releases and the MIT licence
The last push to the repository was on 2026-07-24, and the release history is recent and steady: v4.0.2 on 2026-03-26, v4.0.1 on 2026-02-28 and v4.0.0 on 2026-01-31. The jump to a 4.x line at the start of 2026 suggests a deliberate version reset rather than a quiet incremental patch sequence, so treat a major version bump as a chance to read the CHANGELOG rather than as a routine upgrade.
The project is built and tested on Azure DevOps, and the README badge points at a pipeline named Unit Test and Integration Test, so the repository carries both unit and integration test stages. The default branch is master rather than main, and the tree also contains a Chinese translation of the README, a husky setup in package.json for git hooks and a .config directory for tooling settings.
The licence is MIT. In practice that means permissive use with attribution and no copyleft obligation on your own code, which is why the library sits comfortably inside a proprietary .NET product. That is a licensing observation rather than legal advice. The more concrete upgrade cost for an adopter is the surface area: the client API, the options set, the serializer package choice and the Engine.IO version must all line up with the server you actually run, and a major release may move any of them.
Editorial conclusion
SocketIOClient suits teams whose server is already Socket.IO and whose application lives on .NET, especially when binary payloads or acknowledgement callbacks are needed and a Newtonsoft.Json stack is already in place. It is the wrong choice for a plain WebSocket endpoint, for Node server v2.x when connection credentials are required, or for teams that need a transport other than polling and WebSocket. Before adopting it, check that the Engine.IO version on your server matches the EIO option, confirm which serializer package you want installed, and verify that a polling-first connection performs acceptably against your own latency budget.
Frequently asked questions
Which Socket.IO server versions does SocketIOClient support?
The README states that it supports Socket.IO server v2, v3 and v4. For a server on v2.x you set the EIO option to 3, because it defaults to V4.
How do I install SocketIOClient in a .NET project?
The package is published on NuGet under the name SocketIOClient, which the README links from its badge. If you want Newtonsoft.Json instead of the default System.Text.Json, the README points to a separate package, SocketIOClient.Serializer.NewtonsoftJson.
Can I send connection credentials with SocketIOClient?
Yes, the Auth option configures connection credentials. The options table notes that this feature is not supported when EIO = 3, which is the setting required for Socket.IO server v2.x.
Does SocketIOClient use WebSocket by default?
No. Transport defaults to Polling and AutoUpgrade defaults to true, so the connection starts on HTTP polling and upgrades to WebSocket after the handshake when the server supports it. Set Transport to TransportProtocol.WebSocket when the server only offers WebSocket.
How does SocketIOClient handle reconnection?
Reconnection defaults to true with ReconnectionAttempts set to 10, and ReconnectionDelayMax of 5000 milliseconds bounds the random delay between attempts. Setting Reconnection to false makes an initial failure final with no retries.
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/doghappy-socket-io-client-csharp)Was this useful?