How does ackLater() actually work? #56
-
In my current project I need to stream data from an FTP server to the AsyncWebserver with a chunked response (since I don't know the size in advance). So, my ESP32 is working as a client to the FTP server and as a server to the browser. (The requirement here is that the user should be able to download files from the FTP server wirelessly and there is no other network available). Since the file is bigger than the RAM of the ESP32, i'm looking for a way to stream it. So, now I'm writing the data into a buffer (in the For some reason, the readout of the webserver is slower than the writing of the FTP server. So, my buffer fills up to the point of overflow. I try to mitigate this by calling After that, the webserver does read the data (although not all the time) and the data get's pulled out slowly. In the I would like to add that I have the feeling Small note to add: With wireshark I can see that the data is still being 'acked' even if I call |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 33 replies
-
@me-no-dev @vortigont : if you can help ? thanks! |
Beta Was this translation helpful? Give feedback.
-
} else {
if (_recv_cb) {
_recv_cb(_recv_cb_arg, this, b->payload, b->len);
}
if (!_ack_pcb) {
_rx_ack_len += b->len;
} else if (_pcb) {
_tcp_recved(_pcb, _closed_slot, b->len);
}
} if ackLater() is called within onData callback, _ack_pcb is false then |
Beta Was this translation helpful? Give feedback.
-
Would you be able to show us a MRE ? IT is not clear to me what ESPAsyncWebServer (http) does in the context of commmunication with a FTP server from AsyncTCP client... |
Beta Was this translation helpful? Give feedback.
-
Thanks for these details! If I understand your use case, you are trying to "slow down" the reading in order to let time for an asynchronous task on the background to process the queue when it is full, otherwise keep reading would fill the MCU RAM and crash ? |
Beta Was this translation helpful? Give feedback.
-
@the-programmer I dig a little to see the issues encountered with a tunnel: So I did the following code: // SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Shows how to trigger an async client request from a browser request and send the client response back to the browser
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <cbuf.h>
#define WIFI_SSID "IoT"
#define WIFI_PASSWORD ""
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
Serial.println("Connected to WiFi!");
Serial.println(WiFi.localIP());
#endif
//
// HOW TO RUN THIS EXAMPLE: curl -v http://192.168.125.127?host=www.google.com
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
const AsyncWebParameter *param = request->getParam("host");
if (!param || !param->value().length()) {
request->send(400, "text/plain", "Missing host parameter");
return;
}
AsyncClient *client = new AsyncClient();
cbuf *buffer = new cbuf(1024);
request->_tempObject = buffer;
// when client disconnects, we clear the buffer which signals the end of the chunk request
// note: onDisconnect also called on error
client->onDisconnect([request](void *arg, AsyncClient *client) {
Serial.printf("Tunnel disconnected!\n");
cbuf *content = (cbuf *)request->_tempObject;
delete content;
request->_tempObject = nullptr;
});
// register a callback when an error occurs
client->onError([request](void *arg, AsyncClient *client, int8_t error) {
Serial.printf("Tunnel error: %s\n", client->errorToString(error));
});
// register a callback when data arrives, to accumulate it
client->onData([request](void *arg, AsyncClient *client, void *data, size_t len) {
Serial.printf("Tunnel received %u bytes...\n", len);
size_t total = 0;
while (total < len) {
cbuf *content = (cbuf *)request->_tempObject;
if (content) {
if (content->full()) {
Serial.printf("Buffer is full: waiting for more space...\n");
// IMPORTANT NOTE: a normal request being a poll model, it is not suited with this async design.
// To correctly implement such tunnel, WebSocket should be used and onData should send websocket frames.
delay(100);
} else {
const size_t written = content->write((const char *)data, len);
Serial.printf("Wrote %u bytes to buffer\n", written);
total += written;
}
} else {
Serial.printf("Buffer is null: ignoring data\n");
break;
}
}
});
// register a callback when we are connected
client->onConnect([request](void *arg, AsyncClient *client) {
Serial.printf("HTTP Handshake...\n");
// send request
client->write("GET / HTTP/1.1\r\n");
client->write("Host: ");
client->write(request->getParam("host")->value().c_str());
client->write("\r\n");
client->write("User-Agent: ESP32\r\n");
client->write("Connection: close\r\n");
client->write("\r\n");
});
Serial.printf("Fetching: http://%s...\n", param->value().c_str());
if (client->connect(param->value().c_str(), 80)) {
Serial.printf("Tunnel connected!\n");
AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain", [request](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
// are we done ?
if (request->_tempObject == nullptr) {
return 0;
}
cbuf *content = (cbuf *)request->_tempObject;
const size_t avail = content->available();
// no data ? wait...
if (!avail) {
return RESPONSE_TRY_AGAIN;
}
Serial.printf("Available in buffer: %u bytes\n", avail);
const size_t len = maxLen < avail ? maxLen : avail;
const size_t read = content->read((char *)buffer, len);
Serial.printf("Read %u bytes from buffer\n", read);
Serial.printf("%.*s\n", read, buffer);
return read;
});
request->send(response);
} else {
Serial.printf("Failed to open tunnel!\n");
cbuf *content = (cbuf *)request->_tempObject;
delete client;
delete content;
request->_tempObject = nullptr;
request->send(500, "text/plain", "Failed to open tunnel");
}
});
server.begin();
}
void loop() {
delay(100);
} And actually, many issues prevent it to work:
if (!_ack_pcb) {
_rx_ack_len += b->len;
} else if (_pcb) {
_tcp_recved(_pcb, _closed_slot, b->len);
} Then I crated another example which works fine with websocket: // SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Shows how to trigger an async client request from a browser request and send the client response back to the browser through websocket
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#define WIFI_SSID "IoT"
#define WIFI_PASSWORD ""
static AsyncWebServer server(80);
static AsyncWebSocketMessageHandler wsHandler;
static AsyncWebSocket ws("/ws", wsHandler.eventHandler());
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
Serial.println("Connected to WiFi!");
Serial.println(WiFi.localIP());
#endif
//
// HOW TO RUN THIS EXAMPLE:
// 1. websocat --binary ws://192.168.125.127/ws
// 2. curl -v "http://192.168.125.127?host=www.google.com&port=80"
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
if (!request->getParam("host") || !request->getParam("host")->value().length()) {
request->send(400, "text/plain", "Missing host parameter");
return;
}
if (!request->getParam("port") || !request->getParam("port")->value().length()) {
request->send(400, "text/plain", "Missing port parameter");
return;
}
const String host = request->getParam("host")->value();
const String port = request->getParam("port")->value();
AsyncClient *client = new AsyncClient();
// when client disconnects, we clear the buffer which signals the end of the chunk request
// note: onDisconnect also called on error
client->onDisconnect([](void *arg, AsyncClient *client) {
Serial.printf("Tunnel disconnected!\n");
delete client;
});
// register a callback when an error occurs
client->onError([](void *arg, AsyncClient *client, int8_t error) {
Serial.printf("Tunnel error: %s\n", client->errorToString(error));
});
// register a callback when data arrives, to accumulate it
client->onData([](void *arg, AsyncClient *client, void *data, size_t len) {
Serial.printf("Tunnel received %u bytes...\n", len);
ws.binaryAll(static_cast<const uint8_t *>(data), len);
});
client->onConnect([host, port](void *arg, AsyncClient *client) {
Serial.printf("Tunnel connected!\n");
client->write("GET / HTTP/1.1\r\n");
client->write("Host: ");
client->write(host.c_str());
client->write(":");
client->write(port.c_str());
client->write("\r\n");
client->write("User-Agent: ESP32\r\n");
client->write("Accept: */*\r\n");
client->write("Connection: close\r\n");
client->write("\r\n");
});
Serial.printf("Fetching: http://%s:%s...\n", host.c_str(), port.c_str());
if (!client->connect(host.c_str(), port.toInt())) {
Serial.printf("Failed to open tunnel!\n");
delete client;
request->send(500, "text/plain", "Failed to open tunnel");
} else {
request->send(200, "text/plain", "Tunnel opened, data will be sent through websocket\n");
}
});
wsHandler.onConnect([](AsyncWebSocket *server, AsyncWebSocketClient *client) {
Serial.printf("Client %" PRIu32 " connected\n", client->id());
server->textAll("New client: " + String(client->id()));
});
wsHandler.onDisconnect([](AsyncWebSocket *server, uint32_t clientId) {
Serial.printf("Client %" PRIu32 " disconnected\n", clientId);
server->textAll("Client " + String(clientId) + " disconnected");
});
server.addHandler(&ws);
server.begin();
}
void loop() {
delay(100);
} |
Beta Was this translation helpful? Give feedback.
Thanks for these details!
I am wondering if
ackLater()
correctly works or is something to use in your case....I don't know why it was added and me-no-dev does not remember either...
If I understand your use case, you are trying to "slow down" the reading in order to let time for an asynchronous task on the background to process the queue when it is full, otherwise keep reading would fill the MCU RAM and crash ?