天天看點

ESP32學習筆記(10)——UDP用戶端一、TCP與UDP優缺點二、概述三、API說明四、UDP用戶端

一、TCP與UDP優缺點

1、TCP面向連接配接(如打電話要先撥号建立連接配接);UDP是無連接配接的,即發送資料之前不需要建立連接配接。

2、TCP提供可靠的服務。也就是說,通過TCP連接配接傳送的資料,無差錯,不丢失,不重複,且按序到達;UDP盡最大努力傳遞,即不保證可靠傳遞。

TCP通過校驗和,重傳控制,序号辨別,滑動視窗、确認應答實作可靠傳輸。如丢包時的重發控制,還可以對次序亂掉的分包進行順序控制。

3、UDP具有較好的實時性,工作效率比TCP高,适用于對高速傳輸和實時性有較高的通信或廣播通信。

4、每一條TCP連接配接隻能是點到點的;UDP支援一對一,一對多,多對一和多對多的互動通信。

5、TCP對系統資源要求較多,UDP對系統資源要求較少。

二、概述

ESP-IDF使用開源 lwIP輕量級的TCP / IP堆棧。ESP-IDF版本lwIP(esp-lwip)與上遊項目相比有一些修改和補充。

ESP-IDF支援以下功能 lwIP TCP / IP堆棧功能:

  • BSD套接字API
  • Netconn API已啟用,但ESP-IDF應用程式未正式支援

BSD套接字API

BSD套接字API是一個通用的跨平台TCP / IP套接字API,該API起源于UNIX的Berkeley标準發行版,但現在已在POSIX規範的一部分中進行了标準化。BSD套接字有時稱為POSIX套接字或Berkeley套接字。
正如ESP-IDF中實施的那樣,lwIP支援BSD套接字API的所有常用用法。

ESP-IDF 程式設計指南——ESP-NETIF

ESP-IDF 程式設計指南——lwIP

三、API說明

以下 BSD Socket 接口位于 lwip/lwip/src/include/lwip/sockets.h。

  • socket()

  • bind()

  • accept()

  • shutdown()

  • getpeername()

  • getsockopt()

    setsockopt()

    (請參閱套接字選項)
  • close()

    (通過虛拟檔案系統元件)
  • read()

    readv()

    write()

    writev()

    (經由虛拟檔案系統部件)
  • recv()

    recvmsg()

    recvfrom()

  • send()

    sendmsg()

    sendto()

  • select()

    (通過虛拟檔案系統元件)
  • poll()

    (注意:在ESP-IDF上,

    poll()

    是通過内部調用select來實作的,是以,

    select()

    如果有可用的方法選擇,建議直接使用。)
  • fcntl()

    (請參閱fcntl)

非标準功能:

  • ioctl()

    (請參閱ioctls)

四、UDP用戶端

4.1 主要流程

ESP32學習筆記(10)——UDP用戶端一、TCP與UDP優缺點二、概述三、API說明四、UDP用戶端

4.1.1 第一步:建立socket

int addr_family = 0;
int ip_protocol = 0;

addr_family = AF_INET;
ip_protocol = IPPROTO_IP;

int sock =  socket(addr_family, SOCK_DGRAM, ip_protocol);
if(sock < 0) 
{
    ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
}
           

4.1.2 第二步:配置将要連接配接的伺服器資訊(端口和IP)

伺服器的位址選擇

255.255.255.255

,意思是不指定區域網路内的某一裝置,區域網路所有的裝置如果監聽了這個端口号,那麼都可以收到esp32發來的消息。

#define HOST_IP_ADDR    "255.255.255.255"    // 要連接配接UDP伺服器位址,預設使用廣播
#define UDP_PORT        3333                 // 要連接配接UDP伺服器端口号

struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR);
dest_addr.sin_port = htons(UDP_PORT);
           

4.1.3 第三步:發送資料

static const char *payload = "Message from ESP32 ";

