1. 環境準備
- 安裝ESP-IDF v4.4+ (官方指南)
- 確保Python 3.7+ 和Git已安裝
2. 創建項目
idf.py create-project udp_client
cd udp_client
3. 完整優化代碼 (main/main.c
)
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>// 配置區 ========================================
#define WIFI_SSID "YOUR_WIFI_SSID"
#define WIFI_PASS "YOUR_WIFI_PASSWORD"
#define SERVER_IP "192.168.1.100" // 目標服務器IP
#define SERVER_PORT 8888 // 目標端口
#define MAX_RETRY 5 // WiFi最大重連次數
// ===============================================static const char *TAG = "UDP_Client";
static EventGroupHandle_t s_wifi_event_group;
static int s_retry_num = 0;/* 事件組位定義 */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1// WiFi事件處理函數
static void 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) {if (s_retry_num < MAX_RETRY) {esp_wifi_connect();s_retry_num++;ESP_LOGI(TAG, "Retry connecting to AP. Attempt %d/%d", s_retry_num, MAX_RETRY);} else {xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);ESP_LOGE(TAG, "Failed to connect after %d attempts", MAX_RETRY);}} 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(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));s_retry_num = 0;xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);}
}// WiFi初始化
void wifi_init_sta(void) {s_wifi_event_group = xEventGroupCreate();ESP_ERROR_CHECK(esp_netif_init());ESP_ERROR_CHECK(esp_event_loop_create_default());esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&cfg));// 注冊事件處理器esp_event_handler_instance_t instance_any_id;esp_event_handler_instance_t instance_got_ip;ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&event_handler,NULL,&instance_any_id));ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,IP_EVENT_STA_GOT_IP,&event_handler,NULL,&instance_got_ip));// 配置WiFiwifi_config_t wifi_config = {.sta = {.ssid = WIFI_SSID,.password = WIFI_PASS,.threshold.authmode = WIFI_AUTH_WPA2_PSK,.pmf_cfg = {.capable = true,.required = false},},};ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));ESP_ERROR_CHECK(esp_wifi_start());ESP_LOGI(TAG, "WiFi initialization complete. Connecting to AP...");// 等待連接結果EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,pdFALSE,pdFALSE,portMAX_DELAY);if (bits & WIFI_CONNECTED_BIT) {ESP_LOGI(TAG, "Connected to AP SSID: %s", WIFI_SSID);} else if (bits & WIFI_FAIL_BIT) {ESP_LOGE(TAG, "Failed to connect to SSID: %s", WIFI_SSID);} else {ESP_LOGE(TAG, "Unexpected event");}// 清理事件組vEventGroupDelete(s_wifi_event_group);
}// UDP客戶端任務
void udp_client_task(void *pvParameters) {ESP_LOGI(TAG, "Starting UDP client task");struct sockaddr_in dest_addr = {.sin_addr.s_addr = inet_addr(SERVER_IP),.sin_family = AF_INET,.sin_port = htons(SERVER_PORT)};char rx_buffer[128];char tx_buffer[50];char addr_str[INET_ADDRSTRLEN];inet_ntop(AF_INET, &dest_addr.sin_addr, addr_str, sizeof(addr_str));ESP_LOGI(TAG, "Target server: %s:%d", addr_str, SERVER_PORT);while (1) {int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);if (sock < 0) {ESP_LOGE(TAG, "Failed to create socket: errno %d", errno);vTaskDelay(2000 / portTICK_PERIOD_MS);continue;}// 設置超時選項(2秒)struct timeval timeout = {.tv_sec = 2,.tv_usec = 0};setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));// 發送數據snprintf(tx_buffer, sizeof(tx_buffer), "Hello #%d", (int)(xTaskGetTickCount()/1000));int err = sendto(sock, tx_buffer, strlen(tx_buffer), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));if (err < 0) {ESP_LOGE(TAG, "Send error: errno %d", errno);close(sock);vTaskDelay(2000 / portTICK_PERIOD_MS);continue;}ESP_LOGI(TAG, "Sent: %s", tx_buffer);// 接收響應struct sockaddr_in source_addr;socklen_t addr_len = sizeof(source_addr);int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &addr_len);if (len > 0) {rx_buffer[len] = 0; // Null-terminateinet_ntop(AF_INET, &source_addr.sin_addr, addr_str, sizeof(addr_str));ESP_LOGI(TAG, "Received %d bytes from %s:%d", len, addr_str, ntohs(source_addr.sin_port));ESP_LOGI(TAG, "Data: %s", rx_buffer);} else if (len == 0) {ESP_LOGW(TAG, "Connection closed by server");} else {if (errno == EAGAIN || errno == EWOULDBLOCK) {ESP_LOGW(TAG, "Receive timeout");} else {ESP_LOGE(TAG, "Receive failed: errno %d", errno);}}close(sock);ESP_LOGI(TAG, "Next message in 3 seconds...");vTaskDelay(3000 / portTICK_PERIOD_MS);}vTaskDelete(NULL);
}void app_main() {// 初始化NVS存儲esp_err_t ret = nvs_flash_init();if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {ESP_ERROR_CHECK(nvs_flash_erase());ret = nvs_flash_init();}ESP_ERROR_CHECK(ret);// 連接WiFiwifi_init_sta();// 啟動UDP任務xTaskCreate(udp_client_task, "udp_client", 4096, NULL, 5, NULL);
}
4. 關鍵改進說明
-
健壯的錯誤處理:
- 添加了WiFi連接最大重試機制(
MAX_RETRY
) - 完善的socket錯誤碼處理
- 接收超時設置(2秒)
- 添加了WiFi連接最大重試機制(
-
網絡優化:
- 使用
inet_ntop
替代已棄用的inet_ntoa
- 設置
SO_RCVTIMEO
接收超時選項 - 每次發送后關閉socket釋放資源
- 使用
-
增強可讀性:
- 結構化日志輸出
- 動態生成測試消息(帶時間戳)
- 清晰的錯誤分類(ERROR/WARNING/INFO)
-
資源管理:
- 正確釋放事件組資源
- 安全的字符串處理(
snprintf
) - 內存邊界檢查
5. 編譯燒錄
idf.py set-target esp32 # 根據實際芯片選擇
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor # 替換實際串口
6. 測試服務器示例 (Python)
# UDP_test_server.py
import socketUDP_IP = "0.0.0.0"
UDP_PORT = 8888sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))print(f"Listening on {UDP_PORT}")
while True:data, addr = sock.recvfrom(1024)print(f"Received: {data.decode()} from {addr}")sock.sendto(b"ACK:" + data, addr)
7. 注意事項
-
配置修改:
- 替換
YOUR_WIFI_SSID
和YOUR_WIFI_PASSWORD
- 根據網絡環境修改
SERVER_IP
- 調整
MAX_RETRY
和超時時間
- 替換
-
常見問題排查:
I (1845) UDP_Client: Got IP: 192.168.1.101 I (1845) UDP_Client: Starting UDP client task I (1845) UDP_Client: Target server: 192.168.1.100:8888 I (1855) UDP_Client: Sent: Hello #18 W (2855) UDP_Client: Receive timeout
- 檢查服務器IP/端口是否正確
- 確認服務器防火墻允許UDP流量
- 使用Wireshark抓包驗證網絡連通性