Networking and the state of NetMF 4.4

@ Bill Gates - Truly touched - thanks boss.

1 Like

@ mcalsyn - sorry for my ignorance. But does this code keep kill the thread on hanging accept?

@ mcalsyn - I am humbled by your help on a Friday evening and have tested your code but for me it has so far shown the same symptoms and hung no the Socket.Accept() call after no more than a couple of minutes. I have run it 3 times and with the same result every time.

Sorry for the waithandle code being wrong, it was a cut and paste error.

Spawning the socket processing code on a new thread is something I have tried many times (including from my threadpool implementation) so I don’t see a big change there but the suggestion of limiting the backlog to 1 was something I hadn’t tried and I had a moment of real optimism.

It might be an environmental issue my end, and I will do all I can to try and rule that out.

Thanks,

Steve

@ njbuch - No, I didn’t add anything like that. I am just spawning a new thread for each request. In reality, that should be a bounded thread pool to avoid vulnerability to accidental flooding or purposeful denial-of-service attack. I also don’t think that this was required for the fix. The listen depth seemed to make the difference (and how listen depth gets handled is very implementation-specific, so I can’t claim to know why it mattered).

I didn’t add anything to handle hung accepts because a) I never saw one, and b) there’s sufficient code there for exceptions which result in closing the socket. I aimed for ‘working’ (fixing the reported problem) and not ‘ship-ready’ (which would involve a lot more bullet-proofing). I also wanted to make as few changes as possible so that the OP could apply this back to their solution without having to guess which changes mattered.

It is also interesting to note that in using Chrome as a test framework, the code is actually handling two requests in rapid succession on each refresh - one for the content and one for the favicon.

@ sh - That’s a shame, because I was able to repro your problem, and then made a change that seemed to fix it, so I’m not sure where to go from there. I am happy to help further if you want to bounce another code sample or anything else off of us (the community).

Do be sure to run FezConfig and make sure your firmware is up to date. My environment is TinyCLR version 4.3.7.10, TinyBooter 4.3.7.7 on a CerbuinoNet, which I know is not exactly your setup. I am using an ENC but not the same processor as you. I can switch to the same processor, but I don’t have a Gadgeteer ENC right now that I can move to that board so I would be using a J11D built-in ethernet adapter instead. Both of those factors might matter in some way, but I thought not given that I had repro’d your original problem. Perhaps someone else can step in and grab my code and give it a whirl.

The hosting code may also matter, so make sure you are doing something like this :

using System;
using GHI.Networking;
using GHI.Pins;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.Threading;
using Microsoft.SPOT.Net.NetworkInformation;

namespace Topic22141
{
    public class Program
    {
        public static void Main()
        {
            var netif = new EthernetENC28J60(SPI.SPI_module.SPI1, Generic.GetPin('A', 13), Generic.GetPin('A', 14), Generic.GetPin('B', 10));
            netif.Open();
            netif.EnableDhcp();
            netif.EnableDynamicDns();

            while (netif.IPAddress == "0.0.0.0")
            {
                Thread.Sleep(300);
            }

            var listener = new SampleTCPListener();
            listener.Start(31075);

            Thread.Sleep(Timeout.Infinite);
        }
    }
}

1 Like

I have wireshark running, would you mind helping to understand what your thinking is. You are asking me to check if requests / response are duplicated before the device dies ?

Sorry if I have misunderstood

@ mcalsyn - I was very hopeful too after finding something new to try in the Backlog number. There are some other discrepancies as you say my original code crashed after exactly the backlog number of requests whereas I have seen no similar issues.

I am TinyCLR version 4.3.7.10 but I think the TinyBooter is not up-to-date - is that likely to be an issue ? It’s a harder process updating this on the Raptor from what I read and I need to do some ‘wiring’ to get the pins connected to enter the right boot mode.

Other differences are that I am using static addressing (because I wanted the simplest possible network) and right now the ENC module is being instantiated by Gadgeteer code rather than NetMF but it looks like a pretty thin wrapper around the EthernetENC28J60 NetMF driver.

I am happy to change this if it might prove beneficial but I doubt the pins are wrong. Originally I did this with a copy of the EthernetENC28 code so I could modify the SPI clock speed to see if it made any difference. It’s set to 4000 in the GHI Gadgeteer driver.

Thanks again for your help.

Regards,

Steve

We were not able to locate a contact with 12 day response, which I wasn’t expecting to find one.

I sent you a private message asking for a date and an email.

@ sh - I’m doubtful that any of those things (TinyBooter, pins, static addr, Gadgeteer wrapper, etc) are the source of the problem, but until I get my hands on an ENC module, I can’t get much closer to your hardware setup, so your best bet may be someone else here with a Raptor and ENC running a version of the code I posted with the host snippet adapted to the ENC.