int err = sendto(sock, payload, strlen(payload), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
if(err < 0) 
{
    ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
    close(sock);
}
           

4.1.4 第四步:接收資料

char rx_buffer[128];
char host_ip[] = HOST_IP_ADDR;

while(1)
{
    ······
    // 清空緩存
    memset(rx_buffer, 0, sizeof(rx_buffer));

    struct sockaddr_in source_addr; // Large enough for both IPv4 or IPv6
    socklen_t socklen = sizeof(source_addr);
    int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer), 0, (struct sockaddr *)&source_addr, &socklen);

    // Error occurred during receiving
    if(len < 0) 
    {
        ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
        break;
    }
    // Data received
    else 
    {
        ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
        ESP_LOGI(TAG, "%s", rx_buffer);
    }
}
close(sock);
           

4.2 配置SSID和密碼連接配接WIFI建立UDP用戶端

ESP32學習筆記(10)——UDP用戶端一、TCP與UDP優缺點二、概述三、API說明四、UDP用戶端

使用 esp-idf\examples\protocols\sockets\udp_client 中的例程

/* BSD Socket API Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>
#include "addr_from_stdin.h"

#if defined(CONFIG_EXAMPLE_IPV4)
#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV4_ADDR
#elif defined(CONFIG_EXAMPLE_IPV6)
#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV6_ADDR
#else
#define HOST_IP_ADDR ""
#endif

#define PORT CONFIG_EXAMPLE_PORT

static const char *TAG = "example";
static const char *payload = "Message from ESP32 ";


static void udp_client_task(void *pvParameters)
{
    char rx_buffer[128];
    char host_ip[] = HOST_IP_ADDR;
    int addr_family = 0;
    int ip_protocol = 0;

    while (1) {

#if defined(CONFIG_EXAMPLE_IPV4)
        struct sockaddr_in dest_addr;
        dest_addr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR);
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(PORT);
        addr_family = AF_INET;
        ip_protocol = IPPROTO_IP;
#elif defined(CONFIG_EXAMPLE_IPV6)
        struct sockaddr_in6 dest_addr = { 0 };
        inet6_aton(HOST_IP_ADDR, &dest_addr.sin6_addr);
        dest_addr.sin6_family = AF_INET6;
        dest_addr.sin6_port = htons(PORT);
        dest_addr.sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE);
        addr_family = AF_INET6;
        ip_protocol = IPPROTO_IPV6;
#elif defined(CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN)
        struct sockaddr_in6 dest_addr = { 0 };
        ESP_ERROR_CHECK(get_addr_from_stdin(PORT, SOCK_DGRAM, &ip_protocol, &addr_family, &dest_addr));
#endif

        int sock = socket(addr_family, SOCK_DGRAM, ip_protocol);
        if (sock < 0) {
            ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Socket created, sending to %s:%d", HOST_IP_ADDR, PORT);

        while (1) {

            int err = sendto(sock, payload, strlen(payload), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
            if (err < 0) {
                ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
                break;
            }
            ESP_LOGI(TAG, "Message sent");

            struct sockaddr_in source_addr; // Large enough for both IPv4 or IPv6
            socklen_t socklen = sizeof(source_addr);
            int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);

            // Error occurred during receiving
            if (len < 0) {
                ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
                break;
            }
            // Data received
            else {
                rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
                ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
                ESP_LOGI(TAG, "%s", rx_buffer);
                if (strncmp(rx_buffer, "OK: ", 4) == 0) {
                    ESP_LOGI(TAG, "Received expected message, reconnecting");
                    break;
                }
            }

            vTaskDelay(2000 / portTICK_PERIOD_MS);
        }

        if (sock != -1) {
            ESP_LOGE(TAG, "Shutting down socket and restarting...");
            shutdown(sock, 0);
            close(sock);
        }
    }
    vTaskDelete(NULL);
}

void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    ESP_ERROR_CHECK(example_connect());

    xTaskCreate(udp_client_task, "udp_client", 4096, NULL, 5, NULL);
}
           

idf.py menuconfig

配置伺服器IP和端口

ESP32學習筆記(10)——UDP用戶端一、TCP與UDP優缺點二、概述三、API說明四、UDP用戶端
ESP32學習筆記(10)——UDP用戶端一、TCP與UDP優缺點二、概述三、API說明四、UDP用戶端

配置SSID和密碼

ESP32學習筆記(10)——UDP用戶端一、TCP與UDP優缺點二、概述三、API說明四、UDP用戶端
ESP32學習筆記(10)——UDP用戶端一、TCP與UDP優缺點二、概述三、API說明四、UDP用戶端

然後

idf.py flash

編譯下載下傳

檢視列印:

ESP32學習筆記(10)——UDP用戶端一、TCP與UDP優缺點二、概述三、API說明四、UDP用戶端
ESP32學習筆記(10)——UDP用戶端一、TCP與UDP優缺點二、概述三、API說明四、UDP用戶端

4.3 使用SmartConfig連接配接WIFI建立UDP用戶端

ESP32學習筆記(10)——UDP用戶端一、TCP與UDP優缺點二、概述三、API說明四、UDP用戶端

根據 esp-idf\examples\protocols\sockets\udp_client 中的例程修改

/* BSD Socket API Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>
#include "addr_from_stdin.h"

#include "esp_smartconfig.h"

#if defined(CONFIG_EXAMPLE_IPV4)
#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV4_ADDR
#elif defined(CONFIG_EXAMPLE_IPV6)
#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV6_ADDR
#else
#define HOST_IP_ADDR ""
#endif

#define PORT CONFIG_EXAMPLE_PORT

static const char *TAG = "example";
static const char *payload = "Message from ESP32 ";

/* The event group allows multiple bits for each event,
   but we only care about one event - are we connected
   to the AP with an IP? */
