Files
ESP32-Guide/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/UDP协议应用.md
2025-01-24 16:56:09 +08:00

577 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ESP32网络入门-UDP协议
## 一、概述
上一节教程[ESP32WIFI-2.TCP协议](https://www.duruofu.top/2024/03/15/4.%E7%A1%AC%E4%BB%B6%E7%9B%B8%E5%85%B3/MCU/ESP32/05.ESP32WIFI%E5%85%A5%E9%97%A8/5.2-ESP32%E7%BD%91%E7%BB%9C%E5%85%A5%E9%97%A8-TCP%E5%8D%8F%E8%AE%AE/ESP32%E7%BD%91%E7%BB%9C%E5%85%A5%E9%97%A8-TCP%E5%8D%8F%E8%AE%AE/)详细介绍了TCP协议本机我们介绍UDP的使用。
UDP和TCP在程序上的区别主要如下图
>图来自[Michael_ee](https://space.bilibili.com/1338335828)的UDP教程。
![](attachments/20240317142714.png)
最大的区别就是UDP没有建立连接的过程。
UDP和TCP的对比如下
![](attachments/20240317143536.png)
## 二、使用
> 学习了上一节TCP这部分就会很简单,足够简单就不多描述了。
### 2.1 UDP客户端
首先我们要初始化WIFI连接WIFI这是编写UDP程序的基础连接WIFI在此不再赘述。
>后面的部分,默认已经连接好网络
#### 2.1.1 创建socket
```c
// 创建socket
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) // 创建失败返回-1
{
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
return;
}
```
#### 2.1.2 配置发送目标服务器信息
```c
// 目标服务器地址端口
#define UDP_SREVER_ADDR "255.255.255.255"
#define UDP_SREVER_PORT 8080
// 设置服务器(IPV4)
struct sockaddr_in server_config;
server_config.sin_addr.s_addr = inet_addr(UDP_SREVER_ADDR);
server_config.sin_family = AF_INET;
server_config.sin_port = htons(UDP_SREVER_PORT); // 宏htons 用于将主机的无符号短整型数据转换成网络字节顺序(小端转大端)
```
服务器的地址选择 `255.255.255.255`意思是不指定局域网内的某一设备局域网所有的设备如果监听了这个端口号那么都可以收到ESP32发来的消息
#### 2.1.3 发送数据
```c
// 发送数据
const char *data = "Hello World!";
int err = sendto(sock, data, strlen(data), 0, (struct sockaddr *)&server_config, sizeof(server_config));
if (err < 0)
{
ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
close(sock);
}
```
#### 2.1.4 接收数据
```c
char rx_buffer[1024];
// 接收数据,并发回
while(1)
{
// 清空缓存
memset(rx_buffer, 0, sizeof(rx_buffer));
// 接收数据
struct sockaddr_in source_addr;
socklen_t socklen = sizeof(source_addr);
int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
if (len < 0)
{
ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
break;
}
else
{
// 打印接收到的数据
ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);
}
}
```
### 2.2 UDP服务端
>后面的部分,默认已经连接好网络
#### 2.2.1 创建socket
```c
// 创建socket
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) // 创建失败返回-1
{
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
return;
}
```
#### 2.2.2 配置服务器信息
``` c
// 服务器地址端口
#define UDP_SREVER_PORT 8080
// 设置服务器(IPV4)
struct sockaddr_in server_config;
server_config.sin_addr.s_addr = htonl(INADDR_ANY);
server_config.sin_family = AF_INET;
server_config.sin_port = htons(UDP_SREVER_PORT); // 宏htons 用于将主机的无符号短整型数据转换成网络字节顺序(小端转大端)
```
这里就不需要配置服务器IP了使用ESP32自身分配到的IP。
#### 2.2.3 绑定端口
```c
// 绑定端口
int err = bind(sock, (struct sockaddr *)&server_config, sizeof(server_config));
if (err < 0)
{
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
}
ESP_LOGI(TAG, "Socket bound, port %d", UDP_SREVER_PORT);
```
#### 2.2.4 接收数据
```c
char rx_buffer[1024];
// 接收数据,并发回
while(1)
{
// 清空缓存
memset(rx_buffer, 0, sizeof(rx_buffer));
// 接收数据
struct sockaddr_in source_addr;
socklen_t socklen = sizeof(source_addr);
int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
if (len < 0)
{
ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
break;
}
else
{
// 打印接收到的数据
ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);
}
}
```
#### 2.2.5 发送数据
```c
// 发送数据
int err = sendto(sock, rx_buffer, len, 0, (struct sockaddr *)&source_addr, sizeof(source_addr));
if (err < 0)
{
ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
break;
}
ESP_LOGI(TAG, "Send success");
```
>有没有发现这服务端代码相比UDP客户端除了多一个使用`bind`函数绑定地址,剩下都一模一样。(可不是我在水字数😀)
这就涉及到UDP本身的知识了从套接字编程的角度来看它是完全相同的。由于UDP是无连接的因此服务器和客户端都将使用相同的sendto和recvfrom方法。
但是我们在通信时一般来说以主从通信为主我们更喜欢把主机称作为服务端。相比客户端在DUP里服务端唯一的不同就是服务端的端口和地址是确定的。这样其他客户端才能准确无误向服务端发送消息。
所以上面的示例代码了,服务端多了一个绑定端口的步骤。其余的都是一样的。
使用我们使用网络调试助手会发现,协议类型一栏不分客户端和服务端。
![](attachments/20240317155554.png)
## 三、案例(完整代码)
### 3.1 UDP客户端
>代码:https://github.com/DuRuofu/ESP32_Learning/tree/master/05.wifi/wifi_udp_client
```c
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "esp_mac.h"
#include "esp_netif.h"
#include <sys/socket.h>
// 要连接的WIFI
#define ESP_WIFI_STA_SSID "duruofu_win10"
#define ESP_WIFI_STA_PASSWD "1234567890"
// 目标服务器地址端口
#define UDP_SREVER_ADDR "255.255.255.255"
#define UDP_SREVER_PORT 8080
static const char *TAG = "main";
void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
static uint8_t connect_count = 0;
// WIFI 启动成功
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
ESP_ERROR_CHECK(esp_wifi_connect());
}
// WIFI 连接失败
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
connect_count++;
if (connect_count < 6)
{
vTaskDelay(1000 / portTICK_PERIOD_MS);
ESP_ERROR_CHECK(esp_wifi_connect());
}
else
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
}
}
// WIFI 连接成功(获取到了IP)
if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
}
}
// udp客户端
static void udp_client_task(void *pvParameters)
{
// 等待wifi连接成功(暂时这样处理)
vTaskDelay(5000 / portTICK_PERIOD_MS);
ESP_LOGI("udp_client_task", "udp_client_task start");
// 创建socket
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) // 创建失败返回-1
{
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
return;
}
// 设置服务器(IPV4)
struct sockaddr_in server_config;
server_config.sin_addr.s_addr = inet_addr(UDP_SREVER_ADDR);
server_config.sin_family = AF_INET;
server_config.sin_port = htons(UDP_SREVER_PORT); // 宏htons 用于将主机的无符号短整型数据转换成网络字节顺序(小端转大端)
// 发送数据
const char *data = "Hello World!";
int err = sendto(sock, data, strlen(data), 0, (struct sockaddr *)&server_config, sizeof(server_config));
if (err < 0)
{
ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
close(sock);
}
char rx_buffer[1024];
// 接收数据,并发回
while(1)
{
// 清空缓存
memset(rx_buffer, 0, sizeof(rx_buffer));
// 接收数据
struct sockaddr_in source_addr;
socklen_t socklen = sizeof(source_addr);
int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
if (len < 0)
{
ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
break;
}
else
{
// 打印接收到的数据
ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);
}
}
// 关闭socket
close(sock);
vTaskDelete(NULL);
}
// wifi初始化
static void wifi_sta_init(void)
{
ESP_ERROR_CHECK(esp_netif_init());
// 注册事件(wifi启动成功)
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
// 注册事件(wifi连接失败)
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
// 注册事件(wifi连接失败)
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));
// 初始化STA设备
esp_netif_create_default_wifi_sta();
/*Initialize WiFi */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
// WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
//----------------配置阶段-------------------
// 初始化WIFI设备( 为 WiFi 驱动初始化 WiFi 分配资源,如 WiFi 控制结构、RX/TX 缓冲区、WiFi NVS 结构等,这个 WiFi 也启动 WiFi 任务。必须先调用此API然后才能调用所有其他WiFi API)
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
// STA详细配置
wifi_config_t sta_config = {
.sta = {
.ssid = ESP_WIFI_STA_SSID,
.password = ESP_WIFI_STA_PASSWD,
.bssid_set = false,
},
};
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
//----------------启动阶段-------------------
ESP_ERROR_CHECK(esp_wifi_start());
//----------------配置省电模式-------------------
// 不省电(数据传输会更快)
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
}
void app_main(void)
{
// Initialize 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);
// 创建默认事件循环
ESP_ERROR_CHECK(esp_event_loop_create_default());
// 配置启动WIFI
wifi_sta_init();
// 创建TCP客户端任务
xTaskCreate(udp_client_task, "udp_client_task", 4096, NULL, 5, NULL);
}
```
效果:
![](attachments/20240317151255.png)
### 3.2 UDP服务端
>代码https://github.com/DuRuofu/ESP32_Learning/tree/master/05.wifi/wifi_udp_server
```c
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "esp_mac.h"
#include "esp_netif.h"
#include <sys/socket.h>
#include "lwip/err.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>
// 要连接的WIFI
#define ESP_WIFI_STA_SSID "duruofu_win10"
#define ESP_WIFI_STA_PASSWD "1234567890"
// 服务器端口
#define UDP_SREVER_PORT 8080
static const char *TAG = "main";
void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
static uint8_t connect_count = 0;
// WIFI 启动成功
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
ESP_ERROR_CHECK(esp_wifi_connect());
}
// WIFI 连接失败
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
connect_count++;
if (connect_count < 6)
{
vTaskDelay(1000 / portTICK_PERIOD_MS);
ESP_ERROR_CHECK(esp_wifi_connect());
}
else
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
}
}
// WIFI 连接成功(获取到了IP)
if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
}
}
// udp客户端
static void udp_server_task(void *pvParameters)
{
// 等待wifi连接成功(暂时这样处理)
vTaskDelay(5000 / portTICK_PERIOD_MS);
ESP_LOGI("udp_server_task", "udp_server_task start");
// 创建socket
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) // 创建失败返回-1
{
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
return;
}
// 设置服务器(IPV4)
struct sockaddr_in server_config;
server_config.sin_addr.s_addr = htonl(INADDR_ANY);
server_config.sin_family = AF_INET;
server_config.sin_port = htons(UDP_SREVER_PORT); // 宏htons 用于将主机的无符号短整型数据转换成网络字节顺序(小端转大端)
// 绑定端口
int err = bind(sock, (struct sockaddr *)&server_config, sizeof(server_config));
if (err < 0)
{
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
}
ESP_LOGI(TAG, "Socket bound, port %d", UDP_SREVER_PORT);
char rx_buffer[1024];
// 接收数据,并发回
while(1)
{
// 清空缓存
memset(rx_buffer, 0, sizeof(rx_buffer));
// 接收数据
struct sockaddr_in source_addr;
socklen_t socklen = sizeof(source_addr);
int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
if (len < 0)
{
ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
break;
}
else
{
// 打印接收到的数据
ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);
// 发送数据
int err = sendto(sock, rx_buffer, len, 0, (struct sockaddr *)&source_addr, sizeof(source_addr));
if (err < 0)
{
ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
break;
}
ESP_LOGI(TAG, "Send success");
}
}
// 关闭socket
close(sock);
vTaskDelete(NULL);
}
// wifi初始化
static void wifi_sta_init(void)
{
ESP_ERROR_CHECK(esp_netif_init());
// 注册事件(wifi启动成功)
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
// 注册事件(wifi连接失败)
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
// 注册事件(wifi连接失败)
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));
// 初始化STA设备
esp_netif_create_default_wifi_sta();
/*Initialize WiFi */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
// WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
//----------------配置阶段-------------------
// 初始化WIFI设备( 为 WiFi 驱动初始化 WiFi 分配资源,如 WiFi 控制结构、RX/TX 缓冲区、WiFi NVS 结构等,这个 WiFi 也启动 WiFi 任务。必须先调用此API然后才能调用所有其他WiFi API)
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
// STA详细配置
wifi_config_t sta_config = {
.sta = {
.ssid = ESP_WIFI_STA_SSID,
.password = ESP_WIFI_STA_PASSWD,
.bssid_set = false,
},
};
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
//----------------启动阶段-------------------
ESP_ERROR_CHECK(esp_wifi_start());
//----------------配置省电模式-------------------
// 不省电(数据传输会更快)
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
}
void app_main(void)
{
// Initialize 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);
// 创建默认事件循环
ESP_ERROR_CHECK(esp_event_loop_create_default());
// 配置启动WIFI
wifi_sta_init();
// 创建TCP客户端任务
xTaskCreate(udp_server_task, "udp_server_task", 4096, NULL, 5, NULL);
}
```
效果:
![](attachments/20240317155445.png)
# 参考链接
1. https://www.bilibili.com/video/BV1iG411j7Gq/?spm_id_from=333.788&vd_source=ef5a0ab0106372751602034cdd9ab98e