Handle heap memory during frequent TCP client/server connections

Hey there,
I am using Arduino MKR1000 being a client connecting to NodeMCU ESP8266 being a server and an software accespoint.
In a remote place, the client shall be able to send strings approx. 10 times a second to the server.
My Problem is, that with the high frequency of 10/s I run out of heap memory on NodeMCU quite quickly (< 1 min).
As I understood, this is because of TIME_WAIT status of closed TCP connections. As I further understood, I am not able to stay connected, if I do other things between two readings (reading sensor values and writing to SD Card), so one new connection each loop is necessary.
Searching the forum and the web I was not able to find a satisfying solution.
Is anybody able to explain whether there is a possibility to solve this problem? I would really appreciate.

Minimal Code of AP:

/*  Accesspoint - station communication without router
    see: https://github.com/esp8266/Arduino/blob/master/doc/esp8266wifi/soft-access-point-class.rst
         https://github.com/esp8266/Arduino/blob/master/doc/esp8266wifi/soft-access-point-examples.rst
         https://github.com/esp8266/Arduino/issues/504
    this is the ap sketch
*/

#include <ESP8266WiFi.h>

// The Wifi Server and client
WiFiServer server(80);
IPAddress IP(192, 168, 4, 15);
IPAddress mask = (255, 255, 255, 0);
WiFiClient client;

String fromClient;

void setup() {
  // Setup Serial Port
  Serial.begin(9600);
  
  // Setup WiFi AP and server and print infos to serial
  WiFi.mode(WIFI_AP);
  Serial.println(WiFi.softAP("Wemos_AP", "Wemos_comm"));
  WiFi.softAPConfig(IP, IP, mask);
  server.begin();
  Serial.println("Server started.");
  Serial.print("IP: ");     Serial.println(WiFi.softAPIP());
  Serial.print("MAC:");     Serial.println(WiFi.softAPmacAddress());
}

void loop() {
  // check if client with data available
  client = server.available();
  // return if no client with data
  if (!client) {
    return;
  }         
  //read data if available                              
  client.setTimeout(50);                                          
  fromClient = client.readString();
  Serial.print("Recieved String: ");
  Serial.print(fromClient);
  client.flush(); 
  while (client.connected()) {
    client.stop();
  }
  //client.flush();
  //client.stop();
  Serial.print(F("\tfree heap: ")); 
  Serial.println(ESP.getFreeHeap());

}

Minimal Code of Client

/*  Accesspoint - station communication without router
    see: https://github.com/esp8266/Arduino/blob/master/doc/esp8266wifi/station-class.rst
    this is the station sketch
*/

#include <WiFi101.h>


char ssid[] = "Wemos_AP";       
char pass[] = "Wemos_comm";        

String data;

IPAddress server(192, 168, 4, 15); 
WiFiClient client;


void setup() {
  Serial.begin(9600);               
  WiFi.begin(ssid, pass);           
  delay(500);
}

void loop() {
  // loop while wifi connection fails
  while (WiFi.status() != WL_CONNECTED) {
    WiFi.begin(ssid, pass);
    Serial.println("Trying to connect again...");
    delay(500);
  }
  // connect to server and send String
  data = "Hello";
  client.connect(server, 80);
  Serial.print("Bytes succsessfully sent to the AP: ");
  Serial.println(client.print(data));
  client.flush();
  while (client.connected()) {
    client.stop();
  }
  delay(100);
}
  while (client.connected()) {
    client.stop();
  }

While? How many times do you think you need to call client.stop(), if there is a client connected?

As I further understood, I am not able to stay connected, if I do other things between two readings (reading sensor values and writing to SD Card), so one new connection each loop is necessary.

I don't see a reason why you cannot stay connected. Please explain why you think that the connection has to be closed after every small message sent.

@ PaulS: Totally right, doesnt make a lot of sense. Changed it.

@ pylon: The changed sketch below shows that even if no client.flush() or client.stop() is called in none of both sketches, the connection is lost at the end of the loop. See the Serial output below.

Station sketch:

/*  Accesspoint - station communication without router
    see: https://github.com/esp8266/Arduino/blob/master/doc/esp8266wifi/station-class.rst
    this is the station sketch
*/

#include <WiFi101.h>


char ssid[] = "Wemos_AP";
char pass[] = "Wemos_comm";

String data = "Hello";

IPAddress server(192, 168, 4, 15);
WiFiClient client;



void setup() {
  Serial.begin(9600);
  WiFi.begin(ssid, pass);
  delay(500);
}


void loop() {
  Serial.print("Status Beginning the Loop: ");
  Serial.println(client.connected());
  // loop while wifi connection fails
  while (WiFi.status() != WL_CONNECTED) {
    WiFi.begin(ssid, pass);
    Serial.println("Trying to connect again...");
    delay(500);
  }
  if (client.connected()) {
    Serial.print("Client has still been connected.");
    Serial.println(client.print(data));
    return;
  }
  // connect to server and send String
  data = "Hello";
  Serial.println("Need to connect to the server.");
  client.connect(server, 80);
  Serial.print("Status after connecting to server: ");
  Serial.println(client.connected());
  Serial.print("Bytes succsessfully sent to the AP: ");
  Serial.println(client.print(data));
  //client.flush();
  //client.stop();
  delay(1000);
  Serial.print("Status End of Loop: ");
  Serial.println(client.connected());
  Serial.println();
}