static const int CONNECTED_BIT = BIT0;
static const int ESPTOUCH_DONE_BIT = BIT1;

/* FreeRTOS event group to signal when we are connected & ready to make a request */
static EventGroupHandle_t s_wifi_event_group;

static void smartconfig_example_task(void * parm);
static void udp_client_task(void *pvParameters);

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) {
        xTaskCreate(smartconfig_example_task, "smartconfig_example_task", 4096, NULL, 3, NULL);
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        esp_wifi_connect();
        xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT);
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT);
    } else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE) {
        ESP_LOGI(TAG, "Scan done");
    } else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL) {
        ESP_LOGI(TAG, "Found channel");
    } else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) {
        ESP_LOGI(TAG, "Got SSID and password");

        smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data;
        wifi_config_t wifi_config;
        uint8_t ssid[33] = { 0 };
        uint8_t password[65] = { 0 };

        bzero(&wifi_config, sizeof(wifi_config_t));
        memcpy(wifi_config.sta.ssid, evt->ssid, sizeof(wifi_config.sta.ssid));
        memcpy(wifi_config.sta.password, evt->password, sizeof(wifi_config.sta.password));
        wifi_config.sta.bssid_set = evt->bssid_set;
        if (wifi_config.sta.bssid_set == true) {
            memcpy(wifi_config.sta.bssid, evt->bssid, sizeof(wifi_config.sta.bssid));
        }

        memcpy(ssid, evt->ssid, sizeof(evt->ssid));
        memcpy(password, evt->password, sizeof(evt->password));
        ESP_LOGI(TAG, "SSID:%s", ssid);
        ESP_LOGI(TAG, "PASSWORD:%s", password);

        ESP_ERROR_CHECK( esp_wifi_disconnect() );
        ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
        ESP_ERROR_CHECK( esp_wifi_connect() );
    } else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE) {
        xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT);
    }
}

static void initialise_wifi(void)
{
    ESP_ERROR_CHECK(esp_netif_init());
    s_wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
    assert(sta_netif);

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

    ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL) );
    ESP_ERROR_CHECK( esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL) );
    ESP_ERROR_CHECK( esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL) );

    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK( esp_wifi_start() );
}

