WiFiClient vs. WiFiUDP behavior

Hello,

I wonder why the WiFiUDP class has methods beginPacket() / endPacket() to define what data will be sent in one packet, but WiFiClient does not have such methods.

So will WifiClient send the data as a new network package for each print() statement? I guess yes.

But then, why is this done differently with WiFiUDP?

UDP sends a message/packet. beginPacket initializes the internal buffer. endPacket sends the message filled into the buffer with writes or prints.
WiFiClient creates a TCP socket connection. connect() creates the connection. written data are send as needed a stop() disconnects.

UDP is a connectionless unreliable protocol - a datagram is created with a target IP address and sent off into the network - you have no idea when or even if it arrives - no flow control a sequence of datagrams may or may not arrive in order - a similar concept to posting a letter - used in applications where the odd lost datagram does not mattter
with TCP a connection is created from source IP address to destination IP address - packets are sent with flow control and error detection - similar concept to a telephone call

Example of sending GPS RMC over UDP

/*
   GPS Portal, part of Tardis Time Server by: M. Ray Burnette 20150915
   Create a private 10. network and capture all DNS requests to the Time Portal
   Respond to UDP and DHCP and DNS
   Arduino GUI 1.6.7 on Linux Mint 17.3 on 20160206
   ESP8266 core: http://arduino.esp8266.com/staging/package_esp8266com_index.json
      Sketch uses 235,297 bytes (54%) of program storage space. Maximum is 434,160 bytes.
      Global variables use 32,153 bytes (39%) of dynamic memory, leaving 49,767 bytes for local variables.
*/
#define LEDpin 2                                                // ESP82660-1 8-pin module LED is on GPIO-02
#include <Streaming.h>                                          // \Documents\Arduino\libraries\Streaming (legacy) user-installed
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <DNSServer.h>
#include "./Utility.h"                                          // See Notes tab for credits

const byte   DNS_PORT  =   53;                                  // Listen DNS requests on port 53
int          ack_Count =    0;
uint8_t      hour, minute, seconds;                             // hour, minure, seconds,
uint8_t      day, month, year;                                  // year, month, day;
unsigned int localPort = 8888;                                  // any unused port on LAN
IPAddress    apIP(10, 10, 10, 1);                               // Private network address: local & gateway
IPAddress    broadcastIP(255, 255, 255, 255);                   // https://en.wikipedia.org/wiki/Multicast

char         packetBuffer[UDP_TX_PACKET_MAX_SIZE];              // buffer to hold incoming packet,
char         ReplyBuffer[] = "????????????";                    // a 12 character string to send back

DNSServer         dnsServer;                                    // Create the DNS object
WiFiUDP           UDP;                                          // UDP protocol on STA interface, localPort

extern "C" {
  #include "user_interface.h"                                   // used for diagnostic ESP.getFreeHeap()
}

// Forward declarations
void GPSstuff(char c);


void setup()
{
  Serial.begin(4800);                                           // Initialise Serial for older PMB-648
  Serial << (F("2015 Ray Burnette")) << endl;
  Serial << (F("Tardis Time Portal Version 0.20150915")) << endl;
  
  WiFi.mode(WIFI_AP_STA);                                       // AP + STA
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));   // subnet FF FF FF 00
  WiFi.softAP("TardisTime");

  dnsServer.start(DNS_PORT, "*", apIP);                         // "*" creates a captive portal

  while (! UDP.begin(localPort) ) {                             // UDP protocol connected to localPort which is a variable
    Serial << "+" ;
    yield();                                                    // Play nicely with RTOS (alias = delay(0))
  }                                                             // This will loop forever if UDP fails

  Serial << endl;
  Serial << (F("Setting pin#2 to Output mode")) << endl;
  pinMode(2, OUTPUT);                                           // initialise pin mode
}


void loop()
{
  dnsServer.processNextRequest();                               // TCP Address handler when requested
  yield();                                                      // yield for RTOS
//  Listener();                                                   // UPD (can you hear me now?)
//  yield();

  if (Serial.available() > 0) {                                 // anything in the serial hw buffer?
      char c = Serial.read();                                   // if so, fetch the next character from FIFO
      GPSstuff(c);
  }

  yield();
}                                                               // loop repeats forever unless stack crashes or uC hangs


