<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Magithar Sridhar</title>
    <description>The latest articles on DEV Community by Magithar Sridhar (@magithar).</description>
    <link>https://dev.to/magithar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3863740%2F7aac0feb-68f1-4934-9ccf-be95a143de31.jpeg</url>
      <title>DEV Community: Magithar Sridhar</title>
      <link>https://dev.to/magithar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/magithar"/>
    <language>en</language>
    <item>
      <title>How I Built a Socket.IO v4 Client for Unity from Scratch (with WebGL Support)</title>
      <dc:creator>Magithar Sridhar</dc:creator>
      <pubDate>Mon, 06 Apr 2026 11:57:40 +0000</pubDate>
      <link>https://dev.to/magithar/how-i-built-a-socketio-v4-client-for-unity-from-scratch-with-webgl-support-33f</link>
      <guid>https://dev.to/magithar/how-i-built-a-socketio-v4-client-for-unity-from-scratch-with-webgl-support-33f</guid>
      <description>&lt;p&gt;Every Unity multiplayer developer hits the same wall.&lt;/p&gt;

&lt;p&gt;You've got a Node.js backend running Socket.IO. Your web client connects in three lines. Then you open the Unity docs, search the Asset Store, and spend two hours realising that the Unity side of this equation is... a mess.&lt;/p&gt;

&lt;p&gt;That's where I was. And it's why I built &lt;a href="https://github.com/Magithar/socketio-unity" rel="noopener noreferrer"&gt;socketio-unity&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The State of Socket.IO in Unity (Before This)
&lt;/h2&gt;

&lt;p&gt;When I started, the options were grim:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Paid assets&lt;/strong&gt; with no source code and no way to fix bugs yourself&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Abandoned repos&lt;/strong&gt; targeting Socket.IO v2 or v3, incompatible with the current protocol&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebGL support that was either broken or missing entirely&lt;/strong&gt; — and WebGL multiplayer is huge if you want browser-based games&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I needed something open-source, Socket.IO v4, and WebGL-verified. It didn't exist, so I built it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture (High Level)
&lt;/h2&gt;

&lt;p&gt;Before diving into the hard part, here's the shape of the library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Socket.IO Server
      ↓
  ITransport  ←── WebSocketTransport (Standalone)
              ←── WebGLWebSocketTransport (Browser)
      ↓
 EngineIOClient  (handshake, heartbeat, RTT)
      ↓
 SocketIOClient  (namespaces, events, ACKs, binary)
      ↓
 SocketIOManager (Unity singleton)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single WebSocket connection. Namespaces multiplexed over it. Tick-driven — no background threads. Everything dispatched on Unity's main thread.&lt;/p&gt;

&lt;p&gt;The public API ends up feeling natural for Unity developers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SocketIOManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnConnected&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connected!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;On&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ws://localhost:3002"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello from Unity!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Getting there wasn't straightforward. The hardest part wasn't the protocol parsing, or the reconnect logic, or the binary packet assembly.&lt;/p&gt;

&lt;p&gt;It was WebGL.&lt;/p&gt;

&lt;h2&gt;
  
  
  The WebGL Problem (And How I Solved It)
&lt;/h2&gt;

&lt;p&gt;WebGL builds don't run on the .NET runtime. They compile to WebAssembly, which means your C# code runs in the browser — but it can't touch browser APIs directly. No raw sockets. No threads. No &lt;code&gt;System.Net&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For WebSocket specifically, you have to go through a JavaScript bridge: a &lt;code&gt;.jslib&lt;/code&gt; file that Unity compiles into the final build and exposes to your C# code via &lt;code&gt;DllImport&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's what that looks like at the boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// C# side — calling into JS&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"__Internal"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;SocketIOConnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"__Internal"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;SocketIOSend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SocketIOWebGL.jslib — the JS side&lt;/span&gt;
&lt;span class="nf"&gt;mergeInto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LibraryManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;library&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;SocketIOConnect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urlPtr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UTF8ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urlPtr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// create WebSocket in browser JS, wire up callbacks&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// marshal data back to C#&lt;/span&gt;
      &lt;span class="nc"&gt;SendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SocketIOManager&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OnWebGLMessage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sounds manageable. In practice there were three brutal edge cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. String marshalling across the boundary
&lt;/h3&gt;

&lt;p&gt;Passing strings between C# and JS in Unity WebGL requires manual memory management. You allocate a buffer in the JS heap, write the UTF-8 bytes, pass the pointer to C#. If you get the buffer size wrong, or forget to free it, you get silent corruption or memory leaks that only appear after hundreds of messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Binary data
&lt;/h3&gt;