@ Gus - Nothing in my Forum Inbox

Looks like Gary found you and he replied to this thread.

@ Gus - OK great thanks. I would like to get off to a good start with GHI, I wasn’t moaning about the consultancy services contact, I would have put more energy into it had these other issues not arisen.

I think I read you are on a break, so please lets put this thread on hold, and I hope for some more help from other quarters and I am still very hopeful of the NetMF 4.4 release solving a lot of issues when and if you guys get it done

Thanks

@ sh - we are always here ready to help. Meanwhile, we will look into providing a more detail example on creating a reliable networking setup. After all, IoT is the future.

@ Gus - At some point, it can be in private I would like to know about the state of NetMF on the G400. I can see the new Lwip integration will be very different (better in design) but appears to come with the need for a small RTOS running the network separately to the NetMF interpreter.

Clearly non-trivial but it would be great to know GHI’s plan around doing it or not ?

Everything is possible and directly related to your needed quantity and engineering budget. I know nothing about your needs, which is why the private discussion is needed.

I thought I would try to help out on testing this networking issue, I have several G400HDR and the ENC28 Ethernet module. Spent the best part of the morning on this.

Here is what I changed to connect the ENC28 to G400HDR socket X6. I believe it is correct. Would like it if someone could confirmed this though.



The next issue was that I never got an IP address using DHCP.
Using FEZ Config, I was finally able to set a static IP Address, the check box to use DHCP, seems to enable it's self if you allow the G400-D to reboot after changing the settings. Even on exiting FEZ Config, choose the option not to reboot.
I commented out the netif.EnableDHCP statement.

Also noticed that properties like netif.CableConnected often shows False, The netif.NetworkAvailable always shows False.

The last issue I am having is the _socket.Bind statement throws and SocketException = 1022 which is WSAEINVAL - Invalid argument. Yet the Endpoint seems to have valid parameters.

If I put a do forever loop after the check for an IP address, I cannot ping the module, would have thought this should have worked. But this may not be supported.

My second copy of the hardware is at work, so this is about as far I can go for now.

Turns out that if you define the EndPoint as it’s own statement that resolves the Bind statement throwing an exception.


