socket 才是众望所归, esp-idf 的 http 协议栈用的是 apache http, 一也就是 httpd, 如果我们可以使用 socket, 只要封装好了 read
和 write
操作, 基本上就可以方便移植其他的协议栈。
当然使用 tls
是最好的,官方的tls会用到硬件加解密。然后在 tls
层上 read/write
#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请求过来。
socat 连接
实际上我将 setsockopt
注释也能正常工作, 流个概念把,知道怎么设置socket工作选项,
tls是作为组件加入项目的, 使用方法和 openssl
类似, 也是建立 socket
后通过绑定 ssl_ctx
和 socket_fd
使用