TCP Connection Budget
TCP Connection Budget
FastBee device-side web server is built on ESPAsyncWebServer + AsyncTCP, whose concurrency is limited by the lwIP TCP PCB table. Different chips have different resource capabilities, so connection budgets must be configured differently, otherwise pages may fail to load or SSE push may be lost.
Connection allocation follows these principles:
TCP_TOTAL = SSE (persistent push) + HTTP (page/API requests)- SSE: Reserved slots for
AsyncEventSource(real-time status push) - HTTP: Remaining slots for concurrent page loading and API calls
- Budget constants: Defined in
include/core/ResourceProfile.h - SSE limit:
MAX_SSE_CLIENTSfixed array inSSERouteHandler.h - TCP exhaustion threshold:
TCP_CONN_EXHAUSTION_THRESHOLDinWebConfigManager.hcontrols soft-restart trigger
Note: AsyncTCP v3.4.10 no longer reads
CONFIG_ASYNC_TCP_MAX_CONNECTIONSmacro; it is a legacy configuration. Actual connection count is determined by lwIPMEMP_NUM_TCP_PCB(default 16) and application-layer thresholds.
Chip Hardware Specifications
| Chip | Core | Internal SRAM | PSRAM | WiFi |
|---|---|---|---|---|
| ESP32 | Dual-core Xtensa 240MHz | 520 KB | None | 802.11 b/g/n |
| ESP32-S3 | Dual-core Xtensa 240MHz | 512 KB | 8 MB | 802.11 b/g/n |
| ESP32-C6 | Single-core RISC-V 160MHz | 512 KB | None | 802.11 ax (Wi-Fi 6) |
| ESP32-C3 | Single-core RISC-V 160MHz | 400 KB | None | 802.11 b/g/n |
Memory Budget Calculation
lwIP default configuration (all ESP32 variants, from ESP-IDF sdkconfig):
| Parameter | Default | Description |
|---|---|---|
MEMP_NUM_TCP_PCB | 16 | lwIP TCP PCB hard limit (all variants, cannot exceed) |
TCP_SND_BUF | 5744 B | Per-connection send buffer |
TCP_WND | 5760 B | Per-connection receive window |
| TCP PCB struct | ~172 B | lwIP internal control block |
| Per-connection total | ~11.7 KB | PCB + SND_BUF + WND |
Single-user access scenario analysis (browser single host max 6 concurrent):
| State | MQTT | SSE | HTTP | TIME_WAIT | Total PCB |
|---|---|---|---|---|---|
| Page load peak | 1 | 1 | 5-6 | 2-4 | 9-12 |
| Steady state | 1 | 1 | 0-2 | 1-2 | 3-6 |
| Multi-tab (S3) | 1 | 2 | 4-6 | 3-5 | 10-14 |
Connection Budget by Chip
| Chip | TCP Budget | SSE | HTTP | Exhaustion Threshold | Status |
|---|---|---|---|---|---|
| ESP32 classic | 6 | 1 | 5 | 12 | ✅ Implemented |
| ESP32-S3 | 8 | 2 | 6 | 14 | ✅ Implemented |
| ESP32-C6 | 6 | 1 | 5 | 12 | ✅ Implemented |
| ESP32-C3 | 4 | 1 | 3 | 10 | ✅ Implemented |
Exhaustion threshold must be < 16 (lwIP hard limit), triggering recovery before limit, leaving 2-6 slot buffer. Memory-constrained chips (C3) trigger earlier, memory-rich (S3) tolerate more connections.
PSRAM Impact on TCP Connection Capacity
ESP32-S3 with PSRAM significantly reduces internal DRAM contention, indirectly supporting more concurrent TCP connections:
| Scenario | No PSRAM | With PSRAM (threshold=512B) |
|---|---|---|
| Internal DRAM available | ~18 KB | ~22-35 KB (HTTP buffers offloaded to PSRAM) |
| lwIP TCP PCB space | Tight, easily exhausted | Sufficient (each PCB ~172B, must be in internal DRAM) |
| Concurrent HTTP requests | 2-3 triggers memory pressure | 6+ still handled normally |
CONFIG_ASYNC_TCP_MAX_CONNECTIONS=14(S3) is a build flag documentation; AsyncTCP v3.4.10 no longer reads it. Actual connection count constrained by lwIPMEMP_NUM_TCP_PCB=16and application-layerTCP_TOTAL_BUDGET=8. Raising this flag mainly serves as configuration intent documentation and backward compatibility with older AsyncTCP.
Key Files
| File | Purpose |
|---|---|
include/core/ResourceProfile.h | TCP_TOTAL_BUDGET / TCP_SSE_BUDGET / TCP_HTTP_BUDGET budget constants (chip-conditional compilation) |
include/network/handlers/SSERouteHandler.h | MAX_SSE_CLIENTS fixed array slot count |
include/network/WebConfigManager.h | TCP_CONN_EXHAUSTION_THRESHOLD soft-restart threshold |
src/network/WebConfigManager.cpp | Proactive abort TIME_WAIT connections every 30s + exhaustion detection and auto-recovery |
platformio.ini | CONFIG_ASYNC_TCP_MAX_CONNECTIONS in each [xxx_runtime_flags] (legacy flag, AsyncTCP 3.4.10 no longer reads, S3 raised to 14) |
Management Measures
TIME_WAIT Connection Periodic Cleanup
After ESPAsyncWebServer closes a connection, TCP PCB enters TIME_WAIT state (default 2×MSL ≈ 60s), still occupying slots during this period. WebConfigManager traverses lwIP PCB linked list every 30 seconds, proactively aborting timed-out TIME_WAIT connections:
// WebConfigManager.cpp - Clean up TIME_WAIT every 30s
if (millis() - lastTimeWaitCleanup > 30000) {
lastTimeWaitCleanup = millis();
// abort TIME_WAIT connections to free TCP PCB slots
}SSE Connection Limit
SSERouteHandler uses fixed array _slots[MAX_SSE_CLIENTS] to track clients. ESP32-S3 sets MAX_SSE_CLIENTS=2 (multi-tab support), other chips set MAX_SSE_CLIENTS=1 (single SSE connection). New connections are rejected when slots are full, preventing SSE from filling TCP PCB and blocking HTTP requests.
Modification Notes
- Any threshold modification must ensure
TCP_CONN_EXHAUSTION_THRESHOLD < 16(lwIP PCB hard limit) - After modifying
TCP_TOTAL_BUDGETinResourceProfile.h, must also update:MAX_SSE_CLIENTSinSSERouteHandler.h(≤TCP_SSE_BUDGET)TCP_CONN_EXHAUSTION_THRESHOLDinWebConfigManager.hCONFIG_ASYNC_TCP_MAX_CONNECTIONSin each[xxx_runtime_flags]ofplatformio.ini- Expected values in
scripts/validate-build-matrix.js
- ESP32-C3 has smallest RAM (400KB), TCP should not exceed 4
CONFIG_ASYNC_TCP_MAX_CONNECTIONSis deprecated by AsyncTCP 3.4.10, but retained as configuration documentation and backward compatibility
