两图看懂websocket和http的区别,websocket如何通过Http get方法创建

Http原理

HTTP是基于客户/服务器模式,且面向连接的。典型的HTTP事务处理有如下的过程:
(1)客户与服务器建立连接;
(2)客户向服务器提出请求;
(3)服务器接受请求,并根据请求返回相应的文件作为应答;
(4)客户与服务器关闭连接。

Http特点
被动型,客户端请求->服务端响应。服务端不能主动向客户端发送数据。
在这里插入图片描述

WebSocket

WebSocket是一种全双工通信。服务端和客户端都能主动向对方发送数据。只要一次握手成功,后续就可以不断的收发数据(在程序设计中,这种设计叫做回调),客户端也不用一直查询服务器状态等信息,只需要等服务器主动发送数据过来就可以了。
WebSocket比Http具有更好的实时性(不需要客户端通过Http长链接不断轮询处理结果)。
WebSocket需要的资源比Http更少。如果是Http短连接,Http需要不断地和服务端握手,发送数据,服务器收到数据后,需要处理(耗时、耗资源),才能返回数据给客户端。如果是长链接,需要不断轮询,才能得到处理结果。而WebSocket,只需等待服务器处理完成,服务器就会向客户端发送数据,不需要重新握手,协议头等相关的数据也更少(数据传输效率更高)。
协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。如:ws://example.com:80/some/path
WebSocket兼容Http,支持旧版本的浏览器访问。
在这里插入图片描述

websocket是通过Http get方法创建的

static const httpd_uri_t ws = {
.uri = “/ws”, //协议标识符
.method = HTTP_GET, //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); //创建WebSocketServer
return server;
}

ESP_LOGI(TAG, "Error starting server!");
return NULL;

}

websocket sever回复client的握手信息

ESP32 server在httpd_start(&server, &config)函数,最终调用httpd_ws.c 中的函数httpd_ws_respond_server_handshake(httpd_req_t *req, const char *supported_subprotocol)完成握手信息
下图是server需要回复的握手信息,包含版本和密匙等:
在这里插入图片描述
下图是httpd_ws_respond_server_handshake(httpd_req_t *req, const char *supported_subprotocol)回复的握手信息代码:
在这里插入图片描述

ESP32创建WebSocket完整代码如下:

此代码在D:\Espressif\frameworks\esp-idf-v4.4.2\examples\protocols\http_server\ws_echo_server\main
在这里插入图片描述

/* 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 “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)
    {
    if (req->method == HTTP_GET) {
    ESP_LOGI(TAG, “Handshake done, the new connection was opened”);
    return ESP_OK;
    }
    httpd_ws_frame_t ws_pkt;
    uint8_t buf = NULL;
    memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
    ws_pkt.type = HTTPD_WS_TYPE_TEXT;
    /
    Set max_len = 0 to get the frame len /
    esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
    if (ret != ESP_OK) {
    ESP_LOGE(TAG, “httpd_ws_recv_frame failed to get frame len with %d”, ret);
    return ret;
    }
    ESP_LOGI(TAG, “frame len is %d”, ws_pkt.len);
    if (ws_pkt.len) {
    /
    ws_pkt.len + 1 is for NULL termination as we are expecting a string /
    buf = calloc(1, ws_pkt.len + 1);
    if (buf == NULL) {
    ESP_LOGE(TAG, “Failed to calloc memory for buf”);
    return ESP_ERR_NO_MEM;
    }
    ws_pkt.payload = buf;
    /
    Set max_len = ws_pkt.len to get the frame payload /
    ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
    if (ret != ESP_OK) {
    ESP_LOGE(TAG, “httpd_ws_recv_frame failed with %d”, ret);
    free(buf);
    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) {
    free(buf);
    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);
    }
    free(buf);
    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) { // ESP32 serer在httpd_start(&server, &config)函数,最终调用httpd_ws.c 中的函数httpd_ws_respond_server_handshake()完成握手信息
    // 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 esp_err_t stop_webserver(httpd_handle_t server)
{
// Stop the httpd server
return 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”);
if (stop_webserver(*server) == ESP_OK) {
*server = NULL;
} else {
ESP_LOGE(TAG, “Failed to stop http server”);
}
}
}

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());
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.
 */
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();

}

httpd_ws.c部分代码

此代码在D:\Espressif\frameworks\esp-idf-v4.4.2\components\esp_http_server\src
在这里插入图片描述

esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *req, const char supported_subprotocol)
{
/
Probe if input parameters are valid or not */
if (!req || !req->aux) {
ESP_LOGW(TAG, LOG_FMT(“Argument is invalid”));
return ESP_ERR_INVALID_ARG;
}

/* Detect handshake - reject if handshake was ALREADY performed */
struct httpd_req_aux *req_aux = req->aux;
if (req_aux->sd->ws_handshake_done) {
    ESP_LOGW(TAG, LOG_FMT("State is invalid - Handshake has been performed"));
    return ESP_ERR_INVALID_STATE;
}

/* Detect WS version existence */
char version_val[3] = { '\0' };
if (httpd_req_get_hdr_value_str(req, "Sec-WebSocket-Version", version_val, sizeof(version_val)) != ESP_OK) {
    ESP_LOGW(TAG, LOG_FMT("\"Sec-WebSocket-Version\" is not found"));
    return ESP_ERR_NOT_FOUND;
}

/* Detect if WS version is "13" or not.
 * WS version must be 13 for now. Please refer to RFC6455 Section 4.1, Page 18 for more details. */
if (strcasecmp(version_val, "13") != 0) {
    ESP_LOGW(TAG, LOG_FMT("\"Sec-WebSocket-Version\" is not \"13\", it is: %s"), version_val);
    return ESP_ERR_INVALID_VERSION;
}

/* Grab Sec-WebSocket-Key (client key) from the header */
/* Size of base64 coded string is equal '((input_size * 4) / 3) + (input_size / 96) + 6' including Z-term */
char sec_key_encoded[28] = { '\0' };
if (httpd_req_get_hdr_value_str(req, "Sec-WebSocket-Key", sec_key_encoded, sizeof(sec_key_encoded)) != ESP_OK) {
    ESP_LOGW(TAG, LOG_FMT("Cannot find client key"));
    return ESP_ERR_NOT_FOUND;
}

/* Prepare server key (Sec-WebSocket-Accept), concat the string */
char server_key_encoded[33] = { '\0' };
uint8_t server_key_hash[20] = { 0 };
char server_raw_text[sizeof(sec_key_encoded) + sizeof(ws_magic_uuid) + 1] = { '\0' };

strcpy(server_raw_text, sec_key_encoded);
strcat(server_raw_text, ws_magic_uuid);

ESP_LOGD(TAG, LOG_FMT("Server key before encoding: %s"), server_raw_text);

/* Generate SHA-1 first and then encode to Base64 */
size_t key_len = strlen(server_raw_text);
mbedtls_sha1_ret((uint8_t *)server_raw_text, key_len, server_key_hash);

size_t encoded_len = 0;
mbedtls_base64_encode((uint8_t *)server_key_encoded, sizeof(server_key_encoded), &encoded_len,
                      server_key_hash, sizeof(server_key_hash));

ESP_LOGD(TAG, LOG_FMT("Generated server key: %s"), server_key_encoded);

char subprotocol[50] = { '\0' };
if (httpd_req_get_hdr_value_str(req, "Sec-WebSocket-Protocol", subprotocol, sizeof(subprotocol) - 1) == ESP_ERR_HTTPD_RESULT_TRUNC) {
    ESP_LOGW(TAG, "Sec-WebSocket-Protocol length exceeded buffer size of %d, was trunctated", sizeof(subprotocol));
}


/* Prepare the Switching Protocol response */
char tx_buf[192] = { '\0' };
int fmt_len = snprintf(tx_buf, sizeof(tx_buf),
                       "HTTP/1.1 101 Switching Protocols\r\n"
                       "Upgrade: websocket\r\n"
                       "Connection: Upgrade\r\n"
                       "Sec-WebSocket-Accept: %s\r\n", server_key_encoded);

if (fmt_len < 0 || fmt_len > sizeof(tx_buf)) {
    ESP_LOGW(TAG, LOG_FMT("Failed to prepare Tx buffer"));
    return ESP_FAIL;
}

if ( httpd_ws_get_response_subprotocol(supported_subprotocol, subprotocol, sizeof(subprotocol))) {
    ESP_LOGD(TAG, "subprotocol: %s", subprotocol);
    int r = snprintf(tx_buf + fmt_len, sizeof(tx_buf) - fmt_len, "Sec-WebSocket-Protocol: %s\r\n", supported_subprotocol);
    if (r <= 0) {
        ESP_LOGE(TAG, "Error in response generation"
                      "(snprintf of subprotocol returned %d, buffer size: %d", r, sizeof(tx_buf));
        return ESP_FAIL;
    }

    fmt_len += r;

    if (fmt_len >= sizeof(tx_buf)) {
        ESP_LOGE(TAG, "Error in response generation"
                      "(snprintf of subprotocol returned %d, desired response len: %d, buffer size: %d", r, fmt_len, sizeof(tx_buf));
        return ESP_FAIL;
    }
}

int r = snprintf(tx_buf + fmt_len, sizeof(tx_buf) - fmt_len, "\r\n");
if (r <= 0) {
    ESP_LOGE(TAG, "Error in response generation"
                    "(snprintf of subprotocol returned %d, buffer size: %d", r, sizeof(tx_buf));
    return ESP_FAIL;
}
fmt_len += r;
if (fmt_len >= sizeof(tx_buf)) {
    ESP_LOGE(TAG, "Error in response generation"
                   "(snprintf of header terminal returned %d, desired response len: %d, buffer size: %d", r, fmt_len, sizeof(tx_buf));
    return ESP_FAIL;
}

/* Send off the response */
if (httpd_send(req, tx_buf, fmt_len) < 0) {
    ESP_LOGW(TAG, LOG_FMT("Failed to send the response"));
    return ESP_FAIL;
}

return ESP_OK;

}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值