1 简介
开发板为ESP32-DevKitC
程序为官方示例:esp-idf\examples\protocols\http_server\ws_echo_server
创建方法参照:ESP32——开发环境搭建ESP-IDF+VSCODE 中hello_world示例
2 例程测试
2.1 WIFI配置
点击“”按钮进行SDK参数设置,只需要修改下图中WIFI SSID 和 WIFI Password 两项
点击“保存”,这里其实就是修改ws_echo_server目录下sdkconfig配置文件。
2.2 运行和测试
点击“”按钮编译、下载和监视,监视器中出现如下信息,可以看到服务端地址和端口分别为"192.168.1.18"和"80"。
打开在线测试工具Websocket在线测试-Websocket接口测试-Websocket模拟请求工具
地址栏中输入:ws://192.168.1.18:80/ws ,发送消息栏中输入hello ,点击“发送消息”按钮,页面中出现如下回应信息
VSCODE窗口中也出现了相应接收信息。
3 例程分析
3.1 示例说明
关于官方例程都可以查看README.md说明文件,这个例子简单说就是开发板作为Websocket服务端,利用Websocket客户端测试工具发送信息进行测试。
3.2 ws_echo_server.c源码
/* WebSocket Echo Server 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 <esp_wifi.h>
#include <esp_event.h>
#include <esp_log.h>
#include <esp_system.h>
#include <nvs_flash.h>
#include <sys/param.h>
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_eth.h"
#include "protocol_examples_common.h"
#include <esp_http_server.h>
/* A simple example that demonstrates using websocket echo server
*/
static const char *TAG = "ws_echo_server";
/*
* Structure holding server handle
* and internal socket fd in order
* to use out of request send
*/
struct async_resp_arg {
httpd_handle_t hd;
int fd;
};
/*
* async send function, which we put into the httpd work queue
*/
static void ws_async_send(void *arg)
{
static const char * data = "Async data";
struct async_resp_arg *resp_arg = arg;
httpd_handle_t hd = resp_arg->hd;
int fd = resp_arg->fd;
httpd_ws_frame_t ws_pkt;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.payload = (uint8_t*)data;
ws_pkt.len = strlen(data);
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
httpd_ws_send_frame_async(hd, fd, &ws_pkt);
free(resp_arg);
}
static esp_err_t trigger_async_send(httpd_handle_t handle, httpd_req_t *req)
{
struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
resp_arg->hd = req->handle;
resp_arg->fd = httpd_req_to_sockfd(req);
return httpd_queue_work(handle, ws_async_send, resp_arg);
}
/*
* This handler echos back the received ws data
* and triggers an async send if certain message received
*/
static esp_err_t echo_handler(httpd_req_t *req)
{
uint8_t buf[128] = { 0 };
httpd_ws_frame_t ws_pkt;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.payload = buf;
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 128);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
return ret;
}
ESP_LOGI(TAG, "Got packet with message: %s", ws_pkt.payload);
ESP_LOGI(TAG, "Packet type: %d", ws_pkt.type);
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT &&
strcmp((char*)ws_pkt.payload,"Trigger async") == 0) {
return trigger_async_send(req->handle, req);
}
ret = httpd_ws_send_frame(req, &ws_pkt);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
}
return ret;
}
static const httpd_uri_t ws = {
.uri = "/ws",
.method = HTTP_GET,
.handler = echo_handler,
.user_ctx = NULL,
.is_websocket = true
};
static httpd_handle_t start_webserver(void)
{
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
// Start the httpd server
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
if (httpd_start(&server, &config) == ESP_OK) {
// Registering the ws handler
ESP_LOGI(TAG, "Registering URI handlers");
httpd_register_uri_handler(server, &ws);
return server;
}
ESP_LOGI(TAG, "Error starting server!");
return NULL;
}
static void stop_webserver(httpd_handle_t server)
{
// Stop the httpd server
httpd_stop(server);
}
static void disconnect_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
httpd_handle_t* server = (httpd_handle_t*) arg;
if (*server) {
ESP_LOGI(TAG, "Stopping webserver");
stop_webserver(*server);
*server = NULL;
}
}
static void connect_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
httpd_handle_t* server = (httpd_handle_t*) arg;
if (*server == NULL) {
ESP_LOGI(TAG, "Starting webserver");
*server = start_webserver();
}
}
void app_main(void)
{
static httpd_handle_t server = NULL;
ESP_ERROR_CHECK(nvs_flash_init());//FLASH初始化
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.
*/
//对应头文件protocol_examples_common.h文件位于examples\common_components\protocol_examples_common\include下
//对应connect.c文件位于examples\common_components\protocol_examples_common下
ESP_ERROR_CHECK(example_connect());//启动连接网络
/* Register event handlers to stop the server when Wi-Fi or Ethernet is disconnected,
* and re-start it upon connection.
*/
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
//将连接和断开连接回调函数注册到系统事件循环
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &connect_handler, &server));
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, &disconnect_handler, &server));
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
/* Start the server for the first time */
server = start_webserver();
}
3.3 以上源码的简要说明
app_main 函数:
主任务函数,进入这个函数之前还要执行一些列操作。
ESP_ERROR_CHECK宏:
详见官方指南:错误处理 - ESP32 - — ESP-IDF 编程指南 v4.2 文档
主要作用会在控制台上打印错误消息,然后调用 abort()
函数,不是必须的,加上它有助于程序健壮性和调试。
nvs_flash_init 函数:
NVS FLASH存储器初始化,为了确定为什么必须加上此行代码,直接将其屏蔽掉,编译正常,下载运行监视窗口中反复打印启动信息(不断重启造成,可将鼠标在监视器窗口中点击然后按Ctrl+C终止),根据信息判断是WIFI初始化失败,由此看来初始化数据存储在了NVS FLASH存储器中。
esp_netif_init 函数:
用于网络接口初始化。
esp_event_loop_create_default 函数:
example_connect 函数:
函数定义位于connect.c文件中,用以执行WIFI或以太网连接。
esp_event_handler_register 函数:
ESP_LOGE - 记录错误
ESP_LOGI - 记录信息
以上记录日志函数都是将信息发送到串口,此外还有其它函数,具体可查看官方说明