0

I'm using an Arduino Uno with an Ethernet Shield and the arduino.cc Ethernet library for a custom TCP-based message-exchanging chat-like protocol. Specifically I'm using the Client class with its print() and println() methods. It works.

But on each single print() or println() call a whole TCP packet is sent. Also if just one byte is printed, a whole TCP packet is sent over the IP layer. I mean there's a lot of TCP packets with very few data inside. In other words there's a big overhead.

For example take this message that my Arduino successfully sends and my server successfully receives:

i-am EEEEEEEE-EEEEEEEE-EEEEEEEE-EEEEEEEE\r\n

The code is the following:

stream.print("i-am");
stream.print(' ');
for (byte i = 0 ; i < SERIAL_CODE_LENGTH ; i++){
  if (  i % 4 == 0 && i != 0 ){
    stream.print('-');
  }
  if (serialCode[i] < 16){
    stream.print('0');
  }
  stream.print(serialCode[i], HEX);
}
stream.println();

All that EE are hexadecimal byte values from an array and they are fine. But I can see from Wireshark that those 42 bytes (that could be fit in a single packet) are sent in 22 TCP packets (one for each print call in my code)!

How can I avoid this fragmentation? Is there a way to use an hardware or software buffer to group data before to send (Nagle's algorithm)?

With other SDKs the data are buffered by default and they're sent when the buffer is full or when an explicit "push" or "flush" call occurs. I checked Client flush method and it looks to be a different beast...

  • Use a better Ethernet library. – Ignacio Vazquez-Abrams Sep 18 '16 at 11:41
  • Any suggestions for a better library, @IgnacioVazquez-Abrams? – Michael Vincent Sep 18 '16 at 12:28
  • It is possible to write an ethernet library that buffers directly in the W5100 and uses an efficient SPI interleaving algorithm for transfer. Please see the Cosa W5100/W5200 device drivers for example. github.com/mikaelpatel/Cosa/blob/master/libraries/W5100/… – Mikael Patel Sep 18 '16 at 12:51
  • To be honest, at the point I'm now with the sketch quite finished, I'm already thinking to replace those stream.print() with a sort of bufferredPrint() and to finalise them with bufferFlush(). But I still ask to myself if there is a better and feasible way to use the hardware buffer of the Ethernet shield and to do not upset the entire sketch. – Plap Sep 18 '16 at 13:26
  • Have a look at the example sketch CosaShellTelnetServer; github.com/mikaelpatel/Cosa-Shell/blob/master/examples/… You can use this to check the performance difference. – Mikael Patel Sep 18 '16 at 14:47
0

I suspect its stream.print() that is causing the problem. Write the data into a string and then use a SINGLE call to stream.print() to send the data. It looks like it is trying to send it as soon as possible to prevent delays and this is what is causing fragmentation..

  • Yes indeed. Anyway I prefer a byte buffer so I do not fear to send zeros (C-style string terminator). I posted my final solution in my answer. – Plap Sep 20 '16 at 21:01
0

Finally I managed to derive the EthernetClient class to override the write method and to add a sendNow method. In this way I still can:

  1. Use any print method version simply by inheritance;
  2. Pass the BufferedEthernetClient objects to function taking generalized Stream references (functions that can be used also with Serial, Wire, SD...).

Code

#define BUFFER_SIZE 64

class BufferedEthernetClient : public EthernetClient {
  public:
    BufferedEthernetClient();
    virtual size_t write(const uint8_t *buf, size_t size);
    virtual void sendNow();
  private:
    byte buffer[BUFFER_SIZE];
    byte bufferCount;
};

BufferedEthernetClient::BufferedEthernetClient(){
  bufferCount = 0;
}

size_t BufferedEthernetClient::write(const uint8_t *buf, size_t size){
  for (size_t i = 0; i < size; i++){
    buffer[bufferCount] = *(buf + i);
    bufferCount++;
    if (bufferCount >= BUFFER_SIZE){
      sendNow();
    }
  }
  return size;
}

void BufferedEthernetClient::sendNow(){
  if (bufferCount){
    bufferCount -= EthernetClient::write(buffer, bufferCount);
  }
}

Example

I use it in this way:

void printSerialCode(Stream & stream){
  for (byte i = 0 ; i < SERIAL_CODE_LENGTH ; i++){
    if (  i % 4 == 0 && i != 0 ){
      stream.print('-');
    }
    if (serialCode[i] < 16){
      stream.print("0");
    }
    stream.print(serialCode[i], HEX);
  }
}

BufferedEthernetClient client;
...
client.print("i-am");
client.print(' ');
printSerialCode(client);
client.println();
client.sendNow();
0

Instead of this, you can join strings into one string by separating with a sign (i.e."-") and then split it into array elements

  • 1
    This is just a duplicate of Code Gorilla's answer from a year and a half earlier. – Chris Stratton Feb 19 '18 at 7:52
  • 1
    It will a better answer is you added a code sample. – user31481 Feb 19 '18 at 9:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.