ESP32 Webserver na socketach przykładowy kod
WebServer
ani ESPAsyncWebServer
; pokazuje, jak “od zera” utworzyć socket, zbindować go do portu 80, odebrać żądanie, sparsować metodę/ścieżkę i odesłać odpowiedź. loop()
), więc dla większego obciążenia należy dodać SO_RCVTIMEO
, krótkie time-outy i/lub przerzucić obsługę do osobnego tasku (FreeRTOS)./* ESP32 – Minimalny serwer HTTP na surowych socketach
Testowane: Arduino-core 2.0.14, ESP-IDF 5.1, płytka DEVKIT V1 */
#include <WiFi.h>
#include <lwip/sockets.h> // surowe gniazda
#include <lwip/netdb.h>
const char* ssid = "Twoja_SSID";
const char* password = "Twoje_Haslo";
constexpr uint16_t SERVER_PORT = 80;
int serverSock = -1; // globalny descriptor
// --- Funkcje pomocnicze -----------------------------------------------------
void die(int sock) { if (sock >= 0) { close(sock); } }
void sendHttp(int client, const String& body,
const String& mime = "text/html", uint16_t code = 200)
{
const char* text =
(code == 200) ? "OK" :
(code == 404) ? "Not Found" :
"Internal";
String hdr = "HTTP/1.1 " + String(code) + " " + text + "\r\n"
"Content-Type: " + mime + "\r\n"
"Content-Length: " + String(body.length()) + "\r\n"
"Connection: close\r\n\r\n";
send(client, hdr.c_str(), hdr.length(), 0);
send(client, body.c_str(), body.length(), 0);
}
String mainPage() {
return F(R"rawliteral(
<!DOCTYPE html><html><head>
<meta charset="utf-8"><title>ESP32 Socket Server</title></head>
<body><h1>Witaj z ESP32!</h1>
<p><a href="/api/status">/api/status</a></p></body></html>)rawliteral");
}
String jsonStatus() {
return String("{\"free_heap\":") + ESP.getFreeHeap() +
",\"uptime\":" + millis()/1000 + "}";
}
// --- Konfiguracja Wi-Fi -----------------------------------------------------
void setupWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print(F("Łączenie z Wi-Fi "));
uint8_t retries = 20;
while (WiFi.status() != WL_CONNECTED && retries--) {
delay(500); Serial.print('.');
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println(F("\nBłąd Wi-Fi – restart"));
esp_restart();
}
Serial.print(F("\nIP: ")); Serial.println(WiFi.localIP());
}
// --- Start socket server ----------------------------------------------------
void startServer() {
serverSock = socket(AF_INET, SOCK_STREAM, 0);
if (serverSock < 0) { Serial.println("socket() fail"); return; }
int opt = 1;
setsockopt(serverSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(SERVER_PORT);
if (bind(serverSock, (sockaddr*)&addr, sizeof(addr)) < 0) {
Serial.println("bind() fail"); die(serverSock); return;
}
if (listen(serverSock, 4) < 0) {
Serial.println("listen() fail"); die(serverSock); return;
}
Serial.printf("Serwer HTTP (socket) nasłuchuje na porcie %u\n", SERVER_PORT);
}
// --- Prosta obsługa klienta --------------------------------------------------
void handleClient(int clientSock) {
char buf[1024] = {0};
int n = recv(clientSock, buf, sizeof(buf)-1, 0);
if (n <= 0) { return; }
String req(buf); // pełne żądanie HTTP w RAM
int s1 = req.indexOf(' ');
int s2 = req.indexOf(' ', s1 + 1);
if (s1 < 0 || s2 < 0) { return; }
String method = req.substring(0, s1);
String path = req.substring(s1 + 1, s2);
path.trim();
if (path == "/" || path == "/index.html") {
sendHttp(clientSock, mainPage());
}
else if (path == "/api/status") {
sendHttp(clientSock, jsonStatus(), "application/json");
}
else {
sendHttp(clientSock,
"<html><body><h1>404 - Nie znaleziono</h1></body></html>",
"text/html", 404);
}
}
// ---------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
setupWiFi();
startServer();
}
void loop() {
if (serverSock < 0) { delay(1000); return; }
sockaddr_in clientAddr; socklen_t clen = sizeof(clientAddr);
int cSock = accept(serverSock, (sockaddr*)&clientAddr, &clen);
if (cSock >= 0) {
IPAddress cip(ntohl(clientAddr.sin_addr.s_addr));
Serial.printf("Klient: %s\n", cip.toString().c_str());
handleClient(cSock);
shutdown(cSock, SHUT_RDWR);
close(cSock);
}
delay(10); // krótkie wyjście do IDF scheduler-a
}
Kluczowe elementy:
socket()
+ bind()
+ listen()
tworzą serwer. accept()
blokuje (na ułamek ms) aż pojawi się klient, po czym zwraca deskryptor klienta. accept()
do oddzielnego tasku FreeRTOS, dzięki czemu główna pętla pozostanie responsywna (przykład: xTaskCreatePinnedToCore
). lwip/altcp_tls.h
lub skompiluj z komponentem mbedTLS i wywołaj socket(AF_INET, SOCK_STREAM, IPPROTO_TLS_1_2)
. Trzeba załadować certyfikat (PEM) do flash. ESPAsyncWebServer
(patrz sekcja “Aktualne informacje”). esp_netif
. ESPAsyncWebServer
(asynchroniczny, WebSocket, SSE),mongoose
(kompaktowy, RTOS-friendly, TLS 1.3),esp_http_server
(natywny komponent ESP-IDF, C API). recv()
może nie odebrać całego nagłówka HTTP – w przykładzie przyjmujemy małe żądania GET (do 1024 B). Dla plików POST dodaj pętlę z buforowaniem. setsockopt(..., SO_REUSEADDR)
pozwala natychmiast ponownie uruchomić serwer po resecie. listen()
(backlog) i stosuj SO_RCVTIMEO
. curl http://<ip>/
, curl http://<ip>/api/status
, ab -n 50 -c 5 http://<ip>/
(ApacheBench) do pomiaru RPS. esp_wifi_set_promiscuous(true)
(tylko monitor). CONFIG_LWIP_TCP_SACK_OUT
. WiFiServer
(Arduino) vs. BSD-socket vs. esp_http_server
. nghttp2
– mniejsze zużycie TCP-handshake. /update
(multipart/form-data) obsłużonym na surowych socketach. Udostępniony kod pokazuje, jak w czystym C++ utworzyć serwer HTTP na ESP32 bez bibliotek wysokopoziomowych. Pozwala to w pełni kontrolować buforowanie, bezpieczeństwo i protokół, kosztem większej złożoności. W praktycznych aplikacjach warto rozważyć ESPAsyncWebServer
lub natywny esp_http_server
, lecz zrozumienie podstaw socketów ułatwia debugowanie i optymalizację.