&lt;p&gt;Socket.IO supports binary events — you can send &lt;code&gt;byte[]&lt;/code&gt; payloads alongside JSON. In WebGL, &lt;code&gt;ArrayBuffer&lt;/code&gt; from JS can't be passed directly to C# as a byte array. You have to Base64-encode it in JS, pass it as a string, decode it in C#.&lt;/p&gt;

&lt;p&gt;For large payloads (say, 10MB in stress tests) this adds real overhead. But it was the only reliable cross-boundary option.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Domain reload safety
&lt;/h3&gt;

&lt;p&gt;Unity's editor reloads the domain (re-initialises all C# state) when you enter Play mode. If your &lt;code&gt;.jslib&lt;/code&gt; holds a reference to a WebSocket that no longer has a C# counterpart, you get ghost connections — sockets that are open in JS but invisible to C#, firing events into the void.&lt;/p&gt;

&lt;p&gt;The fix was a cleanup hook in &lt;code&gt;OnDisable&lt;/code&gt; that explicitly closes the JS-side socket on every domain reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnDisable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if UNITY_WEBGL &amp;amp;&amp;amp; !UNITY_EDITOR
&lt;/span&gt;    &lt;span class="nf"&gt;SocketIOClose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple when you know. Absolutely maddening to diagnose before you do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Things Worth Knowing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Reconnect with backoff and jitter
&lt;/h3&gt;

&lt;p&gt;Reconnect logic seems easy until you have 500 clients all disconnecting at once and hammering your server simultaneously. Jitter spreads the reconnect attempts across a window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReconnectConfig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ReconnectConfig&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;initialDelay&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;multiplier&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;maxDelay&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;30f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;maxAttempts&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;jitterPercent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// ±10% randomness on each delay&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Everything runs on the main thread
&lt;/h3&gt;

&lt;p&gt;Unity's API is not thread-safe. Any callback that touches a &lt;code&gt;GameObject&lt;/code&gt;, &lt;code&gt;Transform&lt;/code&gt;, or &lt;code&gt;Debug.Log&lt;/code&gt; needs to be on the main thread. The library uses &lt;code&gt;UnityMainThreadDispatcher&lt;/code&gt; to queue all event callbacks — so you never have to think about this.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI on every commit
&lt;/h3&gt;

&lt;p&gt;The test suite covers 38+ protocol edge cases, binary assembler correctness, ACK overflow, reconnect config, and stress tests (1K events, 10MB binary, 100 concurrent ACKs). It runs on Unity 2022.3 LTS via GitHub Actions on every push.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using It in Your Project
&lt;/h2&gt;

&lt;p&gt;Install via Package Manager → &lt;code&gt;+&lt;/code&gt; → &lt;code&gt;Add package from git URL&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/Magithar/socketio-unity.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll also need NativeWebSocket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/endel/NativeWebSocket.git#upm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create an empty GameObject, attach &lt;code&gt;SocketIOManager&lt;/code&gt;, and you're connected.&lt;/p&gt;

&lt;p&gt;There are four samples included (Basic Chat, PlayerSync, Lobby, LiveDemo) each with a Node.js test server you can run locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;Honestly? Start with the WebGL bridge earlier. I designed the transport abstraction (&lt;code&gt;ITransport&lt;/code&gt;) upfront specifically to keep WebGL isolated from the rest of the stack — but I still underestimated how much the JS boundary would dictate the architecture around binary data and lifecycle management.&lt;/p&gt;

&lt;p&gt;If you're building anything that needs to run in a browser, design for WebGL constraints from day one, not as an afterthought.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Repo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Magithar/socketio-unity" rel="noopener noreferrer"&gt;github.com/Magithar/socketio-unity&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Want to see it running in a browser before installing anything? &lt;/p&gt;

&lt;p&gt;There's a &lt;strong&gt;&lt;a href="https://magithar.github.io/socketio-unity/" rel="noopener noreferrer"&gt;live WebGL demo&lt;/a&gt;&lt;/strong&gt; — open it in two tabs to watch real-time player sync in action.&lt;/p&gt;

&lt;p&gt;MIT licensed. Socket.IO v4 only. WebGL verified. Zero paid dependencies.&lt;/p&gt;

&lt;p&gt;If you're building multiplayer in Unity and connecting to a Socket.IO backend — give it a try. Issues and PRs welcome.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this saved you a few hours of pain, a ⭐ on the repo goes a long way.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>gamedev</category>
      <category>networking</category>
      <category>webgl</category>
    </item>
  </channel>
</rss>
