Notes regarding behaviour of Arduino Wifi shield as a TCP client
Test Environment
Arduino Uno R3
Arduino IDE 1.0.5
Arduino WiFi Shield
WiFi.firmwareVersion() return V1.1.0
Test harness sketch source code appears below.
The (server) listening application is a simple Real Studio desktop application
implementing a single TCPSocket control, whcih is recycled on disconnect.
The behaviour of the listening application was verified using telnet.
Conclusion
These conclusions are based on reverse engineering techniques. The word 'apparently' is liberally inferred.
The WiFi Shield's IP stack is implemented on the H&D WiFi package and decoupled from the Arduino WIFi library, by the Shields MCU. Calling the WiFiClient.write and WiFiClient.println functions, causes the data to be passed across the SPI bus and written into a transmit buffer on the Shield. The Shield services the transmit buffer every two seconds, framing the data within packets of upto ~500 bytes, which are then placed on the wire.
Maximum throughput is unrelated to the speed at which data is written to the SPI bus by the Arduino host. Whether writing 1 byte or 1000 bytes, it will take ~2 seconds for the shield to service the transmit request, before data is recieved by the target host.
Maximum throughput is related to both the method used to write to the SPI bus and the size of the data being written.
The result of the SPI bottleneck in concert with the periodic servicing of transmit requests, causes maximum throughputs to be reached by Arduino applications framing data in 90 byte chunks and calling the WiFiClient.write() method as fast as possible. Exactly how the Shield subsequetly packetises the data is of little consquence, due to the relatively huge 2 Second delay within the Shield's transmit cycle.
Socket interaction is decoupled by the WiFi Library. WiFi.getSocket and WiFiClient.stop do not behave as one might intuitively expect. In concert with the 2 second latency within the transmit service cycle, this decoupling complicates graceful communication and particularly recovery from a dead peer scenario.
WiFiClient.Stop() does not close the connection by signalling the target host but rather, abandons the socket structure and any data associated with it. Calling WiFiClient.Stop() after writing to the Shield but before the transmit request is serviced, 2 seconds later, will cause the data to be abandoned. In any case, calling WiFiClient.stop() will leave the target with a dead peer connection.
Investigation of issues
uint8_t WiFi.getSocket();
Is documented as returning the next available socket number. The utility of the function would appear to be in checking against MAX_SOCKET_NUM to ascertain availability of a free socket to to listen or connect with. WiFi.getSocket() appears to always return 0, in which case there is no straightforward way to check socket availability.
See Output 1 below
WiFiClient.stop();
Is documented as disconnecing from the server. Presumably the function is supposed to close the TCP session and free the Socket. Calling stop does appear to free the socket, however the TCP session is not finalised.
The table below shows the TCP three way handshake. This is what should be expected to happen at packet level, on each iteration of the test harness loop().
What actually happens at packet level when WiFiClient stop is called is, nothing at all.
Calling WiFiClient.stop() does not send a FIN request. There is no way for the target socket to know the Shield has disposed of it's own socket. The target socket is left connected to a dead peer, with the socket erroneously remaining in the connected state.
On the next iteration of the Arduino loop(), the Shield sends a SYN packet but is responded to with a RST ACK (reset) and no connection is forthcoming. So on and so forth on each subsequent iteration until the target is reset. The fault is masked by client/server systems, where typically the server application manages a large pool of listening sockets.
To avoid exceeding MAX_SOCKET_NUM, the Shield automatically closes a connection which was previously stopped, during the call to WiFiClient.connect(). See WiFiShield Socket Management below.
Further implications
See Output 2 below
WiFi Shield TCP Socket Management
The WiFi Shield implements a pool of sockets with MAX_SOCKET_NUM == 4
WiFiClient.stop() frees up a socket structure on the Shield but does not send a FIN to the target
Sockets are closed by the Shield sending RST ACK to the target when calling WiFiClient.Connect, would otherwise cause MAX_SOCKET_NUM to be exceeded.
E.G.
for (i = 0; i < 6; i++) { if (WifiClient.Connect(ip. port) { WiFiClient.Stop(); } }
Arduino, Target, Target Socket State
Syn > Syn Ack > Connected //socket 1 Syn > Syn Ack > Connected //socket 2 Syn > Syn Ack > Connected //socket 3 Syn > Syn Ack > Connected //socket 4 Rst Ack > none > Closed //socket 1 Syn > Syn Ack > Connected //socket 5
WiFiClient.println(const char*);
Is documented as writing a string to the socket and appending '/r' '/n'The function suffers two significant issues;
Issue1
Passing 90 bytes will cause 92 bytes to be written to the socket, as expected.Passing 91 bytes will cause 0 bytes to be written to the socket and there will be no indication of failure.
Issue 2
#define WRITE_DELAY 2000 #define SEND_STRING "Hello World" if (client.connected) { client.println(SEND_STRING); delay(WRITE_DELAY); client.stop(); }
Reducing WRITE_DELAY below 1900 (ms) will cause the write to become unreliable. Reducing WRITE_DELAY below 1700 (ms) will cause the write to fail silently. Increasing or decreasing the length of SEND_STRING does not alter this behaviour. The length of the string is unrelated to the write latency.
WiFiClient.write(uint8_t*, size_t)
Suffers the same 90 byte issue as WiFiClient.println(const char*);
Write latency has not been specifically tested but is presumed to be the same.
Throughput and packet size on the wire
Are not directly related to the length of the data passed to WiFiClient.println() or WiFiClient.write();
For test purposes, the sendData(...) function below, was called repeatedly with no delay. Wireshark was used to observe packet sizes on the wire, during test runs. The test was repeated with different packetLen and maxLoop values, and using WiFiClienr.println() in place of WiFiClient.write();
.println(), packetLen = 90, maxLoop =10The target application receieved the data ~1012 bytes a time at ~2 Second intervals. The most common packet size was 514 bytes.
.println(), packetLen =45, maxLoop = 10The target application recieved the data ~517 bytes a time at ~ 2 Second intervals. The most common packet size was 549 bytes.
.println(), packetLen =90, maxLoop = 100The target application recieved the data ~1012 bytes a time at ~ 2 Second intervals. The most common packet size was 514 bytes.
.write(), packetLen =90, maxLoop = 10The target application received the data ~2970 bytes a time at ~ 2 Second intervals. The most common packet size was 504 bytes.
.write(), packetLen =45, maxLoop = 10The target application received the data ~1485 bytes a time at ~ 2 Second intervals. The most common packet size was 549 bytes.
.write(), packetLen =90, maxLoop = 100The target application received the data ~2970 bytes a time at ~ 2 Second intervals. The most common packet size was 504 bytes.
Arduino SPI write latency, is not directly related to the 2 second transmission latency observed during testing. Writing 9000 bytes to the Shield took between 25ms and 60ms. ~2 orders of magnitude faster than the fastest transmission.
The test results indicate the Shield's maximum throughput is ~12Kbps, subject to input buffering at the receiving application.
void sendData(WiFiClient* client, uint8_t dataLen, uint8_t maxLoop) { byte* packetData = NULL; packetData = (byte*) malloc(packetLen+1); for (uint8_t i = 0; i < dataLen_ ; i++) packetData[i] = 'a'; packetData[dataLen_]= '\0'; for {uint8_t i = 0; i < maxLoop; i++) { // client.println(packetData); client.write(packetData, packetLen); } free(packetData); }
Private uint8_t WiFiClient.getFirstSocket();
Is not documented. The function returns the integer ID of the next available socket. When MAX_SOCKET_NUM is exceeded, 255 is returned. The functions could be a useful substitute for WiFI.getSocket() except the function is declared Private in the class definition. Changing the declaration to Public by modifying the header file appears to be safe, subject to the inherent issues with managing modifications to standard libraries.
Test harness sketch source code
#include <stdio.h> #include <stdlib.h> #include <Streaming.h> #include <SPI.h> #include <WiFi.h> #include <WiFiClient.h> char SSID[] = "SSID"; char PSK[] = "password"; void setup() { Serial.begin(9600); Serial << "Setup...\r\n"; uint8_t numNets = WiFi.scanNetworks(); for (uint8_t i=0; i < numNets; i++) { Serial << WiFi.SSID(i) << '\t' << WiFi.RSSI(i) << "\r\n"; } do { Serial << "Try connect to " << SSID << "..."; WiFi.begin(SSID, PSK); if (WiFi.status() == WL_CONNECTED) { Serial << " Connected\r\n"; } else { Serial << "Failed to connect to " << SSID << "\r\n"; delay(5000); } } while (WiFi.status() != WL_CONNECTED); Serial << "End setup\r\n"; } char targetHost[] = "bigmac.mssystems.home"; const uint16_t port = 32123; WiFiClient client; void loop() { Serial << "\nEnter loop\r\n"; while ( WiFi.status() != WL_CONNECTED ) { Serial << "Try reconnect to " << SSID << "\r\n"; WiFi.begin(SSID, PSK); if (WiFi.status() == WL_CONNECTED) { Serial << "Reconnected to " << SSID << "\r\n"; } delay(5000); } Serial << "client.connected = " << client.connected() << "\r\n"; Serial << "wifi.getSocket = " << WiFi.getSocket() << "\r\n"; IPAddress ip; WiFi.hostByName(targetHost, ip); Serial <<"Try connect to " << targetHost << ' ' << ip << "..."; if (! client.connect(ip, port)) { Serial << "Failed\r\n"; } else Serial << "Connected\r\n"; Serial << "client.connected = " << client.connected() << "\r\n"; Serial << "WiFi.getSocket = " << WiFi.getSocket() << "\r\n"; Serial << "Wait for client.stop..."; delay(3000); if (client.connected()) { Serial << "Stop client\r\n"; client.stop(); } else { Serial << "Client not connected!\r\nApplication stop\r\n"; while(true); } Serial<< "Wait for loop exit...\r\n"; delay(5000); }
Output 1.
Enter loop
client.connected = 0
wifi.getSocket = 0
Try connect to bigmac.mssystems.home 192.168.200.64...Connected
client.connected = 1
WiFi.getSocket = 0
Stop client in 3 seconds
Output 2.
Wait for client.stop...Stop client
Wait for loop exit...
Try connect to bigmac.mssystems.home 192.168.200.64...Failed
Wait for client.stop...Client not connected!
Application stop
« Go back
Powered by Help Desk Software HESK, in partnership with SysAid Technologies