Knowledgebase
emscom > emscom Help Desk > Knowledgebase

Search help:


Arduino WiFiClient (TCP)

Solution

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 println() method can be significantly slower than the write() method.  
  • Writing smaller amounts of data to the SPI bus results in smaller amounts of data appearing on the wire when the trasnmit request is serviced.
  • There is a bottle neck within the SPI bus. Data chunks written to the bus should not exceed 90 bytes.
  • Attempting to write a chunk longer than 90 bytes causes a silent failure and loss of the data.

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().

 

 Arduino   Direction   Host 
Connect
SYN  >  
   < SYN ACK
ACK  >  
  Connected  
Close
FIN  >  
   < FIN ACK
ACK  >  
  Closed  

 

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

  • To work reliably with the WiFi shield as a client, a server application will need to manage a pool of sockets and implement a dead peer detection (keep alive) protocol.
  • For Shield to Shield connection, dead peers must be managed within the 4 socket total limit.

 

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;

  1. Should the length of the string passsed to the function exceed 90 bytes, the write will fail silently.
  2. Calling WiFiClient.Stop() within ~2 seconds of writing, will cause the write to fail silently.

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 =10
The 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 = 10
The 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 = 100
The 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 = 10
The 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 = 10
The 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 = 100
The 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.

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

Wait for client.stop...Stop client

Wait for loop exit...

 

Enter loop

client.connected = 0

wifi.getSocket = 0

Try connect to bigmac.mssystems.home 192.168.200.64...Failed

client.connected = 0

WiFi.getSocket = 0

Wait for client.stop...Client not connected!

Application stop 

 
Was this article helpful? yes / no
Related articles Arduino WiFiUDP
Read fixed length serial packet
Arduino Wifi Firmare upgrade (win)
Arduino UDP device control block example
Arduino Processing Sketch to receive UDP Device Control Block
Article details
Article ID: 51
Category: Arduino
Date added: 14-11-2013 07:59:34
Views: 20708
Rating (Votes): Article rated 3.9/5.0 (51)

 
« Go back

 
Powered by Help Desk Software HESK, in partnership with SysAid Technologies