4、Wi-Fi与TCP/IP协议栈:ESP32的Wi-Fi连接管理,TCP/UDP通信基础

好,咱们进入第四章。这一章讲的是ESP32的联网基本功——Wi-Fi连接和TCP/IP通信。说实话,这部分是物联网网关的命脉。你想想看,一个网关如果连不上网,那跟一块砖头有什么区别?

我个人习惯把Wi-Fi和TCP/IP放在一起讲,因为它们是密不可分的。Wi-Fi负责物理连接,TCP/IP负责数据怎么传。就像打电话,Wi-Fi是拨通号码,TCP/IP是你说的话怎么被对方听懂。

4.1 ESP32的Wi-Fi工作模式

ESP32的Wi-Fi模块有三种工作模式,我刚开始用的时候也经常搞混。咱们一个一个说清楚。

模式 角色 典型场景
Station模式(STA) 客户端 连接家里的路由器上网
Access Point模式(AP) 热点 手机直连ESP32进行配置
STA+AP混合模式 既是客户端又是热点 网关同时连接外网和本地设备

我在项目中遇到过一个问题:设备在STA模式下连不上公司复杂的Wi-Fi认证网络。后来发现是Enterprise认证方式没配置对。嗯,这里要注意,ESP32的Wi-Fi库默认只支持WPA2-Personal,如果要连企业级网络,得用esp_wifi_set_config()单独配置。

4.2 Wi-Fi连接管理——别让连接断了

Wi-Fi连接管理,说白了就是怎么让ESP32稳定地连上网络,断了还能自动重连。我见过太多新手直接把Wi-Fi连接写在setup()里,然后就不管了。结果路由器一重启,设备就彻底失联。

正确的做法是什么?用事件驱动的方式管理连接。ESP-IDF提供了esp_event_handler_register()来监听Wi-Fi事件。

#include "esp_wifi.h"
#include "esp_event.h"

static void wifi_event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        // 断线重连,加个延时防止频繁重连
        vTaskDelay(pdMS_TO_TICKS(3000));
        esp_wifi_connect();
        ESP_LOGI("WIFI", "重连中...");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI("WIFI", "获取到IP: " IPSTR, IP2STR(&event->ip_info.ip));
    }
}

void wifi_init_sta(void)
{
    esp_netif_init();
    esp_event_loop_create_default();
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);

    esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL);
    esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL);

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "你的WiFi名称",
            .password = "你的WiFi密码"
        },
    };
    esp_wifi_set_mode(WIFI_MODE_STA);
    esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
    esp_wifi_start();
}
我的小技巧:断线重连时加个指数退避延时。第一次等3秒,第二次等6秒,第三次等12秒。这样既不会把路由器刷爆,也能在信号恢复后尽快连上。

4.3 TCP通信——可靠但有点重

TCP协议,全称传输控制协议。它保证数据一定送到,顺序不乱。代价是什么?握手开销大,占用内存多。ESP32的RAM只有几百KB,你想想看,如果同时开几十个TCP连接,内存很快就爆了。

我建议在网关场景中,TCP主要用于控制指令的下发。比如云端告诉设备「打开继电器」,这种数据量小、要求可靠的消息,用TCP最合适。

ESP32上使用TCP,一般用LwIP协议栈。ESP-IDF封装了BSD Socket API,用起来跟Linux上的socket编程一模一样。

// TCP客户端示例
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in dest_addr;
dest_addr.sin_addr.s_addr = inet_addr("192.168.1.100");
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(8080);

connect(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
send(sock, "Hello from ESP32", 16, 0);
close(sock);
我曾经踩过的坑:TCP的send()函数并不保证一次把所有数据发完。它返回的是实际发送的字节数。一定要用循环把剩余数据发完,否则会出现数据截断。我当年调试一个固件升级功能,就是因为这个bug,导致升级包总是损坏。

4.4 UDP通信——轻量但不可靠

UDP就简单多了。没有握手,没有确认,发出去就不管了。适合什么场景?传感器数据上报、心跳包、视频流。这些场景丢一两个包无所谓,但要求延迟低。

我个人习惯用UDP做设备发现。网关启动后,发一个UDP广播包,告诉局域网里的设备「我上线了」。设备收到后,用UDP回复自己的IP和端口。整个过程不到10毫秒。

// UDP广播示例
int sock = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in broadcast_addr;
broadcast_addr.sin_family = AF_INET;
broadcast_addr.sin_addr.s_addr = inet_addr("255.255.255.255");
broadcast_addr.sin_port = htons(12345);

// 开启广播选项
int broadcast = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));

sendto(sock, "DISCOVER", 8, 0, (struct sockaddr *)&broadcast_addr, sizeof(broadcast_addr));
close(sock);
核心对比:
  • TCP:可靠、有序、有连接、开销大。适合控制指令、文件传输。
  • UDP:不可靠、无序、无连接、开销小。适合传感器数据、心跳、广播。

4.5 实战建议——选对协议,事半功倍

在物联网网关中,我一般这样分配:

  • 与云端通信:用TCP或MQTT(基于TCP)。因为云端指令必须可靠到达。
  • 与本地传感器通信:用UDP。传感器每秒上报几十次温度,丢一两次无所谓。
  • 设备发现和配置:用UDP广播。速度快,实现简单。

你想想看,如果传感器也用TCP,每次上报都要三次握手,那CPU全花在握手上了,还怎么干正事?

嗯,这一章的内容就到这里。Wi-Fi连接管理和TCP/UDP通信是ESP32网关的基石。下一章我们会讲更高级的协议——MQTT和HTTP,到时候你会看到,它们都是在TCP/UDP之上构建的应用层协议。