<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[blog.denny.dev]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>https://blog.denny.dev/</link><image><url>https://blog.denny.dev/favicon.png</url><title>blog.denny.dev</title><link>https://blog.denny.dev/</link></image><generator>Ghost 5.26</generator><lastBuildDate>Wed, 22 Apr 2026 16:50:06 GMT</lastBuildDate><atom:link href="https://blog.denny.dev/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Protohackers Solving Problem 0 with C#]]></title><description><![CDATA[In this post we implement a TCP Echo Service using C# to solve Problem 0 of the Protohackers challenges.]]></description><link>https://blog.denny.dev/protohackers-problem-0-csharp/</link><guid isPermaLink="false">63b0c55debc28e46885f59e1</guid><category><![CDATA[C#]]></category><dc:creator><![CDATA[Denny]]></dc:creator><pubDate>Sun, 01 Jan 2023 21:00:00 GMT</pubDate><media:content url="https://blog.denny.dev/content/images/2023/01/proto-with-csharp-2.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.denny.dev/content/images/2023/01/proto-with-csharp-2.png" alt="Protohackers Solving Problem 0 with C#"><p><a href="https://protohackers.com">Protohackers</a> provides you a set of challenges to create servers for network protocols. You can use whichever language you&apos;d like, the implementation just needs to be publicly accessible so the automated tests can run against it.</p><p>During my Christmas break I decided to give Protohackers <a href="https://protohackers.com/problem/0">Problem 0 (Smoke Test)</a> a try and implement a TCP Echo Service using C#. The gist of challenge 0 is essentially the following:</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">1. Accept TCP connections.<br>2. Whenever you receive data from a client, send it back unmodified.<br>3. Make sure you don&apos;t mangle binary data, and that you can handle at least 5 simultaneous clients.</div></div><p>Now admittedly it&apos;s been a looong time since I tried to do anything with network protocols/sockets so feel free to let me know if there are any improvements that can be made! To start the project off I created a .NET 7 command-line project.</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F50D;</div><div class="kg-callout-text">If you&apos;d like to just skip to the finished code you can find it on <a href="https://github.com/dennyferra/ProtoHackers/blob/master/ProtoHackers/Program.cs">GitHub</a>.</div></div><p>The ultimate goal of our service is to echo back data that is sent to our service. In order to do this we must first be able to handle connections to our app. Lets look at the code that allows us to start listening to connections and I&apos;ll explain a few details afterwards:</p><pre><code class="language-csharp">using System.Net.Sockets;
using System.Net;

// Constants for our echo server
const int PORT = 11000;
const int RECEIVE_BUFFER_SIZE = 8192;