AP sketch:

/*  Accesspoint - station communication without router
    see: https://github.com/esp8266/Arduino/blob/master/doc/esp8266wifi/soft-access-point-class.rst
         https://github.com/esp8266/Arduino/blob/master/doc/esp8266wifi/soft-access-point-examples.rst
         https://github.com/esp8266/Arduino/issues/504
    this is the ap sketch
*/

#include <ESP8266WiFi.h>

// The Wifi Server and client
WiFiServer server(80);
IPAddress IP(192, 168, 4, 15);
IPAddress mask = (255, 255, 255, 0);
WiFiClient client;

String fromClient;


void setup() {
  // Setup Serial Port
  Serial.begin(57600);
  
  // Setup WiFi AP and server and print infos to serial
  WiFi.mode(WIFI_AP);
  Serial.println(WiFi.softAP("Wemos_AP", "Wemos_comm"));
  WiFi.softAPConfig(IP, IP, mask);
  server.begin();
  Serial.println("Server started.");
  Serial.print("IP: ");     Serial.println(WiFi.softAPIP());
  Serial.print("MAC:");     Serial.println(WiFi.softAPmacAddress());
}

void loop() {
  // check if client with data available
  client = server.available();
  // return if no client with data
  if (!client) {
    return;
  }         
  //read data if available                              
  client.setTimeout(50);                                          
  fromClient = client.readString();
  Serial.print("Recieved String: ");
  Serial.print(fromClient);
  //client.flush();
  //client.stop();
  Serial.print(F("\tfree heap: ")); 
  Serial.println(ESP.getFreeHeap());

}

Serial Output Station/client:

...

Status Beginning the Loop: 0
Need to connect to the server.
Status after connecting to server: 1
Bytes succsessfully sent to the AP: 5
Status End of Loop: 0

Status Beginning the Loop: 0
Need to connect to the server.
Status after connecting to server: 1
Bytes succsessfully sent to the AP: 5
Status End of Loop: 0

Status Beginning the Loop: 0
Need to connect to the server.
Status after connecting to server: 1
Bytes succsessfully sent to the AP: 5
Status End of Loop: 0

...

@ pylon: The changed sketch below shows that even if no client.flush() or client.stop() is called in none of both sketches, the connection is lost at the end of the loop. See the Serial output below.

Sure, you're throwing the client away after every loop and requesting a new connection from the server instance. You have to maintain the connection on the server or it will be closed.

@Juraj: Thanks, I will try that tomorrow!

@pylon: Thank you too! Could you give me a hint how not to throw it away? I assumed that in the code in #3 I didn´t do anything what can destroy it after reading, but connection is lost anyway.

In loop(), you ‘return’ ... where is it going to return to?

lastchancename: In loop(), you ‘return’ ... where is it going to return to?

to the same place where it goes after the ending } of the function - to main()

In both sketches I intended to return to the beginning of the loop() function without executing the code following after the if(){return;} statements. If this is not the case, where does it go then? To which position in the main()? Obviously (regarding the serial output), it is not starting the main() function again, because setup() is never executed again.

@ Juraj: Yesterday you gave me the hint to a bug in 2.4.1. of ESP8266/Arduino. Now I wanted to check and realized your post is gone? You mentioned quite exactly where the problem was and I would like to check, so could you post it again?

fauth: @ Juraj: Yesterday you gave me the hint to a bug in 2.4.1. of ESP8266/Arduino. Now I wanted to check and realized your post is gone? You mentioned quite exactly where the problem was and I would like to check, so could you post it again?

I later noticed your client is WiFi101. And I am not sure if the fix in esp8266 library applies to WiFiClient created by WiFiServer, since it is in connect()

Could you give me a hint how not to throw it away? I assumed that in the code in #3 I didn´t do anything what can destroy it after reading, but connection is lost anyway.

void loop() {
  // check if client with data available
  client = server.available();
...
  fromClient = client.readString();
...
}

In every loop your overwriting the local client variable with the return value of server.available(). If there isn't a new connection coming in you get an empty client (no socket association) back and the old value is destroyed, so the destructor (~WifiClient()) is called and the connection is closed.

Thanks a lot for all you help!

Now I understood the problem with frequent client/server-connection/closing and I´am able to keep the connection alive.

And I solved the problem of running out of heap (even with high frequent new connections):

Adding the following line as line 77 in the close function to ClientContext.h.

tcp_abort(_pcb);

In case, different versions exist, the following code shows the changed function in ClientContext.h with the new line.

    err_t close()
    {
        err_t err = ERR_OK;
        if(_pcb) {
            DEBUGV(":close\r\n");
            tcp_arg(_pcb, NULL);
            tcp_sent(_pcb, NULL);
            tcp_recv(_pcb, NULL);
            tcp_err(_pcb, NULL);
            tcp_poll(_pcb, NULL, 0);
            err = tcp_close(_pcb);
            tcp_abort(_pcb);                                            // Diese Zeile löst das heap-Problem!!!!!
            if(err != ERR_OK) {
                DEBUGV(":tc err %d\r\n", (int) err);
                tcp_abort(_pcb);
                err = ERR_ABRT;
            }
            _pcb = 0;
        }
        return err;
    }