Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 19 of 19

Thread: Ethernet library socket issues

  1. #1
    Senior Member
    Join Date
    Nov 2012
    Location
    Salt Lake City, UT, USA
    Posts
    276

    Ethernet library socket issues

    This is a new thread, carrying on the Ethernet topic started there which isn't really a good fit since that topic was about compiler optimizations.

    I'm forking Paul's Ethernet library, to try adding some debugging help there. My fork is here. In the process of looking at that, low and behold, found @hvta reporting the same issue. Separately, @drmartin is dealing with the same topic.

    For the record, I am testing with Teensy 3.2, WIZnet 850io, Arduino 1.8.2 and TD 1.36. Sample code etc will be in my W5500_Test repo.

  2. #2
    Senior Member
    Join Date
    Nov 2012
    Location
    Salt Lake City, UT, USA
    Posts
    276

    Where is EthernetClient::print() source code?

    Where is EthernetClient print() source code? I can't find it anywhere in Paul's Ethernet library. It's not in EthernetClient.cpp, or Ethernet.cpp. I'm curious what EthernetClient print() and println() actually do, and how I can flush any pending data.
    Last edited by bboyes; 07-19-2017 at 10:41 PM.

  3. #3
    Senior Member
    Join Date
    Nov 2012
    Location
    Salt Lake City, UT, USA
    Posts
    276

    Zombie Sockets with W5500/WIZ850io

    Thanks to @drmartin for his suggestions in this thread... I have some socket status code in a fork of Paul's Ethernet library here, on a simple TempServer application. I'm seeing some strange socket behavior. There should be one socket listening and another establishing a connection, then getting closed. That's what I see. For a while. This is a single-threaded application, so incoming connection requests can only get handled one at a time. Yet, what happens is that multiple sockets (of the four available, less the one listening, so only a "pool" or shall we say "puddle" of three) wind up in "established" status. Meaning they are not available for use when a new request needs a connection. You can see this in the detailed logs which I'm tracking as a means to try to understand what's wrong and how to fix it.

    In the forked Ethernet library (github link above) socket.cpp, EthernetClass::socketBegin, I have this code compiled by changing to an "#if 1" from "#if 0":
    Code:
    #if 1
    	Serial.printf("W5000socket step3\n");
    	// next, use any that are effectively closed
    	for (s=0; s < MAX_SOCK_NUM; s++) {
    		uint8_t stat = status[s];
    		// TODO: this also needs to check if no more data
    		if (stat == SnSR::CLOSE_WAIT) goto closemakesocket;
    	}
    #endif
    and in the logs you can see this step3 getting executed, sometimes many times, before there is actually a socket available. This raises a lot of questions.
    For one: where and how do I check to see if there is data which needs to be flushed? EthernetClient has a non-implemented flush function:
    Code:
    void EthernetClient::flush()
    {
    	// TODO: Wait for transmission to complete
    }
    There are a lot of missing pieces in the Ethernet library, going back years of rejected pull requests. For one thing, the provided examples I've tried fail to run for long, and don't even pass the W3 validator. There are few helpful source code comments, documentation is incomplete and sometimes incorrect, and there's little to help you debug quiet failures. There's no good way for mere mortals to contribute to the official Arduino community and remedy these. What is needed is something like @nox771's i2c_t3 library which makes up for deficiencies in Wire. I'm going to attempt to do that since I have no other choice. Is there some other Ethernet library improvement effort going on already? I know Paul has put a lot of work into the existing library to add W5500/WIZ850io support, among other things.

    W5500 Ethernet Resources
    PJRC Ethernet library
    There is the Arduino.cc library here
    Adafruit Ethernet 2 (not backwards compatible to W5100) which I have used with M0 Pro and WIZ W5500 shield. This Adafruit library is based on WIZnet code of Soohwan Kim which has not seen any change in 2+ years.
    Arduino.org Ethernet2 library which now seems to have the same source repo as arduino.cc, specifically Ethernet same as Arduino.cc above so maybe the .cc and .org are finally merged back together?
    WIZnet W5500 Forum
    WIZnet W5500 page with links to documents and code

    With the simple changes so far, TempServer has now run for 151998 seconds (42 hrs) and 148675 http requests and recovered multiple times from no available sockets. Previously it would hang after at most a few hours, sometimes a few minutes.

    There's a lot I need to learn about how sockets are supposed to work. I do have the Comer/Stevens canonical text which I'm working through.

    I like @nox771's approach of a struct per I2C "wire" instance. I may try a similar approach of a struct per W5500 socket.

    Why is the library still not using all 8 possible sockets in the W5500? W5200 also had 8. There must be some reason this has not been implemented yet. My application ultimately wants a UDP socket to NTP, plus a listener and connection for TCP, leaving just one spare. That's enough for now, especially if the Zombie Socket issue can get fixed. But if I want to add a Telnet connection too, then it's full, and anything else new would be out of luck.
    Last edited by bboyes; 07-19-2017 at 10:46 PM.

  4. #4
    Senior Member+ manitou's Avatar
    Join Date
    Jan 2013
    Posts
    2,231
    Paul may have chosen to support only 4 sockets, so that larger socket buffers can be used. You could try changing to 8 sockets, but that may only delay the problem from appearing.

    The TCP deficiencies may actually be in the wiznet chip and not in the Ethernet lib.

    Is your web server accessible from the big Internet or just available on your local wire ? Bad things can happen to web servers on the wide-area Internet. Does your log show what IP's are connecting to your server? Any strange clients connecting?

    you may have to manually close/clear the busy wiznet sockets by writing in to the wiznet chip memory .... like closeGhostSocket() in
    https://github.com/PaulStoffregen/Ethernet/issues/4
    Last edited by manitou; 07-19-2017 at 10:49 PM.

  5. #5
    Senior Member
    Join Date
    Nov 2012
    Location
    Salt Lake City, UT, USA
    Posts
    276

    EthernetServer::available()

    @manitou: Yes it is on the Big Internet via DDNS and we do have a firewall. This is still a little dangerous, but what's life without some risk? I'm adding ability to see the remote IP address so I can detect those bots from China. I do have a "no robots" line in the header. There's not a lot of damage someone could do since the code in the W5500 can't be hacked, AFAIK.

    The TCP deficiencies may actually be in the wiznet chip and not in the Ethernet lib.
    Yeah, that would be sad. That's why I am reading the Comer book to see how it all is *supposed* to work.
    you may have to manually close/clear the busy wiznet sockets by writing in to the wiznet chip memory ....
    Yes, working on functions to do that once I understand how the library works now. Which could take me a while.

    I'm still getting my head around concepts like how EthernetServer available() returns an EthernetClient object. Actually this may be part of the problem:
    Code:
    EthernetClient EthernetServer::available()
    {
    	accept();
    	for (uint8_t i=0; i < MAX_SOCK_NUM; i++) {
    		if (server_port[i] == _port) {
    			uint8_t stat = Ethernet.socketStatus(i);
    			if (stat == SnSR::ESTABLISHED || stat == SnSR::CLOSE_WAIT) {
    				if (Ethernet.socketRecvAvailable(i) > 0) {
    					EthernetClient client(i);
    					return client;
    				}
    			}
    		}
    	}
    	return EthernetClient();
    }
    If I understand this code (some comments would help immensely...) if you ask if a server is available, and there is either a socket to one established or a socket in wait_close mode, it will return a client object and let you continue using the wait_close mode socket. So when does that socket ever get properly closed, then? In my observation (see the logs) never. Or at least never until all sockets are gone and there is the normally-uncompiled code in EthernetClass::socketBegin() which attempts to close with
    Code:
    #if 1
    	Serial.printf("W5000socket step3\n");
    	// next, use any that are effectively closed
    	for (s=0; s < MAX_SOCK_NUM; s++) {
    		uint8_t stat = status[s];
    		// TODO: this also needs to check if no more data
    		if (stat == SnSR::CLOSE_WAIT) goto closemakesocket;
    	}
    #endif
    	SPI.endTransaction();
    	Serial.printf("All %u Sockets in use\r\n", MAX_SOCK_NUM);
    	return MAX_SOCK_NUM; // all sockets are in use
    closemakesocket:
    	Serial.printf("W5000socket close\n");
    	W5100.execCmdSn(s, Sock_CLOSE);

  6. #6
    Senior Member+ manitou's Avatar
    Join Date
    Jan 2013
    Posts
    2,231
    Quote Originally Posted by bboyes View Post
    @manitou: Yes it is on the Big Internet via DDNS and we do have a firewall. This is still a little dangerous, but what's life without some risk? I'm adding ability to see the remote IP address so I can detect those bots from China. I do have a "no robots" line in the header. There's not a lot of damage someone could do since the code in the W5500 can't be hacked, AFAIK.
    Well, hacking may not be an issue, but denial-of-service with SYN flooding or trolling looking for open TCP ports by just sending a SYN will leave your sockets in ESTABLISHED state.

    the CLOSE_WAIT logic may be that CLOSE_WAIT state is good enough to allow the socket to be re-used for a new client, given the server_port[i] matches the server's listening port.

  7. #7
    Senior Member
    Join Date
    Nov 2012
    Location
    Salt Lake City, UT, USA
    Posts
    276

    MAX_SOCK_NUM defined in multiple places, are 8 sockets even supported?

    I tried increasing MAX_SOCK_NUM to 8 in w5100.h but that causes multiple errors. It seems MAX_SOCK_NUM is defined in multiple places:
    1. Ethernet.h
    2. w5100.h

    Some library files include one and some the other. Urk.

    Ethernet.h does not include w5100.h.

    There should be one "master" include file for things like this, and all other files should include it. What should that file be?

    Also there is no documentation I can find about what may happen if 8 sockets are allowed. Is it even supported?

    Code:
    Compiling library "Ethernet"
    "D:\arduino-1.8.2-TD1v36\hardware\teensy/../tools/arm/bin/arm-none-eabi-g++" -c -O1 -g -Wall -ffunction-sections -fdata-sections -nostdlib -MMD -fno-exceptions -felide-constructors -std=gnu++0x -fno-rtti -mthumb -mcpu=cortex-m4 -fsingle-precision-constant -D__MK20DX256__ -DTEENSYDUINO=136 -DARDUINO=10802 -DF_CPU=48000000 -DUSB_SERIAL -DLAYOUT_US_ENGLISH "-ID:\arduino-1.8.2-TD1v36\hardware\teensy\avr\cores\teensy3" "-IC:\Users\BAB\Documents\code\Arduino\libraries\ILI9341_t3" "-IC:\Users\BAB\Documents\code\Arduino\libraries\XPT2046_Touchscreen" "-IC:\Users\BAB\Documents\code\Arduino\libraries\SPI" "-IC:\Users\BAB\Documents\code\Arduino\libraries\Ethernet" "-IC:\Users\BAB\Documents\code\Arduino\libraries\TeensyID" "-IC:\Users\BAB\Documents\code\Arduino\libraries\Systronix_TMP102" "-IC:\Users\BAB\Documents\code\Arduino\libraries\i2c_t3" "C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Dhcp.cpp" -o "C:\Users\BAB\AppData\Local\Temp\arduino_build_125511\libraries\Ethernet\Dhcp.cpp.o"
    In file included from C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Dhcp.cpp:6:0:
    
    C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\w5100.h:23:0: warning: "MAX_SOCK_NUM" redefined
    
     #define MAX_SOCK_NUM 8
    
     ^
    
    In file included from C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Dhcp.cpp:4:0:
    
    C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Ethernet.h:6:0: note: this is the location of the previous definition
    
     #define MAX_SOCK_NUM 4
    
     ^
    
    "D:\arduino-1.8.2-TD1v36\hardware\teensy/../tools/arm/bin/arm-none-eabi-g++" -c -O1 -g -Wall -ffunction-sections -fdata-sections -nostdlib -MMD -fno-exceptions -felide-constructors -std=gnu++0x -fno-rtti -mthumb -mcpu=cortex-m4 -fsingle-precision-constant -D__MK20DX256__ -DTEENSYDUINO=136 -DARDUINO=10802 -DF_CPU=48000000 -DUSB_SERIAL -DLAYOUT_US_ENGLISH "-ID:\arduino-1.8.2-TD1v36\hardware\teensy\avr\cores\teensy3" "-IC:\Users\BAB\Documents\code\Arduino\libraries\ILI9341_t3" "-IC:\Users\BAB\Documents\code\Arduino\libraries\XPT2046_Touchscreen" "-IC:\Users\BAB\Documents\code\Arduino\libraries\SPI" "-IC:\Users\BAB\Documents\code\Arduino\libraries\Ethernet" "-IC:\Users\BAB\Documents\code\Arduino\libraries\TeensyID" "-IC:\Users\BAB\Documents\code\Arduino\libraries\Systronix_TMP102" "-IC:\Users\BAB\Documents\code\Arduino\libraries\i2c_t3" "C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Dns.cpp" -o "C:\Users\BAB\AppData\Local\Temp\arduino_build_125511\libraries\Ethernet\Dns.cpp.o"
    In file included from C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Dns.cpp:7:0:
    
    C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\w5100.h:23:0: warning: "MAX_SOCK_NUM" redefined
    
     #define MAX_SOCK_NUM 8
    
     ^
    
    In file included from C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Dns.cpp:5:0:
    
    C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Ethernet.h:6:0: note: this is the location of the previous definition
    
     #define MAX_SOCK_NUM 4
    
     ^
    
    "D:\arduino-1.8.2-TD1v36\hardware\teensy/../tools/arm/bin/arm-none-eabi-g++" -c -O1 -g -Wall -ffunction-sections -fdata-sections -nostdlib -MMD -fno-exceptions -felide-constructors -std=gnu++0x -fno-rtti -mthumb -mcpu=cortex-m4 -fsingle-precision-constant -D__MK20DX256__ -DTEENSYDUINO=136 -DARDUINO=10802 -DF_CPU=48000000 -DUSB_SERIAL -DLAYOUT_US_ENGLISH "-ID:\arduino-1.8.2-TD1v36\hardware\teensy\avr\cores\teensy3" "-IC:\Users\BAB\Documents\code\Arduino\libraries\ILI9341_t3" "-IC:\Users\BAB\Documents\code\Arduino\libraries\XPT2046_Touchscreen" "-IC:\Users\BAB\Documents\code\Arduino\libraries\SPI" "-IC:\Users\BAB\Documents\code\Arduino\libraries\Ethernet" "-IC:\Users\BAB\Documents\code\Arduino\libraries\TeensyID" "-IC:\Users\BAB\Documents\code\Arduino\libraries\Systronix_TMP102" "-IC:\Users\BAB\Documents\code\Arduino\libraries\i2c_t3" "C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Ethernet.cpp" -o "C:\Users\BAB\AppData\Local\Temp\arduino_build_125511\libraries\Ethernet\Ethernet.cpp.o"
    In file included from C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Ethernet.cpp:2:0:
    
    C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\w5100.h:23:0: warning: "MAX_SOCK_NUM" redefined
    
     #define MAX_SOCK_NUM 8
    
     ^
    
    In file included from C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Ethernet.cpp:1:0:
    
    C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Ethernet.h:6:0: note: this is the location of the previous definition
    
     #define MAX_SOCK_NUM 4
    
     ^
    
    "D:\arduino-1.8.2-TD1v36\hardware\teensy/../tools/arm/bin/arm-none-eabi-g++" -c -O1 -g -Wall -ffunction-sections -fdata-sections -nostdlib -MMD -fno-exceptions -felide-constructors -std=gnu++0x -fno-rtti -mthumb -mcpu=cortex-m4 -fsingle-precision-constant -D__MK20DX256__ -DTEENSYDUINO=136 -DARDUINO=10802 -DF_CPU=48000000 -DUSB_SERIAL -DLAYOUT_US_ENGLISH "-ID:\arduino-1.8.2-TD1v36\hardware\teensy\avr\cores\teensy3" "-IC:\Users\BAB\Documents\code\Arduino\libraries\ILI9341_t3" "-IC:\Users\BAB\Documents\code\Arduino\libraries\XPT2046_Touchscreen" "-IC:\Users\BAB\Documents\code\Arduino\libraries\SPI" "-IC:\Users\BAB\Documents\code\Arduino\libraries\Ethernet" "-IC:\Users\BAB\Documents\code\Arduino\libraries\TeensyID" "-IC:\Users\BAB\Documents\code\Arduino\libraries\Systronix_TMP102" "-IC:\Users\BAB\Documents\code\Arduino\libraries\i2c_t3" "C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\EthernetClient.cpp" -o "C:\Users\BAB\AppData\Local\Temp\arduino_build_125511\libraries\Ethernet\EthernetClient.cpp.o"
    In file included from C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\EthernetClient.cpp:3:0:
    
    C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\w5100.h:23:0: warning: "MAX_SOCK_NUM" redefined
    
     #define MAX_SOCK_NUM 8
    
     ^
    
    In file included from C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\EthernetClient.cpp:1:0:
    
    C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Ethernet.h:6:0: note: this is the location of the previous definition
    
     #define MAX_SOCK_NUM 4
    
     ^
    
    "D:\arduino-1.8.2-TD1v36\hardware\teensy/../tools/arm/bin/arm-none-eabi-g++" -c -O1 -g -Wall -ffunction-sections -fdata-sections -nostdlib -MMD -fno-exceptions -felide-constructors -std=gnu++0x -fno-rtti -mthumb -mcpu=cortex-m4 -fsingle-precision-constant -D__MK20DX256__ -DTEENSYDUINO=136 -DARDUINO=10802 -DF_CPU=48000000 -DUSB_SERIAL -DLAYOUT_US_ENGLISH "-ID:\arduino-1.8.2-TD1v36\hardware\teensy\avr\cores\teensy3" "-IC:\Users\BAB\Documents\code\Arduino\libraries\ILI9341_t3" "-IC:\Users\BAB\Documents\code\Arduino\libraries\XPT2046_Touchscreen" "-IC:\Users\BAB\Documents\code\Arduino\libraries\SPI" "-IC:\Users\BAB\Documents\code\Arduino\libraries\Ethernet" "-IC:\Users\BAB\Documents\code\Arduino\libraries\TeensyID" "-IC:\Users\BAB\Documents\code\Arduino\libraries\Systronix_TMP102" "-IC:\Users\BAB\Documents\code\Arduino\libraries\i2c_t3" "C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\EthernetServer.cpp" -o "C:\Users\BAB\AppData\Local\Temp\arduino_build_125511\libraries\Ethernet\EthernetServer.cpp.o"
    In file included from C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\EthernetServer.cpp:2:0:
    
    C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\w5100.h:23:0: warning: "MAX_SOCK_NUM" redefined
    
     #define MAX_SOCK_NUM 8
    
     ^
    
    In file included from C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\EthernetServer.cpp:1:0:
    
    C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Ethernet.h:6:0: note: this is the location of the previous definition
    
     #define MAX_SOCK_NUM 4
    
     ^
    
    C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\EthernetServer.cpp:4:50: error: conflicting declaration 'uint16_t EthernetServer::server_port [8]'
    
     uint16_t EthernetServer::server_port[MAX_SOCK_NUM];
    
                                                      ^
    
    In file included from C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\EthernetServer.cpp:1:0:
    
    C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\Ethernet.h:202:18: note: previous declaration as 'uint16_t EthernetServer::server_port [4]'
    
      static uint16_t server_port[MAX_SOCK_NUM];
    
                      ^
    
    C:\Users\BAB\Documents\code\Arduino\libraries\Ethernet\EthernetServer.cpp:4:50: error: declaration of 'uint16_t EthernetServer::server_port [4]' outside of class is not definition [-fpermissive]
    
     uint16_t EthernetServer::server_port[MAX_SOCK_NUM];

  8. #8
    I have successfully used all eight sockets that the WINet hardware provides. My project was done about 18 months ago using the Libraries and the development tools of the day. It worked (and continues to) work well for my specific application. Since the changes were specific to the tools and Libraries of the time, I do not believe posting them today would be useful to you.

    In general, I just changed MAX_SOCK_NUM and then carefully walked through how that consent was used to make sure all the dependencies properly accounted for the change. As I recall, I had to correct the buffer size calculations and related mask bits as they were originally focused on a four socket solution.

    Since it was over 18 months ago, all the details are a little fuzzy but it just was a careful walk through the dependencies that got my project working.

    So - bottom line - it is possible to use all eight sockets but there are some trade offs involved as the available buffer RAM is limited.

  9. #9
    Senior Member
    Join Date
    Nov 2012
    Location
    Salt Lake City, UT, USA
    Posts
    276
    Quote Originally Posted by manitou View Post
    the CLOSE_WAIT logic may be that CLOSE_WAIT state is good enough to allow the socket to be re-used for a new client, given the server_port[i] matches the server's listening port.
    @manitou, if I understand Close-Wait, it only has an "open" connection going one way. In Comer's Volume 1, 6th Edition, "TCP/IP", page 236, the TCP State Machine, Close-Wait has only one possible next state: Last-Ack, which sends the FIN-Ack to the "other" end which originally sent the FIN to start the Close. When this end receives the Ack of the FIN-Ack, or has a 2-segment timeout, then the state goes to Closed where only then can the socket be possibly re-used. So there is no "reconnect" or reuse possible once one end of the connection has been sent a FIN. The sender of the FIN can't use the socket again, but the recipient of the FIN can continue to send traffic while in Close-Wait state, just to finish or clean up. Then the FIN recipient should send a FIN-Ack and finish Closing it's direction of the connection. The state machine diagram is way better at explaining this. (Sounds of googling for the diagram...) Actually here is the exact diagram.
    Last edited by bboyes; 08-05-2017 at 11:35 PM.

  10. #10
    Senior Member
    Join Date
    Nov 2012
    Location
    Salt Lake City, UT, USA
    Posts
    276
    Quote Originally Posted by bboyes View Post
    Where is EthernetClient print() source code? I can't find it anywhere in Paul's Ethernet library. It's not in EthernetClient.cpp, or Ethernet.cpp. I'm curious what EthernetClient print() and println() actually do, and how I can flush any pending data.
    Still have not located this. Any ideas? EthernetClient.h does not define any of the functions in EthernetClient.cpp - they are all mashed together in Ethernet.h! Why did they do it this way? It prevents nicely encapsulated modules. And makes it hard to find module code.

    Ethernet.h does not define
    Code:
    EthernetClient::print
    and I can't find it in a search of the entire Arduino/TD installation.
    Last edited by bboyes; 07-28-2017 at 04:57 PM. Reason: forum insists on turning ::print into an emoticon

  11. #11
    Senior Member
    Join Date
    Jan 2013
    Posts
    184
    Quote Originally Posted by bboyes View Post
    Still have not located this. Any ideas? EthernetClient.h does not define any of the functions in EthernetClient.cpp - they are all mashed together in Ethernet.h! Why did they do it this way? It prevents nicely encapsulated modules. And makes it hard to find module code.

    Ethernet.h does not define
    Code:
    EthernetClient::print
    and I can't find it in a search of the entire Arduino/TD installation.
    Name:  EthernetClient Class Diagram.png