// Get IPv4 endpoint
IPHostEntry ipHostInfo = await Dns.GetHostEntryAsync(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList.First(ip =&gt; ip.AddressFamily == AddressFamily.InterNetwork);
IPEndPoint ipEndPoint = new(ipAddress, PORT);

// Bind the listener
using Socket listener = new(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(ipEndPoint);
listener.Listen(100);

Console.WriteLine($&quot;Started listening on {ipEndPoint}&quot;);</code></pre><h2 id="get-ip-endpoint">Get IP Endpoint</h2><p><em>Lines 8-11</em><br>To determine the proper IP address to listen on I filtered <code>ipHostInfo.AddressList</code> and returned the first IP that was an <code>AddressFamily.InterNetwork</code> which translates to an <a href="https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.addressfamily?view=net-7.0">Address for IP version 4</a>. This essentially gets my machine&apos;s local IPv4 address (example: <code>192.168.1.25</code>).</p><blockquote>TCP/IP uses a network address and a service port number to uniquely identify a service. <a href="https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/sockets/socket-services">[1]</a></blockquote><p>The port is hardcoded in our constants so with the IP address and our port we now have our endpoint defined.</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x26A0;&#xFE0F;</div><div class="kg-callout-text">You may have multiple IPv4 addresses or maybe you&apos;re only using IPv6. Either way if you have trouble look through your <code>ipHostInfo.AddressList</code> and find the proper interface you want to use and filter to that address accordingly.</div></div><h2 id="bind-the-listener">Bind the Listener</h2><p><em>Lines 13-16</em><br>We now need to start listening for connections so that our service can communicate with clients that connect to it. To do this we need to create a socket using the endpoint information we previously created.</p><blockquote>A socket creates a data pipe between your app and the remote destination. <a href="https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/sockets/socket-services">[1]</a></blockquote><p>Calling <code>bind</code> on the socket will bind the socket to the endpoint. And finally calling <code>listen</code> lets the socket start listening for incoming connection attempts. The <code>backlog</code> parameter of the <code>listen</code> method specifies the number of incoming connections that can be queued for acceptance. </p><p>We&apos;re not done yet! We can now listen for connections but we haven&apos;t handled the process of accepting the connection, receiving data, or echoing the data back. We&apos;ll take a look at that next.</p><h2 id="accepting-connections">Accepting Connections</h2><p>Continuing from where our code previously left off:</p><pre><code class="language-csharp">SocketAsyncEventArgs e = new();
e.Completed += AcceptSocketCallback;
if (!listener.AcceptAsync(e))
{
    AcceptSocketCallback(listener, e);
}

// Keep console active until keypress
Console.ReadKey(true);</code></pre><p>In order to handle our connections in an asynchronous manner we create a new <code>SocketAsyncEventArgs</code> class and attach to the <code>Completed</code> event.</p><blockquote>Accepting connections asynchronously gives you the ability to send and receive data within a separate execution thread.</blockquote><p>Next we check if the <code>listener.AcceptAsync</code> operation completed synchronously and if so we manually execute the callback function (see note below). Maybe I could have wrapped this in <code>Task.Run</code>.</p><blockquote>If <code>AcceptAsync</code> I/O operation is pending the <code>Completed</code> event of our <code>SocketAsyncEventArgs</code> will be called when the operation finishes. However, if the I/O operation completed <strong>synchronously</strong> the <code>Completed</code> event will not be raised and we need to manually execute the callback method. <a href="https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.acceptasync?view=net-7.0#system-net-sockets-socket-acceptasync(system-net-sockets-socketasynceventargs)">[2]</a></blockquote><p>Now lets look at the <code>AcceptSocketCallback</code> method to see how we communicate with clients that connect to our service.</p><h3 id="communicating-with-clients">Communicating with clients</h3><pre><code class="language-csharp">static async void AcceptSocketCallback(object? sender, SocketAsyncEventArgs e)
{
    if (sender == null) return;
    Socket listenSocket = (Socket)sender;

    do
    {
        try
        {
            Socket? newSocket = e.AcceptSocket;
            if (newSocket == null) return;

            Console.WriteLine($&quot;Connection open {newSocket.RemoteEndPoint}&quot;);

            byte[] buffer = new byte[RECEIVE_BUFFER_SIZE];
            int received;

            do
            {
                received = await newSocket.ReceiveAsync(buffer, SocketFlags.None);

                if (received &gt; 0)
                {
                    Console.WriteLine($&quot; &lt;&lt; Received {received} bytes...&quot;);

                    // Echo the data back
                    await newSocket.SendAsync(buffer[..received], SocketFlags.None);
                }
            } while (received &gt; 0);

            Console.WriteLine($&quot;Connection closing {newSocket.RemoteEndPoint}&quot;);
            newSocket.Disconnect(false);
            newSocket.Close();
        }
        catch
        {
            // handle any exceptions here;
            Console.WriteLine(&quot;Oops an error occurred!&quot;);
        }
        finally
        {
            e.AcceptSocket = null; // to enable reuse
        }
    } while (!listenSocket.AcceptAsync(e));
}</code></pre><p>We start with a <code>do...while</code> where we will continue to loop if the <code>AcceptAsync</code> result is <code>false</code>. Remember in the previous code if it completes asynchronously then it will fire the <code>Completed</code> event so we won&apos;t need to manually run <code>AcceptSocketCallback</code> otherwise we must run the callback ourselves.</p><p>On <em>line 10</em> we call <code>e.AcceptSocket</code> which returns a socket that we can use to send and receive data with the client.</p><p><em>Lines 18-29</em> handle the receiving, and echoing, of the client data. In this <code>do...while</code> block we&apos;re looping until we haven&apos;t received anymore data from the client and we&apos;re receiving it in chunks up to our <code>buffer</code> size and then immediately sending it back to the client using <code>SendAsync</code>.</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Admittedly my first solution was to receive all the data first and then send all of it back. But this means I would need to keep the entire contents of the data in memory (potentially larger than my buffer size too) until I sent it back. Instead we can just immediately send back the same received data.</div></div><p>Notice that <code>ReceiveAsync</code> returns an <code>int</code> which is the size of the received data. Since we might receive less than our buffer size when we send our data back we use <code>buffer[..received]</code> (using C# 8&apos;s range feature) which also allows us to simply reuse the buffer instead of instantiating a new one.</p><p>Finally once we have no more data from our client we can disconnect and close the client socket. Our last bit of cleanup is to set the <code>e.AcceptSocket</code> to <code>null</code> which allows that socket to be reused.</p><p>And that&apos;s the entirety of the code. Once you have your service up and running you just have to test it using the automated tests on the <a href="https://protohackers.com/problem/0">Protohackers Problem 0</a> page and you should see a pass:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.denny.dev/content/images/2023/01/proto-0-pass.png" class="kg-image" alt="Protohackers Solving Problem 0 with C#" loading="lazy" width="760" height="296" srcset="https://blog.denny.dev/content/images/size/w600/2023/01/proto-0-pass.png 600w, https://blog.denny.dev/content/images/2023/01/proto-0-pass.png 760w" sizes="(min-width: 720px) 720px"><figcaption>Protohackers Challenge 0 Pass Screen</figcaption></figure><h2 id="review">Review</h2><p>In this post we were introduced to the Protohackers Problem, learned how to build our IPv4 Endpoint and bind a listener to it. We learned how to accept incoming connections and finally we saw how we can buffer incoming data and echo it back to the client. </p><p>You can find the complete source code on <a href="https://github.com/dennyferra/ProtoHackers/blob/master/ProtoHackers/Program.cs">GitHub</a>.</p><p>Good luck! Have fun! Keep Hacking! And if you&apos;ve found any issues or better ways to handle this feel free to reach out!</p>]]></content:encoded></item></channel></rss>