赞
踩
(1)、掌握 Websocket 原理和工作过程;
(2)、掌握ESP32 的 WebSocket 的程序设计;
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
(1)、WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算)。
有交集,但是并不是全部。另外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
当服务器完成协议升级后(HTTP->Websocket),服务端就可以主动推送信息给客户端啦
客户端:啦啦啦,我要建立Websocket协议,需要的服务:chat,Websocket协议版本:17(HTTP Request)
服务端:ok,确认,已升级为Websocket协议(HTTP Protocols Switched)
客户端:麻烦你有信息的时候推送给我噢。。
服务端:ok,有的时候会告诉你的。
服务端:balabalabalabala
服务端:balabalabalabala
服务端:哈哈哈哈哈啊哈哈哈哈
服务端:笑死我了哈哈哈哈哈哈哈
就变成了这样,只需要经过一次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
这里介绍大部分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);
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;
本实例使用的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; } }
所有文章源代码:https://download.csdn.net/download/lu330274924/88518092
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。