ESP32——WebSocket Echo Server官方例程分析和测试

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 -  记录信息

以上记录日志函数都是将信息发送到串口,此外还有其它函数,具体可查看官方说明

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值