Views: 943
Size:  5.8 KB

    The EthernetClient class inherits indirectly from the Print class. The print() member function is from the Print class. Look in files Print.h and Print.cpp.

  12. #12
    Senior Member
    Join Date
    Nov 2012
    Location
    Salt Lake City, UT, USA
    Posts
    276
    Update: here is a really detailed explanation of Print and Printable and what they mean, in the context of why you can Serial.print the type IPAddress and it comes out as XXX.XXX.XXX.XXX but Serial.printf of any format of IPAddress just gives a uint32_t value.

    @markonian: OK... how does a mere mortal figure this out? EthernetClient has nothing about inheritance. EthernetClient.h only #includes Ethernet.h, which declares
    [CODE]class EthernetClient : public Client
    <snip>
    using Print::write;/CODE]
    but nothing about Print:rint or Print::Println.
    In fact in Print::Println is only the void definition
    Code:
    size_t Print::println(void)
    {
    	uint8_t buf[2]={'\r', '\n'};
    	return write(buf, 2);
    }
    So elsewhere must exist some definition of println("something") as a non-void Print() followed by the void version which only outputs \r\n.

    It seems odd to me that Stream is not the base class for Print.

    In any case, back to the matter at hand. Tempserver seems to be failing in the response to client request, in this code:
    Code:
                        outcount = client.println("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">");
                        if (!outcount) Serial.println("Could not print to client"); else Serial.printf ("Sent %u\n", outcount);
                        outcount = client.println("<html>");
                        if (!outcount) Serial.println("Could not print to client");
                        outcount = client.println("<head>");
                        outcount = client.println("<title>Simple Temperature Server</title>");
                        outcount = client.println("</head>");
                        outcount = client.println("<body>");
    Detailed logs here at github
    The message "Sent XX" is the last output, and Teensy then locks up. WIZ850io CS is low and so is MISO. The final SPI message is truncated (more details in the log). Internal WIZ850io lockup? Why does Teensy just halt too?
    I'm adding more output along the way in the response so I might see more precisely where it fails.
    DOCTYPE now is HTML 2 and the response passes validation at http://www.htmlhelp.com/tools/validator/ and https://validator.w3.org/ although warnings with the latter.
    Last edited by bboyes; 08-05-2017 at 11:41 PM.

  13. #13
    Senior Member
    Join Date
    Nov 2012
    Location
    Salt Lake City, UT, USA
    Posts
    276

    Passive listening socket gets moved around... how? why?

    UPDATE 2017Aug05: After trolling around in the Ethernet2 EthernetServer class, it's clear how this happens. The listening socket gets used for the new Established connection, and part of server.available() involves opening another listener on a different socket, if necessary. I had mistakenly assumed the new connection request would be honored on a new socket: that might be possible, but more difficult than using the listening socket to honor the request for connection.

    A recent update to socket status, which includes the most recent remoteIP assigned to that socket, made this clear. See these logs if you want to know what that looks like.

    Another thing I don't understand about how this EthernetClient is supposed to work. Socket 0 is initially set as the passive listener, by EthernetServer.begin():
    Code:
    Start listening:
    W5000socket begin, protocol=1, port=8080
        found closed socket 0
        W5000 makesocket 0
        W5000socket prot=1, RX_RD=0
        Socket(0) SnSr = Listen SnMR = TCP
        Socket(1) SnSr = Closed SnMR = Close
        Socket(2) SnSr = Closed SnMR = Close
        Socket(3) SnSr = Closed SnMR = Close
    And I would expect this socket to always be the listener, since there is no explicit code to make it otherwise or to shuffle it around to other sockets.
    Then a request comes in, and the debug output I have added to socket.cpp does not make sense to me:
    Code:
    W5000socket begin, protocol=1, port=8080
        found closed socket 1
        W5000 makesocket 1
        W5000socket prot=1, RX_RD=0
    ..'.@ 31 sec, Got new client, Temp is 26.688 C
        Socket(0) SnSr = Establ SnMR = TCP
        Socket(1) SnSr = Listen SnMR = TCP
        Socket(2) SnSr = Closed SnMR = Close
        Socket(3) SnSr = Closed SnMR = Close
    The first message is correct: socket 1 is closed. But then what happens? Socket 0 gets set to the Established connection and socket 1 moves to Listening? How is this shuffling even possible?

    The socket status reporting is:
    Code:
    static const char *SnMr[] = {"Close", "TCP", "UDP", "IPRAW", "MACRAW"};
    char socStatus[8];			// 7 char plus null terminator
    /**
    	From drmartin 
    	@link https://forum.pjrc.com/threads/43572-Optimization-Fast-Faster-Fastest-with-without-LTO?p=148431#post148431
    	<a href="https://forum.pjrc.com/threads/43572-Optimization-Fast-Faster-Fastest-with-without-LTO?p=148431#post148431">PJRC Forum sockets discussion</a>
    
    	Print out status of all 8 sockets in WIZ850io. Only 4 are currently supported in Arduino Ethernet, but 8 are there.
    	@param num - how many sockets to report, [0] thru [n-1], n is a max of 8. Only 4 supported in Arduino.
    */
    void EthernetClass::getSocketStatus(uint8_t num)
    {
      for (uint8_t i = 0; i < num; i++) 
      {
        switch (socketStatus(i)) 
        {
          case 0x00:
            sprintf(socStatus, "Closed");
            break;
          case 0x14:
            sprintf(socStatus,"Listen");
            break;
          case 0x17:
           sprintf(socStatus, "Establ");
            break;
          case 0x1C:
          	sprintf(socStatus, "ClsWait");
          	break;
          case 0x22:
          	sprintf(socStatus, "Open");		// open in UDP mode
          	break;
          default:
            sprintf(socStatus, "0x%02X  ", socketStatus(i));
            break;
        }
    
        Serial.printf("    Socket(%d) SnSr = %s SnMR = %s\r\n", i, socStatus, SnMr[W5100.readSnMR(i)]);
      } // end of for loop
    }
    Last edited by bboyes; 08-06-2017 at 01:20 AM. Reason: add answer to my own question about listening socket moving as needed

  14. #14
    Senior Member
    Join Date
    Jan 2013
    Posts
    184
    Quote Originally Posted by bboyes View Post
    OK... how does a mere mortal figure this out? EthernetClient has nothing about inheritance. EthernetClient.h only #includes Ethernet.h, which declares
    [CODE]class EthernetClient : public Client
    <snip>
    using Print::write;/CODE]
    but nothing about Print:rint or Print::Println.
    In fact in Print::Println is only the void definition
    Code:
    size_t Print::println(void)
    {
    	uint8_t buf[2]={'\r', '\n'};
    	return write(buf, 2);
    }
    So elsewhere must exist some definition of println("something") as a non-void Print() followed by the void version which only outputs \r\n.

    It seems odd to me that Stream is not the base class for Print.
    Code:
    class EthernetClient : public Client 
    <snip>
    Indeed, EthernetClient declares that it publicly inherits from Client. If you track down Client, you'll find that it publicly inherits from Stream which publicly inherits from Print. Print has many overloaded versions of print() and printLn().

  15. #15
    Senior Member
    Join Date
    Nov 2012
    Location
    Salt Lake City, UT, USA
    Posts
    276

    Some success with Ethernet2 library

    So in somewhat a state of desperation, I have converted to using Ethernet2 library from Adafruit. This is actually originally written by WIZnet, modified in 2015 by Arduino.org team (not Arduino.cc) specifically for the W5500 and related modules. Only change in 2 years by Adafruit is version number. You'd hope WIZnet would be able to get this right, being the chip vendor and all, and it seems they have. I've previously tested this library on an Arduino.org M0Pro board with W5500 shield purchased direct from WIZnet, running DHCP Stress Test and it worked fine. Now I have run DHCPStressTest-T3 and NTP_clock_set as well as TempServer in the W5500_Test branch with_ethernet2.

    The Ethernet2 library has some major differences from Ethernet, though it is "compatible", at least superficially.
    1. Only supports W5500 chip. If you want one lib for W5100, W5200, W5500, this is not it.
    2. No timeout param in Ethernet constructor. This could be added, of course.
    3. 8 sockets supported as default. They are in the part along with buffer memory, so why not use them?
    4. socket.cpp does not contain the EthernetClass as does the Ethernet library. This never made sense to me anyway. But it does affect how you can use the library and where/how you add things such as socket status report.
    5. The approach of socket.cpp is very different from that taken in the Ethernet library. Also, zero "goto". It seems cleaner, simpler, and easier to follow, at least to me.
    6. Header files such as socket.h actually declare the contents of their matching .cpp files
    7. In w5500.h are declared classes for each of the basic register sets of W5500, mostly just declaring static const values. Not sure why a class was used for that.
    8. There are none of the optimizations for Teensy 3, but I am using it with PJRC SPI library (latest from github), so I'm getting the SPI optimizations for Teensy. I need reliability more than speed. Amounts of data I'm moving are relatively small and infrequent (every few seconds a few hundred bytes). But this is expected to work 24/7 forever without rebooting. The actual application (not in W5500_Test) has a watchdog timer as a last resort.
    9. Sockets appear to be handled more appropriately. So far I see no persistent zombie sockets in state 0x16 (SYN request) or Closed-wait state. Is this a library difference or the bad guys are not pounding on it just now?
    10. "Extra" Established sockets get cleaned up pretty quickly
    11. There is not always a socket in passive Listening mode. This seems curious. I thought server.begin() set a socket to be always listening, only needed to be done one time, in setup().
    12. Sockets are never reported in Closed-wait status. Interesting. Is this a library difference or the bad guys are not pounding on it just now?
    13. each client http request bumps up the http request counter by one. With Ethernet, each request bumps the counter by two. This always puzzled me. Here's the code which handles that counter, no change between Ethernet and Ethernet2 library Tempserver example code:
      Code:
              while (client.connected()) 
              {
                  if (client.available()) 
                  {
                      char c = client.read();
                      if (verbose) Serial.write(c);   // echo incoming request to serial monitor
      
                      // if you've gotten to the end of the line (received a newline
                      // character) and the line is blank, the http request has ended,
                      // so you can send a reply
                      // if (c == '\n') Serial.println ("newline");
                      if (c == '\n' && currentLineIsBlank) 
                      {
                          Serial.println("Request is complete");
                          // we could be here if we get a request consisting of one blank line, in theory
                          // send a standard http response header
                          Serial.println("Sending Response...");
                          http_request_count++;
      update: it turns out this is not true. There are still two requests per client connection, but why? The browser only reports one. But the serial monitor output shows one, and then, a second later, another, from different IP and port (from Chrome Android). Browser only reports the first connection. What's up with this? Is one the SYN request and the other the actual HTTP connection? I don't think so since the serial monitor says it is sending the whole http response both times. Anyone understand this?
      Code:
      .....@ 645 sec, Got new client, Temp is 27.500 C
      From 66.249.84.46, port 33138
          Socket(0) SnSr = Establ SnMR = TCP
          Socket(1) SnSr = Listen SnMR = TCP
          Socket(2) SnSr = Closed SnMR = TCP
          Socket(3) SnSr = Closed SnMR = TCP
          Socket(4) SnSr = Closed SnMR = TCP
          Socket(5) SnSr = Closed SnMR = Close
          Socket(6) SnSr = Closed SnMR = Close
          Socket(7) SnSr = Closed SnMR = Close
      Sending Response...
      done
          Socket(0) SnSr = Closed SnMR = TCP
          Socket(1) SnSr = Listen SnMR = TCP
          Socket(2) SnSr = Closed SnMR = TCP
          Socket(3) SnSr = Closed SnMR = TCP
          Socket(4) SnSr = Closed SnMR = TCP
          Socket(5) SnSr = Closed SnMR = Close
          Socket(6) SnSr = Closed SnMR = Close
          Socket(7) SnSr = Closed SnMR = Close
      --------
      
      @ 645 sec, Got new client, Temp is 27.500 C
      From 66.249.84.46, port 60420
          Socket(0) SnSr = Listen SnMR = TCP
          Socket(1) SnSr = Establ SnMR = TCP
          Socket(2) SnSr = Closed SnMR = TCP
          Socket(3) SnSr = Closed SnMR = TCP
          Socket(4) SnSr = Closed SnMR = TCP
          Socket(5) SnSr = Closed SnMR = Close
          Socket(6) SnSr = Closed SnMR = Close
          Socket(7) SnSr = Closed SnMR = Close
      Sending Response...
      done
          Socket(0) SnSr = Listen SnMR = TCP
          Socket(1) SnSr = Closed SnMR = TCP
          Socket(2) SnSr = Closed SnMR = TCP
          Socket(3) SnSr = Closed SnMR = TCP
          Socket(4) SnSr = Closed SnMR = TCP
          Socket(5) SnSr = Closed SnMR = Close
          Socket(6) SnSr = Closed SnMR = Close
          Socket(7) SnSr = Closed SnMR = Close
    14. There are more comments in the code, though many more would help. There is no other documentation source AFAIK. Maybe similar code at WIZnet? I don't know.
    15. So far, no WIZ850io or Teensy SPI lockups, but the amount of testing time is small (50,000 http requests so far).
    16. Still needs IP remote address support added. Working on that per this github commit to Arduino
    17. No library-wide exception handling. Functions return 0 for failure typically, but then what? Is cleanup or recovery code needed? An exercise left to the user. We can do that.


    My test code is kind of a hack since I'm mostly a hardware guy, and additions to socket.cpp are no longer part of EthernetClass. But I'll clean that up. I still like the idea of a struct instance per socket, which has all the data and status you need or want for that one socket.

    Here is a serial monitor output for a typical http request handling, starting with the close socket of the previous request.
    Code:
    client stopped
        Socket(0) SnSr = Closed SnMR = TCP
        Socket(1) SnSr = Listen SnMR = TCP
        Socket(2) SnSr = Closed SnMR = TCP
        Socket(3) SnSr = Closed SnMR = TCP
        Socket(4) SnSr = Closed SnMR = TCP
        Socket(5) SnSr = Closed SnMR = Close
        Socket(6) SnSr = Closed SnMR = Close
        Socket(7) SnSr = Closed SnMR = Close
    49012 http requests, 0.87 per sec, 0 timeouts
    86 sec max w/o client
    --------
    
    .'.@ 56421 sec, Got new client, Temp is 26.875 C
        Socket(0) SnSr = Listen SnMR = TCP
        Socket(1) SnSr = Establ SnMR = TCP
        Socket(2) SnSr = Closed SnMR = TCP
        Socket(3) SnSr = Closed SnMR = TCP
        Socket(4) SnSr = Closed SnMR = TCP
        Socket(5) SnSr = Closed SnMR = Close
        Socket(6) SnSr = Closed SnMR = Close
        Socket(7) SnSr = Closed SnMR = Close
    GET / HTTP/1.1
    Host: 192.168.1.10:8080
    Connection: keep-alive
    Cache-Control: max-age=0
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
    Referer: http://192.168.1.10:8080/
    Accept-Encoding: gzip, deflate
    Accept-Language: en-US,en;q=0.8
    
    Request is complete
    Sending Response...
        Socket(0) SnSr = Listen SnMR = TCP
        Socket(1) SnSr = Establ SnMR = TCP
        Socket(2) SnSr = Closed SnMR = TCP
        Socket(3) SnSr = Closed SnMR = TCP
        Socket(4) SnSr = Closed SnMR = TCP
        Socket(5) SnSr = Closed SnMR = Close
        Socket(6) SnSr = Closed SnMR = Close
        Socket(7) SnSr = Closed SnMR = Close
    Sent 48
    Sent head
    Sent body header and build
    Sent temperature
    Sent all of body
    done
    client stopped
        Socket(0) SnSr = Listen SnMR = TCP
        Socket(1) SnSr = Closed SnMR = TCP
        Socket(2) SnSr = Closed SnMR = TCP
        Socket(3) SnSr = Closed SnMR = TCP
        Socket(4) SnSr = Closed SnMR = TCP
        Socket(5) SnSr = Closed SnMR = Close
        Socket(6) SnSr = Closed SnMR = Close
        Socket(7) SnSr = Closed SnMR = Close
    49013 http requests, 0.87 per sec, 0 timeouts
    86 sec max w/o client
    --------
    Here, in the end of the previous request, socket 1 is listening. Then at the new request, the listener has changed to socket 0 (how does that happen). The new connection request gets handled by socket 1 which Establishes, then Closes, while socket 0 is always passively listening.

    I hope this saga is of interest to others...
    Last edited by bboyes; 08-04-2017 at 02:09 AM. Reason: clarification

  16. #16
    Senior Member
    Join Date
    Nov 2012
    Location
    Salt Lake City, UT, USA
    Posts
    276

    remoteIP() and remotePort() added to Ethernet2, also print socket status

    If anyone cares, I have added remoteIP() and remotePort() to Ethernet2 at my fork at github. You can see these show the remote IP and Port in the demo Tempserver, at this link. Also display status of one socket or n sockets. This is running with 8 active sockets on a WIZ850io.

  17. #17
    Senior Member
    Join Date
    Nov 2012
    Location
    Salt Lake City, UT, USA
    Posts
    276
    So here's something odd. Chrome and FF on Android 5.1 phone always result in two requests where there should be only one. The browser reports only the first request (judging by its report of remote IP and remote Port). My HTTP request count goes up by 2. Chrome on desktop does the same. The second request I can see in the Teensy serial monitor (see the logs), and it usually has a different IP address and always a different port.

    Here's the oddity: FF on desktop only issues one request, as expected.

    This is true whether access is over Internet or direct on local subnet.

    Anyone have any insight into this?

    It could be
    1. my server code is not interpreting a "request" properly. But why does it work properly with desktop FF?
    2. a library bug. But then it should be doing this even with FF desktop.
    3. browser anomaly.
    4. something else in the chain of http/TCP message handling. But why not with desktop FF?

  18. #18
    Quote Originally Posted by bboyes View Post
    Chrome and FF always result in two requests where there should be only one.

    Anyone have any insight into this?
    When I'm presented with puzzles like this, I tend to look at the raw data. Wireshark is a pretty quick and easy way to see all of the Ethernet packets between your computer and the server. It's output is pretty easy to understand and it gives insight to the exchanges you have dancing around for some time. Every SYN, ACK, FIN, and all the details are there for your instant inspection.

    In answer to your specific "why two requests" query...

    It appears that Chrome performs an HTTP GET to obtain the main page from your server. This is then immediately followed by a second HTTP GET in an attempt to obtain the "favicon.ico" graphic for the pretty graphic in Chrome's address bar.

    See a sample from your server below


    Code:
    GET / HTTP/1.1
    Host: systronix.hopto.org:8080
    Connection: keep-alive
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
    Accept-Encoding: gzip, deflate
    Accept-Language: en-US,en;q=0.8
    
    HTTP/1.1 200 OK
    Content-Type: text/html; charset=UTF-8[2 bytes missing in capture file].Connection: close
    Refresh: 5
    <meta name="robots" content="noindex" />
    
    <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
    <html>
    <head>
    <title>Simple Temperature Server</title>
    </head>
    <body>
    <h2>SALT TMP102 Temperature Server</h2>
    Build 18:15:24 Aug  5 2017<br>Updates approx every 5 seconds<br>@96245 sec: 23.38 deg C <br> You: xxx.xxx.xxx.xxx, port 61333<br>26625 http requests, 0.28 per sec. 0 timeouts, 7110 max sec w/o client<br></body>
    </html>
    
    -----------------------------------------------------------------------
    
    GET /favicon.ico HTTP/1.1
    Host: systronix.hopto.org:8080
    Connection: keep-alive
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
    Accept: image/webp,image/apng,image/*,*/*;q=0.8
    Referer: http://systronix.hopto.org:8080/
    Accept-Encoding: gzip, deflate
    Accept-Language: en-US,en;q=0.8
    
    Connection: close
    Refresh: 5
    <meta name="robots" content="noindex" />
    <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
    <html>
    <head>
    <title>Simple Temperature Server</title>
    </head>
    <body>
    <h2>SALT TMP102 Temperature Server</h2>
    Build 18:15:24 Aug  5 2017<br>Updates approx every 5 seconds<br>@96246 sec: 23.38 deg C <br> You: xxx.xxx.xxx.xxx, port 61335<br>26626 http requests, 0.28 per sec. 0 timeouts, 7110 max sec w/o client<br></body>
    </html>

  19. #19
    Senior Member
    Join Date
    Nov 2012
    Location
    Salt Lake City, UT, USA
    Posts
    276
    Doh! Thanks for that @drmartin. Seems obvious now. Can you tell I'm new to web programming? The simple code being used to answer requests doesn't parse out any specifics, or I would have seen this more clearly. I have used Wireshark in the past and am prepared to do so again. I would not call it an easy to learn tool but that's life. I did find this thread about serving favicon.ico from Arduino SD card.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •