一、TCP与UDP核心差异
特性 | TCP | UDP |
---|---|---|
连接方式 | 面向连接 (需三次握手) | 无连接 |
可靠性 | 可靠传输 (重传/排序/校验) | 尽力交付 (不保证可靠性) |
实时性 | 延迟较高 | 低延迟,实时性强 |
传输效率 | 协议开销大 | 头部开销小 (仅8字节) |
连接类型 | 点对点 | 支持广播/多播 |
资源占用 | 高 (需维护连接状态) | 极低 |
📌 关键场景选择:物联网传感器上报(UDP)、OTA升级(TCP)、音视频传输(UDP)
二、ESP32网络栈架构
-
lwIP轻量级TCP/IP栈
- 开源协议栈,专为嵌入式优化
- ESP-IDF修改版本:
esp-lwip
- 支持功能:
- BSD Socket API(推荐)
- Netconn API(不推荐直接使用)
-
核心文档
- ESP-NETIF 编程指南
- lwIP 配置指南
三、BSD Socket API 关键接口
头文件:lwip/sockets.h
函数 | 功能说明 | 返回值 |
---|---|---|
socket() | 创建通信端点 | 套接字描述符 |
bind() | 绑定IP和端口 | 0成功/-1失败 |
recvfrom() | 接收数据(来源地址) | 接收字节数 |
sendto() | 发送数据到指定地址 | 发送字节数 |
setsockopt() | 设置套接字选项(超时/广播等) | 0成功/-1失败 |
close() | 关闭套接字 | 0成功/-1失败 |
⚠️ 重要限制:
select()
通过VFS组件实现poll()
底层调用select()
- 文件操作需使用VFS接口 (
read()/write()
)
四、UDP服务端实现详解
4.1 工作流程
4.2 代码实现
// 配置UDP服务器参数
#define UDP_PORT 3333
#define RX_BUFFER_SIZE 128
void udp_server_task(void *pvParameters) {
// 1. 创建套接字
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (sock < 0) {
ESP_LOGE(TAG, "创建套接字失败: errno %d", errno);
vTaskDelete(NULL);
}
// 2. 配置服务器地址
struct sockaddr_in server_addr = {
.sin_family = AF_INET,
.sin_port = htons(UDP_PORT),
.sin_addr.s_addr = INADDR_ANY
};
// 3. 绑定端口
if (bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
ESP_LOGE(TAG, "绑定失败: errno %d", errno);
close(sock);
vTaskDelete(NULL);
}
ESP_LOGI(TAG, "UDP服务已启动, 端口:%d", UDP_PORT);
// 4. 数据循环处理
char rx_buffer[RX_BUFFER_SIZE];
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
while (1) {
// 接收数据
int len = recvfrom(sock, rx_buffer, RX_BUFFER_SIZE - 1, 0,
(struct sockaddr *)&client_addr, &addr_len);
if (len < 0) {
ESP_LOGE(TAG, "接收错误: errno %d", errno);
continue;
}
// 数据处理
rx_buffer[len] = '\0';
ESP_LOGI(TAG, "收到来自 %s:%d 的 %d 字节数据",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port),
len);
// 发送响应 (回显模式)
sendto(sock, rx_buffer, len, 0,
(struct sockaddr *)&client_addr, addr_len);
}
close(sock);
vTaskDelete(NULL);
}
4.3 关键配置项
menuconfig 设置:
# 启用IPv4
CONFIG_EXAMPLE_IPV4=y
# 设置UDP端口
CONFIG_EXAMPLE_PORT=3333
# WiFi配置 (Station模式)
CONFIG_ESP_WIFI_SSID="your_SSID"
CONFIG_ESP_WIFI_PASSWORD="your_password"
AP模式初始化:
void wifi_init_softap() {
// ... [AP初始化代码]
wifi_config_t ap_config = {
.ap = {
.ssid = "ESP32_UDP_Server",
.password = "esp32pass",
.max_connection = 4,
.authmode = WIFI_AUTH_WPA2_PSK
}
};
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &ap_config));
ESP_ERROR_CHECK(esp_wifi_start());
}
五、模式对比与选择
特性 | Station模式 | AP模式 |
---|---|---|
网络角色 | 连接现有WiFi | 自建热点 |
适用场景 | 设备接入互联网 | 局域网直连调试 |
IP获取 | DHCP (通常) | 固定IP (默认192.168.4.1) |
客户端连接 | 需路由器支持 | 设备直接连接ESP32 |
典型应用 | 云端数据上报 | 设备快速配网 |
✅ 推荐实践:
- 产品环境使用Station模式
- 开发调试使用AP模式避免路由器依赖
- 双模式切换可通过
esp_wifi_set_mode(WIFI_MODE_APSTA)
六、常见问题解决
-
绑定失败 (errno 98)
- 原因:端口被占用或套接字未关闭
- 解决方案:
int opt = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
-
数据接收超时
- 设置接收超时:
struct timeval timeout = { .tv_sec = 5 }; setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
- 设置接收超时:
-
多客户端管理
- UDP无连接状态,需通过
client_addr
区分客户端 - 建议使用白名单机制:
// 校验客户端IP if(client_addr.sin_addr.s_addr != expected_ip) { ESP_LOGW(TAG, "非法客户端访问"); continue; }
- UDP无连接状态,需通过
💡 最佳实践总结:
- 使用
SO_BROADCAST
选项开启广播功能- 定期检查套接字有效性 (心跳机制)
- 大数据传输时添加分包序号校验
- 生产环境启用WPA3加密通信
完整示例代码:ESP-IDF UDP示例