socket 才是众望所归, esp-idf 的 http 协议栈用的是 apache http, 一也就是 httpd, 如果我们可以使用 socket, 只要封装好了 readwrite 操作, 基本上就可以方便移植其他的协议栈。

当然使用 tls 是最好的,官方的tls会用到硬件加解密。然后在 tls 层上 read/write

image.png


#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_wifi.h"
#include "esp_wifi_default.h"
#include "esp_wifi_types_generic.h"
#include "freertos/FreeRTOS.h"
#include "freertos/idf_additions.h"
#include "freertos/task.h"
#include "lwip/sockets.h"
#include "nvs_flash.h"
#include <assert.h>
#include <lwip/netdb.h>
#include <stdio.h>

static const char *TAG = "TCP";
bool isConnected = false;

static void event_handler(void *arg, esp_event_base_t event_base,
                          int32_t event_id, void *event_data) {
  if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
    // STA开始工作, esp_wifi_connect 只会连接一次
    esp_wifi_connect();
  } else if (event_base == WIFI_EVENT &&
             event_id == WIFI_EVENT_STA_DISCONNECTED) {
    // 或许你可以引入超时机制
    esp_wifi_connect();
  } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
    ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
    ESP_LOGI("IP_EVENT", "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
    // 连接上了
    isConnected = true;
  }
}

void wifiInit() {
  // 创建互斥量
  isConnected = xSemaphoreCreateBinary();

  // 初始化 netif 和事件轮训
  ESP_ERROR_CHECK(esp_netif_init());
  ESP_ERROR_CHECK(esp_event_loop_create_default());

  // 默认配置设置wifi
  wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
  ESP_ERROR_CHECK(esp_wifi_init(&cfg));

  // 注册事件回调
  ESP_ERROR_CHECK(esp_event_handler_instance_register(
      WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, NULL));
  ESP_ERROR_CHECK(esp_event_handler_instance_register(
      IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, NULL));

  // 初始化 sta 和 netif, 个人感觉是为netif接入sta
  esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
  assert(sta_netif);
  ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));

  // sta 配置, 你要连接哪个wifi
  wifi_config_t wifi_config = {
      .sta =
          {
              .ssid = "Man_2.4GHz",
              .password = "liang114514",
          },
  };
  ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));

  // 开始干活
  ESP_ERROR_CHECK(esp_wifi_start());
}

static void do_retransmit(const int sock) {
  int len;
  char rx_buffer[128];

  do {
    // 依旧读取
    len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
    if (len < 0) {
      ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
    } else if (len == 0) {
      ESP_LOGW(TAG, "Connection closed");
    } else {
      rx_buffer[len] =
          0; // Null-terminate whatever is received and treat it like a string
      ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);

      // send() can return less bytes than supplied length.
      // Walk-around for robust implementation.
      int to_write = len;
      while (to_write > 0) {
        int written = send(sock, rx_buffer + (len - to_write), to_write, 0);
        if (written < 0) {
          ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
          // Failed to retransmit, giving up
          return;
        }
        to_write -= written;
      }
    }
  } while (len > 0);
}

static void tcp_server_task(void *pvParameters) {
  ESP_LOGI("SOCKET", "Start tcp server");
  char addr_str[128];
  int addr_family = (int)pvParameters;
  int ip_protocol = 0;
  int keepAlive = 1;
  int keepIdle = 5;
  int keepInterval = 5;
  int keepCount = 3;
  struct sockaddr_storage dest_addr;

  // 依旧 socket_addr
  if (addr_family == AF_INET) {
    struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;
    dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
    dest_addr_ip4->sin_family = AF_INET;
    dest_addr_ip4->sin_port = htons(3333);
    ip_protocol = IPPROTO_IP;
  }

  // 依旧 socket
  int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
  if (listen_sock < 0) {
    ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
    vTaskDelete(NULL);
    return;
  }

  // 设置 sockett 行为, SO_REUSEADDR允许套接字绑定到已在使用的地址和端口,
  // SOL_SOCKET表示套接字层
  int opt = 1;
  setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

  ESP_LOGI(TAG, "Socket created");

  // 绑定地址和端口
  int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
  if (err != 0) {
    ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
    ESP_LOGE(TAG, "IPPROTO: %d", addr_family);
    goto CLEAN_UP;
  }
  ESP_LOGI(TAG, "Socket bound, port %d", 3333);

  // 开始监听
  err = listen(listen_sock, 1);
  if (err != 0) {
    ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
    goto CLEAN_UP;
  }

  while (1) {

    ESP_LOGI(TAG, "Socket listening");

    struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
    socklen_t addr_len = sizeof(source_addr);
    int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
    if (sock < 0) {
      ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
      break;
    }

    // Set tcp keepalive option
    // 自个问AI, 我也不了解,设置socket选项保活
    setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));
    setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));
    setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));
    setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));
    // Convert ip address to string
    if (source_addr.ss_family == PF_INET) {
      inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str,
                  sizeof(addr_str) - 1);
    }

    ESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str);

    do_retransmit(sock);

    shutdown(sock, 0);
    close(sock);
  }

CLEAN_UP:
  close(listen_sock);
  vTaskDelete(NULL);
}

void app_main() {
  esp_err_t ret = nvs_flash_init();
  if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
      ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    ESP_ERROR_CHECK(nvs_flash_erase());
    ret = nvs_flash_init();
  }
  ESP_ERROR_CHECK(ret);
  wifiInit();
  while (!isConnected)
    ;
  // 和操作系统上操作差不多, 默认是阻塞的
  xTaskCreate(tcp_server_task, "tcp_server", 4096, (void *)AF_INET, 5, NULL);
}

浏览器访问地址,默认是HTTP请求过来。

image.png

socat 连接

image.png

实际上我将 setsockopt 注释也能正常工作, 流个概念把,知道怎么设置socket工作选项,

tls是作为组件加入项目的, 使用方法和 openssl 类似, 也是建立 socket 后通过绑定 ssl_ctxsocket_fd 使用