八、【ESP32开发全栈指南:UDP客户端】

发布于:2025-06-10 ⋅ 阅读:(55) ⋅ 点赞:(0)

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));

    // 配置WiFi
    wifi_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-terminate
            inet_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);

    // 连接WiFi
    wifi_init_sta();

    // 启动UDP任务
    xTaskCreate(udp_client_task, "udp_client", 4096, NULL, 5, NULL);
}

4. 关键改进说明

  1. 健壮的错误处理

    • 添加了WiFi连接最大重试机制(MAX_RETRY
    • 完善的socket错误码处理
    • 接收超时设置(2秒)
  2. 网络优化

    • 使用inet_ntop替代已弃用的inet_ntoa
    • 设置SO_RCVTIMEO接收超时选项
    • 每次发送后关闭socket释放资源
  3. 增强可读性

    • 结构化日志输出
    • 动态生成测试消息(带时间戳)
    • 清晰的错误分类(ERROR/WARNING/INFO)
  4. 资源管理

    • 正确释放事件组资源
    • 安全的字符串处理(snprintf
    • 内存边界检查

5. 编译烧录

idf.py set-target esp32  # 根据实际芯片选择
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor  # 替换实际串口

6. 测试服务器示例 (Python)

# UDP_test_server.py
import socket

UDP_IP = "0.0.0.0"
UDP_PORT = 8888

sock = 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. 注意事项

  1. 配置修改

    • 替换YOUR_WIFI_SSIDYOUR_WIFI_PASSWORD
    • 根据网络环境修改SERVER_IP
    • 调整MAX_RETRY和超时时间
  2. 常见问题排查

    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抓包验证网络连通性

网站公告

今日签到

点亮在社区的每一天
去签到