ESP32以太网HTTPS下载大文件连接断开,求解决方法
ESP32以太网HTTPS大文件下载断开问题
我在ESP32设备上通过以太网(未使用WiFi),采用HTTP 1.1协议发起HTTPS的GET请求。下载极小文件(25字节)时可成功完成,但文件尺寸增大至20KB或2MB时,连接会很快断开,无法完成下载。以下是我的代码:
#include <EthernetUdp.h> #include <EthernetLarge.h> #include <SSLClient.h> // Choose the analog pin to get semi-random data from for SSL // Pick a pin that's not connected or attached to a randomish voltage source const int rand_pin = A5; // Initialize the SSL client library // We input an EthernetClient, our trust anchors, and the analog pin EthernetClient base_client; SSLClient client(base_client, TAs, (size_t)TAs_NUM, rand_pin); bool NanoEthernet::httpsGet( char const *hostname, char const *path, byte *buffer, size_t bufferCapacity, size_t &bodyLength, bool &bufferOverflows, void (*downloadChunkCallback)(byte * buffer, size_t bufferReadCount, size_t contentLength) ) { log("Time is: %d %d %d %02d:%02d:%02d", day(), month(), year(), hour(), minute(), second()); char const * subModule = "httpsGet: "; log("%sbegin %s%s…",subModule, hostname, path); // === if you get a connection, report back via serial: if (!client.connect(hostname, 443)) { // if you didn't get a connection to the server: error("%sconnection failed", subModule); return false; } success("%sConnected!",subModule); // Make a HTTP request: client.printf("GET %s HTTP/1.1", path); client.println(); client.printf("Host: %s", hostname); client.println(); // client.println("Connection: keep-alive"); client.println(); // client.println("Keep-Alive: timeout=5, max=100"); client.println(); //client.println("Connection: close"); client.println(); //while(client.connected() && !client.available()) delay(1); //waits for data // success("%sSent query headers (connected:%d)", // subModule, // client.connected() // ); /// Body counter int bodyCounter = 0; /// Chunk counter int chunkReadCount = 0; /// Whether to read response header? bool readResponseHeader = true; byte *p = buffer; int contentLength = 0; const unsigned timeoutNoData = 10*1000; // 10 seconds unsigned long long lastTimeNoData = millis(); unsigned waitForConnection = 10; while (true) { // log("%sResponse: connected: %d | readResponseHeader: %d", // subModule, // client.connected(), // readResponseHeader // ); if (!client.connected()) { delay(1); error("%sNo more connected, aborting", subModule); if (waitForConnection-- <= 0) continue; else break; } if (!client.available()) { delay(1); // == Still reading the header then continue, otherwise hanle timeout if (!readResponseHeader) { if (millis() - lastTimeNoData >= timeoutNoData) { if (contentLength==0) { log("%sNo more data are available after %d seconds timeout", subModule, timeoutNoData/1000 ); } else { error("%sNo more data are available timeout", subModule); } // == Stopping the connection break; } } //log(">."); continue; } lastTimeNoData = millis(); // = Skip response header if (readResponseHeader) { const String header = client.readStringUntil('\n'); // End of line is "\r\n" log("%sResponse headers: %s", subModule, header.c_str()); const int nextData = client.peek(); // End of stream? if (nextData==-1) { error("%sEnd of stream (received data -1) while reading headers"); break; } const char nextChar = char(nextData); //log("Response headers: => next char is %d 0x%02x (%c)", nextChar, nextChar, nextChar); // == End of headers? (empty line) if (nextChar == '\r') { // Actually read the end of line client.read(); // eat \r client.read(); // eat \n readResponseHeader=false; } else { char const *headerString = "Content-Length: "; const int headerStringLength = 16; if (header.startsWith(headerString)) { const String sContentLength = header.substring(headerStringLength); contentLength = header.substring(headerStringLength).toInt(); //log("Read Content-Length: string: '%s'| int: %d bytes", sContentLength.c_str(), byteCountToRead); } } continue; } const int data = client.read(); //gets byte from ethernet buffer // == End of stream? if (data==-1) { log("%sEnd of stream (data received: -1)",subModule); break; } // == Keep the actual byte const byte b = byte(data); #if LOG_LEVEL >= LOG_LEVEL_DEBUG //log(">%02x (%d)", b, bodyCounter); log(">%02x ", b); #endif *p++ = b; bodyCounter++; chunkReadCount++; // = When no chunk callback, overflow means stop reading if (downloadChunkCallback==NULL) { // == If buffer overflows, stop if (bodyCounter >= bufferCapacity) { bufferOverflows = true; break; } } // = But when chunk callback has been provided, pass it the // chunk, rewind the buffer and continue else { // == If buffer overflows, stop if (chunkReadCount >= bufferCapacity) { downloadChunkCallback(buffer, chunkReadCount, contentLength); // Now rewind the chunk to the satrt of the buffer prior to // continue reading the reponsse body chunkReadCount = 0; p = buffer; } } // == We read everything? if (contentLength>0) { if (bodyCounter>=contentLength) { log("%sEnd of stream (received ontentLength bytes)", subModule); break; } } } // = When chunk callback, pass it the potential remaining chunk if(downloadChunkCallback!=NULL && chunkReadCount>0) { downloadChunkCallback(buffer, chunkReadCount, contentLength); } // == Write the length we read bodyLength = bodyCounter; #if LOG_LEVEL >= LOG_LEVEL_DEBUG log(""); log("%s: did the get and read %d bytes", subModule, bodyCounter); #endif client.stop(); return true; }
问题排查与修复方案
1. 修正连接状态判断逻辑
当前代码中!client.connected()的处理逻辑反转,导致连接断开时错误地无限重试或提前退出。修正为:
if (!client.connected()) { delay(1); if (waitForConnection-- <= 0) { error("%sNo more connected, aborting", subModule); break; } continue; }
重试10次后仍未恢复连接则退出循环,避免无效等待。
2. 批量读取数据,提升处理效率
单字节读取client.read()会导致IO效率极低,TCP接收窗口被填满后服务器会停止发送甚至断开连接。改用批量读取:
// 替换原单字节读取的代码块 size_t availableBytes = client.available(); if (availableBytes == 0) continue; // 计算可读取的最大字节数,避免缓冲区溢出 size_t readSize = min(availableBytes, bufferCapacity - chunkReadCount); size_t bytesRead = client.readBytes(p, readSize); if (bytesRead == 0) { log("%sEnd of stream", subModule); break; } p += bytesRead; bodyCounter += bytesRead; chunkReadCount += bytesRead;
3. 显式关闭HTTP持久连接
HTTP/1.1默认启用长连接,ESP32的SSLClient在长连接下易因超时或资源不足断开。添加Connection: close请求头:
// 在请求头中添加以下行 client.println("Connection: close");
让服务器在传输完成后主动关闭连接,避免长连接超时问题。
4. 调整SSL接收缓冲区大小
SSLClient默认缓冲区较小,大文件下载时容易溢出。初始化时指定更大的接收缓冲区(根据ESP32内存情况调整,示例为4KB):
// 修改SSLClient初始化代码 SSLClient client(base_client, TAs, (size_t)TAs_NUM, rand_pin, 4096);
5. 修复Content-Length类型与解析
将contentLength改为size_t类型(支持更大文件尺寸),并使用无符号整数解析:
// 替换原contentLength定义 size_t contentLength = 0; // 替换原解析代码 if (header.startsWith(headerString)) { contentLength = header.substring(headerStringLength).toUInt(); }
6. 减少调试输出
调试输出会占用大量CPU时间,导致数据处理不及时。生产环境关闭LOG_LEVEL_DEBUG或移除不必要的日志打印。
内容的提问来源于stack exchange,提问作者Stéphane de Luca
相关产品推荐
相关产品推荐