void GPSstuff(char c) {                                         // GPSbuffer[] 
  static int i, j;                                              //   persistent within function scope
  static char q;
  static bool flag = false;
  static char GPSbuffer[120];                                   // GPS serial line input buffer
  q = c;

  if ( q == 0x24 )                                              // '$'
  {
    i = 0;                                                      // brute force sync on '$' to GPSbuffer[0]
    // Serial << "Found $" << endl;
  }

  if ( i < 120) GPSbuffer[i++] = q;

  if (q == 0x0d) {
    flag = true;                                                // is the character a CR for eol
    i = 0;
  }

  if (flag) {                                                   // test for end of line and if the right GPSbuffer
    flag = false;                                               // reset for next time
    digitalWrite(LEDpin, !(digitalRead(LEDpin)));               // blink LED (Warning: using ESP8266-01 module)
    UDP.beginPacketMulticast(broadcastIP, localPort, apIP);
    // Serial << "We are in the flag routine..." << GPSbuffer[3] << GPSbuffer[4] << GPSbuffer[5] << endl;
    // Serial << "Analyzing " << GPSbuffer[3] << GPSbuffer[4] << GPSbuffer[5] << endl;
    if ( (GPSbuffer[3] == 0x52) && (GPSbuffer[4] == 0x4d) && (GPSbuffer[5] == 0x43)) // 'R' && 'M' && 'C'
    {
      long int RAM = ESP.getFreeHeap();
      Serial << "Free= " << RAM << endl;

      for (j = 0; j < 120 ; j++) {
        UDP.write(GPSbuffer[j]);
      }

      UDP.write("\r\n");                                        // terminate the line
      UDP.endPacket();                                          // clear UDP buffer
      Serial << GPSbuffer << endl;
    }
  }
}

/*
void Listener() {
  int packetSize = UDP.parsePacket();                           // if there’s data available, read a packet
  if (packetSize)
  {
    Serial << endl;
    Serial << (F("Received packet of size: "));
    Serial << (packetSize);
    Serial << (F(" From remote: "));
    IPAddress remote = UDP.remoteIP();

    for (int k = 0; k < 4; k++)
    {
      Serial << (remote[k], DEC);
      if (k < 3)
      {
        Serial << (F("."));
      }
    }
    Serial << (F(", port: "));
    Serial << " UDPremotePort: " << (UDP.remotePort()) << endl ;

    UDP.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);             // read the packet into packetBufffer
    Serial << (F("Contents: "));
    int value = packetBuffer[0] * 256 + packetBuffer[1];
    Serial << value << endl;
    Serial << (F("Ack counter: "));
    Serial << (ack_Count) << endl;                              // ever increasing serialnumber
    intToStr(ack_Count, ReplyBuffer, 12);                       // 12 characters wide
    UDP.beginPacket(UDP.remoteIP(), UDP.remotePort());          // send a reply, to the IP address and port that sent us the packet we received
    UDP.write(ReplyBuffer);
    UDP.endPacket();
    ++ack_Count;    
  }                                                             // if(packetSize)
}
*/



Utility.cpp (1.9 KB)
Utility.h (283 Bytes)

Hi, I appreciate your explanations of TCP vs. UDP, and of the WiFiUDP interface. Maybe I did not state my point clearly enough:

  • it is quite clear that for UDP it makes sense to collect a few print statements into a buffer and then send it in one go as one packet. This reduces protocol overhead and makes communication faster. So that is why the library implements sending UDP like that.
  • the same reasoning would be true for sending data using the TCP protocol. One larger packet is more efficient than sending multiple smaller ones. Actually we have experimented with multiple print statements vs. one larger print statement, and see the speed difference. So I would like to understand the reasoning why the library does not implement an interface supporting that for TCP.

TCP is connection based, and UDP is connection-less. But with both protocols the data is sent in packages with some overhead. So this difference does not explain the different approach to the protocols as seen in Wifi101, I would say.

TCP is treated as a continuous stream of data. Of course, on the wire it is sent as a series of packets, but that really doesn't matter to the process using it. Indeed, it would be harder to use if you had to manage the packets yourself.

each datagram is transmitted as an independed packet containing source and destination IP address and other overhead information
on TCP initialisation a virtual circuit is setup thru the network from source IP to destination IP. Once the virtual circuit is setup the overhead for each data packet is less than for a UDP datagram. TCP uses a sliding window protocol for flow control to check for lost or multiple packets and other error conditions

TCP guaranties that the packet is delivered and that packets are delivered in correct order. UDP is send and forget.

use buffering if you want larger packets
https://github.com/jandrassy/StreamLib/blob/master/README.md#buffering-and-flush

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.