ESP32 IDF开发 应用篇⑳ WebSocket

ESP32 IDF开发 应用篇⑳ WebSocket

    • 1、博主写这篇技术文章的目的:
    • 2、概述
    • 3、 websocket与http
    • 4、Websocket建立连接
    • 5、Websocket相关API的介绍
    • 6、软件设计
    • 7、实例
    • 8、调试结果

别迷路-导航栏
快速导航找到你想要的(文章目录)

此篇文章如果对你有用,请点赞收藏,您的支持就是博主坚持的动力。

1、博主写这篇技术文章的目的:

(1)、掌握 Websocket 原理和工作过程;
(2)、掌握ESP32 的 WebSocket 的程序设计;

2、概述

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

3、 websocket与http

(1)、WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算)。
ESP32 IDF开发 应用篇⑳ WebSocket_第1张图片

有交集,但是并不是全部。另外Html5是指的一系列新的API,或者说新规范,新技术。
(2)、请求握手包
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
(3)、接收请求包
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat
这里开始就是HTTP最后负责的区域了,告诉客户,我已经成功切换协议啦~
Upgrade: websocket
Connection: Upgrade

4、Websocket建立连接

当服务器完成协议升级后(HTTP->Websocket),服务端就可以主动推送信息给客户端啦
客户端:啦啦啦,我要建立Websocket协议,需要的服务:chat,Websocket协议版本:17(HTTP Request)
服务端:ok,确认,已升级为Websocket协议(HTTP Protocols Switched)
客户端:麻烦你有信息的时候推送给我噢。。
服务端:ok,有的时候会告诉你的。
服务端:balabalabalabala
服务端:balabalabalabala
服务端:哈哈哈哈哈啊哈哈哈哈
服务端:笑死我了哈哈哈哈哈哈哈

ESP32 IDF开发 应用篇⑳ WebSocket_第2张图片

就变成了这样,只需要经过一次HTTP请求,就可以做到源源不断的信息传送了。(在程序设计中,这种设计叫做回调,即:你有信息了再来通知我,而不是我傻乎乎的每次跑来问你 )这样的协议解决了上面同步有延迟,而且还非常消耗资源的这种情况。那么为什么他会解决服务器上消耗资源的问题呢?
其实我们所用的程序是要经过两层代理的,即HTTP协议在Nginx等服务器的解析下,然后再传送给相应的Handler(PHP等)来处理。简单地说,我们有一个非常快速的 接线员(Nginx) ,他负责把问题转交给相应的 客服(Handler) 。
本身接线员基本上速度是足够的,但是每次都卡在客服(Handler)了,老有客服处理速度太慢。,导致客服不够。Websocket就解决了这样一个难题,建立后,可以直接跟接线员建立持久连接,有信息的时候客服想办法通知接线员,然后接线员在统一转交给客户。
这样就可以解决客服处理速度过慢的问题了。
同时,在传统的方式上,要不断的建立,关闭HTTP协议,由于HTTP是非状态性的,每次都要重新传输 identity info (鉴别信息),来告诉服务端你是谁。
虽然接线员很快速,但是每次都要听这么一堆,效率也会有所下降的,同时还得不断把这些信息转交给客服,不但浪费客服的处理时间,而且还会在网路传输中消耗过多的流量/时间。
但是Websocket只需要一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的非状态性,服务端会一直知道你的信息,直到你关闭请求,这样就解决了接线员要反复解析HTTP协议,还要查看identity info的信息。
同时由客户主动询问,转换为服务器(推送)有信息的时候就发送(当然客户端还是等主动发送信息过来的。。),没有信息的时候就交给接线员(Nginx),不需要占用本身速度就慢的客服(Handler)了
注:详细讲解参考https://www.cnblogs.com/fuqiang88/p/5956363.html

5、Websocket相关API的介绍

这里介绍大部分Websocket库的使用更详细的请参考:
更多API 参考esp-idf\components\esp_websocket_client\include\esp_websocket_client.h
Websocket API和http API的封装类似使用起来也似曾相识。
跟esp_http_client主要是对esp_websocket_client_config_t结构体的操作。

(1)、启动Websocket会话

/**
  * @brief 启动Websocket会话
  *此函数必须是第一个要调用的函数,
  *它返回一个esp_websocket_client_handle_t句柄。
  *操作完成后,此调用必须对esp_websocket_client_destroy进行相应的调用。
  *
  * @param [in] config配置
  *
  * @return
  *-`esp_websocket_client_handle_t`
  *-如果有任何错误,则为NULL
  */
esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config);

(2)、注册回调函数类似于wifi连接过程的回调函数,此函数使用WEBSOCKET_EVENT_ANY事件

esp_err_t esp_websocket_register_events(esp_websocket_client_handle_t client,
                                        esp_websocket_event_id_t event,
                                        esp_event_handler_t event_handler,
                                        void *event_handler_arg);

(3)、启动websocket_client,创建了一个esp_websocket_client_task任务

/**
 * @brief      打开WebSocket连接
 *
 * @param[in]  client  客户端句柄
 *
 * @return     esp_err_t
 */
esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client);

esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client)
{
。。。。
    if (xTaskCreate(esp_websocket_client_task, "websocket_task", client->config->task_stack, client, client->config->task_prio, NULL) != pdTRUE) {
。。。。
    return ESP_OK;
}

(4)、最后解释连接写函数,读取数据在回调函数中获取数据

/**
 * @brief     检查WebSocket连接状态
 *
 * @param[in]  client  客户端句柄
 *
 * @return
 *     - true
 *     - false
 */
bool esp_websocket_client_is_connected(esp_websocket_client_handle_t client);
/**
 * @brief      将通用数据写入WebSocket连接; 默认为二进制发送
 *
 * @param[in]  client  The client
 * @param[in]  data    The data
 * @param[in]  len     The length
 * @param[in]  timeout Write data timeout in RTOS ticks
 *
 * @return
 *     - Number of data was sent
 *     - (-1) if any errors
 */
int esp_websocket_client_send(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout);

6、软件设计

websocket_client程序的实现主要是在esp_websocket_client.c文件中实现,这里主要是使用其中的API即可,其操作也主要是围绕esp_websocket_client_config_t结构体的操作。先来看看结构体几个重要的成员描述:

/**
 * MQTT client configuration structure
 */
typedef struct {
    mqtt_event_callback_t event_handle;     /*!< handle for MQTT events as a callback in legacy mode */
    esp_event_loop_handle_t event_loop_handle; /*!< handle for MQTT event loop library */
    const char *host;                       /*!< MQTT server domain (ipv4 as string) */
    const char *uri;                        /*!< Complete MQTT broker URI */
    uint32_t port;                          /*!< MQTT server port */
    const char *client_id;                  /*!< default client id is ``ESP32_%CHIPID%`` where %CHIPID% are last 3 bytes of MAC address in hex format */
    const char *username;                   /*!< MQTT username */
    const char *password;                   /*!< MQTT password */
 
} esp_mqtt_client_config_t;

这个结构体跟esp_http_client_config_t结构体类似。
①、初始化结构体创建句柄

esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);

②、注册回调函数并启动

  //注册websocket回调函数用于检测执行过程中的一些状态,跟wifi连接回调函数一样
    esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client);
    //启动websocket任务,开始检测回调状态
    esp_websocket_client_start(client);

③、发送数据

//将通用数据写入WebSocket连接; 默认为二进制发送
esp_websocket_client_send(client, data, len, portMAX_DELAY);

④、从服务端接收数据,这个在回调函数中处理

static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
    switch (event_id) {
    case WEBSOCKET_EVENT_CONNECTED:
        ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
        break;
    case WEBSOCKET_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED");
        break;
    case WEBSOCKET_EVENT_DATA:
        ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA");
        ESP_LOGI(TAG, "Received opcode=%d", data->op_code);
        ESP_LOGW(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr);
        ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset);
        break;
    case WEBSOCKET_EVENT_ERROR:
        ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR");
        break;
    }
}

接收数据结构体

/**
 * @brief Websocket事件数据
 */
typedef struct {
    const char *data_ptr;                   /*!< 数据指针 */
    int data_len;                           /*!< Data length */
    uint8_t op_code;                        /*!< 收到操作码 */
    esp_websocket_client_handle_t client;   /*!< esp_websocket_client_handle_t context */
    void *user_context;                     /*!< user_data context, from esp_websocket_client_config_t user_data */
    int payload_len;                        /*!< 包长度 */
    int payload_offset;                     /*!< 与此事件关联的数据的实际偏移量 */
} esp_websocket_event_data_t;

7、实例

本实例使用的url是官网提供的测试连接ws://echo.websocket.org
源码如下:
在wifi连接成功后开始调用测试函数

static void vTaskWebSocket(void *pvParameters)
{
    //等待连接成功,或已经连接有断开连接,此函数会一直阻塞,只有有连接
   xEventGroupWaitBits(xCreatedEventGroup_WifiConnect,// 事件标志组句柄 
        WIFI_CONNECTED_BIT, // 等待bit0和bit1被设置 
        pdFALSE,             //TRUE退出时bit0和bit1被清除,pdFALSE退出时bit0和bit1不清除
        pdTRUE,             //设置为pdTRUE表示等待bit1和bit0都被设置,pdFALSE表示等待bit1或bit0其中一个被设置
        portMAX_DELAY);      //等待延迟时间,一直等待 
    websocket_test();
    ESP_LOGI(TAG, "Finish websocket Test");
    vTaskDelete(NULL);
}
static void websocket_test(void)
{
    //Websocket客户端设置配置
    esp_websocket_client_config_t websocket_cfg = {};
    //填充url
    websocket_cfg.uri = "ws://echo.websocket.org";
    ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri);
    //创建websocket句柄
    esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
    //注册websocket回调函数用于检测执行过程中的一些状态,跟wifi连接回调函数一样
    esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client);
    //启动websocket任务,开始检测回调状态
    esp_websocket_client_start(client);

    char data[32];
    int i = 0;
    while (i < 10)
    {
        //检查WebSocket连接状态
        if (esp_websocket_client_is_connected(client)) 
        {
            int len = sprintf(data, "hello %04d", i++);
            ESP_LOGI(TAG, "Sending %s", data);
            //将通用数据写入WebSocket连接; 默认为二进制发送
            esp_websocket_client_send(client, data, len, portMAX_DELAY);
        }
        vTaskDelay(1000 / portTICK_RATE_MS);
    }
    esp_websocket_client_stop(client);
    ESP_LOGI(TAG, "Websocket Stopped");
    esp_websocket_client_destroy(client);
}

回调函数

static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
    switch (event_id) {
    case WEBSOCKET_EVENT_CONNECTED:
        ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
        break;
    case WEBSOCKET_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED");
        break;
    case WEBSOCKET_EVENT_DATA:
        ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA");
        ESP_LOGI(TAG, "Received opcode=%d", data->op_code);
        ESP_LOGW(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr);
        ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset);
        break;
    case WEBSOCKET_EVENT_ERROR:
        ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR");
        break;
    }
}

8、调试结果

ESP32 IDF开发 应用篇⑳ WebSocket_第3张图片

所有文章源代码:https://download.csdn.net/download/lu330274924/88518092

你可能感兴趣的:(ESP32,IDF小白到大师实战,websocket,网络协议,网络)