static void smartconfig_example_task(void * parm)
{
    EventBits_t uxBits;
    ESP_ERROR_CHECK( esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS) );
    smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
    ESP_ERROR_CHECK( esp_smartconfig_start(&cfg) );
    while (1) {
        uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY); 
        if(uxBits & CONNECTED_BIT) {
            ESP_LOGI(TAG, "WiFi Connected to ap");
        }
        if(uxBits & ESPTOUCH_DONE_BIT) {
            ESP_LOGI(TAG, "smartconfig over");
            xTaskCreate(udp_client_task, "udp_client", 4096, NULL, 5, NULL);
            esp_smartconfig_stop();
            vTaskDelete(NULL);
        }
    }
}

static void udp_client_task(void *pvParameters)
{
    char rx_buffer[128];
    char host_ip[] = HOST_IP_ADDR;
    int addr_family = 0;
    int ip_protocol = 0;

    while (1) {

#if defined(CONFIG_EXAMPLE_IPV4)
        struct sockaddr_in dest_addr;
        dest_addr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR);
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(PORT);
        addr_family = AF_INET;
        ip_protocol = IPPROTO_IP;
#elif defined(CONFIG_EXAMPLE_IPV6)
        struct sockaddr_in6 dest_addr = { 0 };
        inet6_aton(HOST_IP_ADDR, &dest_addr.sin6_addr);
        dest_addr.sin6_family = AF_INET6;
        dest_addr.sin6_port = htons(PORT);
        dest_addr.sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE);
        addr_family = AF_INET6;
        ip_protocol = IPPROTO_IPV6;
#elif defined(CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN)
        struct sockaddr_in6 dest_addr = { 0 };
        ESP_ERROR_CHECK(get_addr_from_stdin(PORT, SOCK_DGRAM, &ip_protocol, &addr_family, &dest_addr));
#endif

        int sock = socket(addr_family, SOCK_DGRAM, ip_protocol);
        if (sock < 0) {
            ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Socket created, sending to %s:%d", HOST_IP_ADDR, PORT);

        while (1) {

            int err = sendto(sock, payload, strlen(payload), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
            if (err < 0) {
                ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
                break;
            }
            ESP_LOGI(TAG, "Message sent");

            struct sockaddr_in source_addr; // Large enough for both IPv4 or IPv6
            socklen_t socklen = sizeof(source_addr);
            int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);

            // Error occurred during receiving
            if (len < 0) {
                ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
                break;
            }
            // Data received
            else {
                rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
                ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
                ESP_LOGI(TAG, "%s", rx_buffer);
                if (strncmp(rx_buffer, "OK: ", 4) == 0) {
                    ESP_LOGI(TAG, "Received expected message, reconnecting");
                    break;
                }
            }

            vTaskDelay(2000 / portTICK_PERIOD_MS);
        }

        if (sock != -1) {
            ESP_LOGE(TAG, "Shutting down socket and restarting...");
            shutdown(sock, 0);
            close(sock);
        }
    }
    vTaskDelete(NULL);
}

void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    initialise_wifi();
    // ESP_ERROR_CHECK(esp_netif_init());
    // ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    // ESP_ERROR_CHECK(example_connect());

    // xTaskCreate(udp_client_task, "udp_client", 4096, NULL, 5, NULL);
}
           

idf.py menuconfig

配置伺服器IP和端口

ESP32學習筆記(10)——UDP用戶端一、TCP與UDP優缺點二、概述三、API說明四、UDP用戶端
ESP32學習筆記(10)——UDP用戶端一、TCP與UDP優缺點二、概述三、API說明四、UDP用戶端

然後

idf.py flash

編譯下載下傳

檢視列印:

ESP32學習筆記(10)——UDP用戶端一、TCP與UDP優缺點二、概述三、API說明四、UDP用戶端
ESP32學習筆記(10)——UDP用戶端一、TCP與UDP優缺點二、概述三、API說明四、UDP用戶端

• 由 Leung 寫于 2021 年 4 月 30 日

• 參考:第十七章 ESP32的UDP廣播

    樂鑫Esp32學習之旅⑧ esp32上實作本地 UDP 用戶端和服務端角色,在區域網路内實作通訊

    ESP32 開發筆記(三)源碼示例 22_WIFI_STA_UDP 在站模式STA下實作UDP通訊

    ESP32UDP 通信

繼續閱讀