一、TCP与UDP核心差异
|
特性 |
TCP |
UDP |
|---|---|---|
| 「连接方式」 |
面向连接 (需三次握手) |
无连接 |
| 「可靠性」 |
可靠传输 (重传/排序/校验) |
尽力交付 (不保证可靠性) |
| 「实时性」 |
延迟较高 |
低延迟,实时性强 |
| 「传输效率」 |
协议开销大 |
头部开销小 (仅8字节) |
| 「连接类型」 |
点对点 |
支持广播/多播 |
| 「资源占用」 |
高 (需维护连接状态) |
极低 |
❝
📌 「关键场景选择」:物联网传感器上报(UDP)、OTA升级(TCP)、音视频传输(UDP)
❞
二、ESP32网络栈架构
-
「lwIP轻量级TCP/IP栈」
-
BSD Socket API(推荐) -
Netconn API(不推荐直接使用) -
开源协议栈,专为嵌入式优化 -
ESP-IDF修改版本: esp-lwip -
支持功能: -
「核心文档」
-
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;
}
❝
💡 「最佳实践总结」:
使用 SO_BROADCAST选项开启广播功能定期检查套接字有效性 (心跳机制) 大数据传输时添加分包序号校验 生产环境启用WPA3加密通信 ❞
完整示例代码:ESP-IDF UDP示例