Socket Bind Help

I’m running into an issue when creating a new “listener” for a very basic web server. I have included a screenshot of the bind exception and sample code to recreate the error. The behavior that I find strange and makes me think this might be a bug is that I can create a new listener object the destroy it over and over again (no problem at all). However, once the listener/socket processes some type of traffic I can never bind to it again without getting an InvalidOperationException. A few things I have tried to fix the issue with no success including:

  • When, what order and at what point I bind to the listener in the creation process.
  • Tried using both ethernet and wifi connection.
  • Assigning a specific endpoint instead of IPAddress.Any.
  • Timing delays
  • Using a Fez Duino board too

To run this example you will need to launch a web browser and send at least one request to the server on port 80. The bind will only fail once traffic has been previously processed. Binding without any traffic being process will work without issue and behaves as expected.

Any thoughts you might have would be apricated!

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Diagnostics;

using GHIElectronics.TinyCLR.Pins;
using GHIElectronics.TinyCLR.Devices.Network;

namespace Bytewizer.TinyCLR.WebServer
{
    class Program
    {
        static void Main()
        {
            // Initialize SC2026D development board ethernet
            InitializeEthernet();

            var server = new HttpServer();

            server.Start();
            server.Stop();
            server.Start();
        }

        private static void InitializeEthernet()
        {
            var networkController = NetworkController.FromName(SC20260.NetworkController.EthernetEmac);

            var networkInterfaceSetting = new EthernetNetworkInterfaceSettings
            {
                MacAddress = new byte[] { 0x00, 0x8D, 0xA4, 0x49, 0xCD, 0xBD },
                IsDhcpEnabled = true,
                IsDynamicDnsEnabled = true
            };

            networkController.SetInterfaceSettings(networkInterfaceSetting);
            networkController.NetworkAddressChanged += NetworkAddressChanged;
            networkController.SetAsDefaultController();
            
            networkController.Enable();
        }

        private static void NetworkAddressChanged(
            NetworkController sender,
            NetworkAddressChangedEventArgs e)
        {
            var ipProperties = sender.GetIPProperties();
            var address = ipProperties.Address.GetAddressBytes();

            if (address != null && address[0] != 0 && address.Length > 0)
            {
                Debug.WriteLine($"Lauch web brower on: http://{ipProperties.Address}");
            }
        }
    }

    public class HttpServer
    {
        private Thread _thread;
        private Socket _listener;

        private bool _active = false;
        private readonly ManualResetEvent _acceptEvent = new ManualResetEvent(false);
        private readonly ManualResetEvent _startedEvent = new ManualResetEvent(false);

        public void Start()
        {
            // Don't return until thread that calls Accept is ready to listen
            _startedEvent.Reset();

            // create the socket listener
            _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);

            // bind the listening socket to the port
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 80);
            _listener.Bind(endPoint);

            // start listening
            _listener.Listen(5);

            _thread = new Thread(() =>
            {
                _active = true;
                AcceptConnections();
            });
            _thread.Priority = ThreadPriority.AboveNormal;
            _thread.Start();

            // Waits for thread that calls Accept() to start
            _startedEvent.WaitOne();

            Debug.WriteLine($"Started socket listener");
        }

        public void Stop()
        {
            _active = false;

            // Signal the accept thread to continue
            _acceptEvent.Set();

            // Wait for thread to exit 
            _thread.Join();
            _thread = null;

            _listener.Close();
            _listener = null;

            Debug.WriteLine("Stopped socket listener");
        }

        private void AcceptConnections()
        {
            // Set the started event to signaled state
            _startedEvent.Set();

            while (_active)
            {
                // Set the accept event to nonsignaled state
                _acceptEvent.Reset();

                Debug.WriteLine("Waiting for a connection...");
                using (var remoteSocket = _listener.Accept())
                {
                    // Set the accept event to signaled state
                    _acceptEvent.Set();

                    // Send response to client
                    Response(remoteSocket);

                    // Close connection
                    remoteSocket.Close();
                }

                // Wait until a connection is made before continuing
                _acceptEvent.WaitOne();
            }

            Debug.WriteLine("Exited AcceptConnection()");
        }

        private void Response(Socket socket)
        {
            using (var network = new NetworkStream(socket))
            {
                using (var reader = new StreamReader(network))
                {
                    while (reader.Peek() != -1)
                    {
                        var line = reader.ReadLine();
                        Debug.WriteLine(line);
                    }

                    string response = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\nConnection: close\r\n\r\n" +
                                         "<doctype !html><html><head><title>Hello, world!</title>" +
                                         "<style>body { background-color: #111 } h1 { font-size:2cm; text-align: center; color: white;}</style></head>" +
                                         "<body><h1>" + DateTime.Now.Ticks.ToString() + "</h1></body></html>";

                    var bytes = Encoding.UTF8.GetBytes(response);
                    network.Write(bytes, 0, bytes.Length);
                }
            }
        }
    }
}

Why not use the HttpListener? I find this to be the simplest when creating a webserver.

This code is just being used as an example. This issue only happens when you actually process data using the socket so I’m using a web browsers to demonstrate the issue made it simple. I’m actually creating a socket service that supports an extendable pipeline. The full project is here:

https://github.com/microcompiler/microserver

Makes sense. Thanks for the clarification.

After reading the C# documentation and this article here, I believe it has to do with the fact that you aren’t creating a ‘bindable’ endpoint as you aren’t giving it a specific IP Address. Try instead of IPAddress.Any, try 127.0.0.1 (local IP). I don’t know if it will work on TinyCLR, but when I set up a listening port on a traditional server that’s what i used.

Thank you @sgtyar95 for the suggestion but unfortunately this didn’t help. The socket completely refuses the connection when using a local loopback IP. The only two address that will work are the assigned IP address to the interface and IPAddress.Any. TinyCLR dose not appear to have a local loopback address.

Possible. If you like, please create an issue on GitHub with and example to test on our end

Since the local loopback address can only be used for intra device connections, and has minimal use in embedded systems, it makes sense not to implement.

I would agree I don’t see the need for a local loopback address. At this point I’m willing to try anything to work around the exception. I will get a GitHub case opened up with a working example.