try
{
     EndPoint endPoint = new IPEndPoint(_interfaceAddress, servicePort);
     ...
      _socket.Bind(endPoint);

Now the Accept() statement is throwing an SocketException ErrorCode 10050 WSAENETDOWN Network is down.

Looks like I have to see if I can get the initialization code for the network working.

Hi,
I tested @ mcalsyn’s code on the Raptor with ENC28 Module with minor modifications
(added an http-header to the response and used port 80)
I used my little test http-Client from:
https://www.ghielectronics.com/community/codeshare/entry/927
it run flawlessly over night with more than 143.000 GET Requests and with up to 4 Clients sending requests running at the same time.


// This is a modification of the webserver of @ mcalsyn, GHI Community forum
// NETMF 4.3, GHI-SDK 2015 R1 
// -https://www.ghielectronics.com/community/forum/topic?id=22141&page=5
using System;
using Microsoft.SPOT;
using GHI.Networking;
using GHI.Pins;
using Microsoft.SPOT.Hardware;
using System.Threading;
using Microsoft.SPOT.Net.NetworkInformation;

namespace MCalsyn_Raptor_WebServer
{
    public class Program
    {

        public static void Main()
        {
            /* for Socket 1 Raptor Mainboard
            var netif = new GHI.Networking.EthernetENC28J60(Microsoft.SPOT.Hardware.SPI.SPI_module.SPI2
                                                        , GHI.Pins.FEZRaptor.Socket1.Pin6
                                                        , GHI.Pins.FEZRaptor.Socket1.Pin3
                                                        , GHI.Pins.FEZRaptor.Socket1.Pin4);
            */
            // for Socket 3 Raptor Mainboard
            var netif = new GHI.Networking.EthernetENC28J60(Microsoft.SPOT.Hardware.SPI.SPI_module.SPI1
                                                        , GHI.Pins.FEZRaptor.Socket3.Pin6
                                                        , GHI.Pins.FEZRaptor.Socket3.Pin3
                                                        , GHI.Pins.FEZRaptor.Socket3.Pin4);

            netif.Open();
            netif.EnableDhcp();
            netif.EnableDynamicDns();

            while (netif.IPAddress == "0.0.0.0")
            {
                Thread.Sleep(300);
            }
            Debug.Print("Got IP-Address by DHCP: " + netif.IPAddress.ToString());

            var listener = new SampleTCPListener();
            //listener.Start(31075);
            listener.Start(80);

            Thread.Sleep(Timeout.Infinite);
        }
    }
}


using System;
using Microsoft.SPOT;
using System.Threading;
using System.Net.Sockets;
using System.Net;
using System.Text;

namespace MCalsyn_Raptor_WebServer
{
    public class SampleTCPListener
    {
        private AutoResetEvent _listenerStarted;
        internal Socket _socket;
        internal Thread _thread;
        private IPAddress _interfaceAddress = IPAddress.Any;
        private int _receiveTimeout = -1;
        private int _sendTimeout = -1;
        //private int _listenBacklog = 10;
        private bool _isActive = false;

        const int BufferSize = 1460;
        const int StringLength = 1 * 1024;

        public bool Start(int servicePort)
        {
            try
            {
                //BUG: This line referred to a non-existent var and _listenerStarted was never
                //     assigned, so I presume this is what you meant.
                _listenerStarted = new AutoResetEvent(false);

                _interfaceAddress = System.Net.IPAddress.GetDefaultLocalAddress();

                _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                _socket.Bind(new IPEndPoint(_interfaceAddress, servicePort));
                _socket.ReceiveTimeout = _receiveTimeout;
                _socket.SendTimeout = _sendTimeout;

                //BUG: Listen with a backlog of n where n > 1 hangs after n requests
                _socket.Listen(1); // _listenBacklog);

                _isActive = true;
                _thread = new Thread(StartListen);
                _thread.Start();

                _listenerStarted.WaitOne();
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        public bool Stop()
        {
            try
            {
                _isActive = false;
                _socket.Close();
            }
            catch (Exception)
            {
                return false;
            }

            return true;
        }

        private void StartListen()
        {
            //Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;

            _listenerStarted.Set();
            while (_isActive)
            {
                var clientSocket = _socket.Accept();

                //CHANGE: new thread for each request (a thread pool would be better)
                new Thread(() =>
                {
                    try
                    {
                        OnSocket(clientSocket);
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }
                }).Start();
            }

            _socket.Close();
        }

        protected virtual void OnSocket(Socket socket)
        {
            try
            {
                if (socket.Poll(-1, SelectMode.SelectRead))
                {
                    EndPoint remoteEndPoint = new IPEndPoint(0, 0);

                    if (socket.Available == 0)
                        return;

                    // Change by RoSchmi : Add an html header to the message
                    // string stringDocument = "<html><head></head><body>HELLO world littlething</body></html>";               // send html page with other messgage
                    // string stringDocument = "<html><head></head><body>" + RandomResponse(StringLength) + "</body></html>";  // send an html page
                    string stringDocument = RandomResponse(StringLength);
                     //Edit: Header additionally with Connection:close and Cache-Control:no-cache
                    string header = "HTTP/1.1 200 OK\r\nContent-type: text/html\r\nConnection:close\r\n
                    Cache-Control:no-cache\r\nContent-Length:" + stringDocument.Length + "\r\n\r\n";
                    //string header = "HTTP/1.1 200 OK\r\nContent-type: text/html\r\nContent-Length:" + stringDocument.Length + "\r\n\r\n";
                    string headerAndDocument = header + stringDocument;
                    byte[] response = System.Text.Encoding.UTF8.GetBytes(headerAndDocument);

                    //int numBytesToWrite = StringLength;   // original version
                    int numBytesToWrite = headerAndDocument.Length;

                    //*************************************************************
                    int offset = 0;
                    do
                    {
                        var sendSize = (numBytesToWrite <= BufferSize) ? numBytesToWrite : BufferSize;

                        var bytesSent = socket.Send(response, offset, sendSize, SocketFlags.None);
                        numBytesToWrite -= bytesSent;
                        offset += bytesSent;

                    } while (numBytesToWrite > 0);
                }
            }
            catch (SocketException ex)
            {
                if (ex.ErrorCode == (int)SocketError.ConnectionReset)
                    return;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                socket.Close();
            }
        }
        // Minor changes to return a string instead of a Char array
        //private char[] RandomResponse(int length) // origninal version
        private string RandomResponse(int length)
        {
            var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            var stringChars = new char[length];
            var random = new Random();
            for (int i = 0; i < stringChars.Length; i++)
            {
                stringChars[i] = chars[random.Next(chars.Length)];
            }
            chars = new string(stringChars);
            //return stringChars;   // original version
            return chars;
        }
    }
}

3 Likes

@ RoSchmi - Well done.

@ mcalsyn - are you test still running?

@ RoSchmi - Nicely done indeed. In all fairness to @ sh, it was 99.99% @ sh’s code, and I purposely omitted refinements like fixing the return headers, just so that the changes boiled down to just what was essential to fix the hang. That said, these are all excellent improvements and I’m happy to hear that it is running well on a mirror of @ sh’s hardware.

@ njbuch - running like a champ.

EDIT: @ RoSchmi, can you confirm your bootloader and tinyclr versions so that @ sh can compare to his?

1 Like