ESP-C3入门9. 创建TCP Server

Posted 编程圈子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ESP-C3入门9. 创建TCP Server相关的知识,希望对你有一定的参考价值。

ESP-C3入门9. 创建TCP Server

一、ESP32 IDF的TCP/IP协议栈

TCP/IP协议栈是ESP32 IDF的一个核心组件。它实现了TCP、UDP、IP、DHCP、DNS和其他网络协议,使ESP32可以与其他设备通信。具体来说,ESP32 IDF的TCP/IP协议栈包括以下几个主要模块:

  1. WiFi协议栈
  2. TCP/IP协议栈
  3. LWIP协议栈
  4. SPI Flash文件系统
    通过TCP/IP协议栈,ESP32可以轻松地实现各种网络应用,例如HTTP服务器、MQTT客户端、TCP/UDP服务器和客户端等等。同时,ESP32 IDF提供了丰富的API和组件,使得开发人员可以快速地实现自己的应用程序。

ESP32主要使用LwIP协议栈,支持函数:

  • BSD风格 Sockets API
  • Netconn API (未正式启用)

官方文档提示:
在使用任何lwIP API(除BSD Sockets API之外)时,请确保它是线程安全的。
要检查特定的API调用是否安全,请启用CONFIG_LWIP_CHECK_THREAD_SAFETY并运行应用程序。这样,lwIP会断言TCP/IP核心功能被正确访问,如果没有正确锁定或从正确的任务(lwIP FreeRTOS任务)访问,则执行中止。
一般建议使用ESP-NETIF组件与lwIP交互。

ESP-IDF对lwIP进行了一些封装,以间接支持部分功能,如:

  • DHCP
  • SNTP
  • ICMP Ping
  • NetBios查找可使用的lwIP API
  • mDNS
  • 串行PPP接口
    等。

二、BSD套接字API介绍

BSD Sockets API 是一个常见的跨平台TCP/IP套接字API, 有时被称为POSIX Sockets或 Berkeley Sockets。

lwIP支持BSD Sockets API的所有常见用法,一些功能如下:

  • socket()
  • bind()
  • accept()
  • shutdown()
  • getpeername()
  • getsockopt()
  • setsockopt()
  • close()
  • read(), readv() write() writev()
  • recv() recvmsg() recfrom()
  • send() sendmsg() sendto()
  • select()
  • poll()
  • fcntl()
    另外lwIP还支持ioctl()非标准的功能。

三、创建TCP Server的步骤

1. 引用TCP/IP协议栈

在CMakeLists.txt包含以下内容:


idf_component_register(SRCS "your_source_files.c"
                    INCLUDE_DIRS "include"
                    REQUIRES "tcpip_adapter")

2. 创建 TCP套接字拼绑定端口

创建一个TCP套接字,并将其绑定到指定的端口。然后使用listen()函数将服务器套接字设置为侦听连接。

#include <string.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netdb.h>
#include <lwip/sockets.h>

#define PORT CONFIG_EXAMPLE_PORT

static struct sockaddr_in server_addr;
static int server_socket = -1;

void create_tcp_server() 
    server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if (server_socket < 0) 
        ESP_LOGE(TAG, "Failed to create server socket!");
        return;
    

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);

    int err = bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (err != 0) 
        ESP_LOGE(TAG, "Failed to bind server socket!");
        close(server_socket);
        return;
    

    err = listen(server_socket, 1);
    if (err != 0) 
        ESP_LOGE(TAG, "Failed to listen on server socket!");
        close(server_socket);
        return;
    

    ESP_LOGI(TAG, "TCP server started on port %d", PORT);


3. 接收客户端请求

使用accept()函数从服务器套接字接受新连接。
下面代码里,创建一个TCP客户端,使用结构体client_addr存储客户端的信息。

  • 接收到一般消息时,自动原文回复;
  • 收到ping时,回复pong;
  • 收到bye时,回复bye并关闭连接。

关闭连接后,程序会重新开始等待新连接。

void process_tcp_client()
    struct sockaddr_in client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
    if(client_socket <0)
        ESP_LOGE(TAG, "Failed to accept client socket!");
        return;
    
    ESP_LOGI(TAG, "Accepted new client connection");
    // 处理客户端请求
    bool end=false;
    while (!end) 
        char buffer[1024];
        // 初始化数组
        memset(buffer, 0, sizeof(buffer));
        int len = recv(client_socket, buffer, sizeof(buffer), 0);
        if (len > 0) 
            const char *response;
            if (strncmp(buffer, "ping", 4) == 0) 
                response = "pong";
                ESP_LOGI(TAG, "Received ping, sending pong");
             else if (strncmp(buffer, "bye", 3) == 0) 
                response = "bye";
                ESP_LOGI(TAG, "Received bye, closing connection");
                end = true;
             else 
                response = buffer;
                ESP_LOGI(TAG, "Received data: %s", response);
            
            send(client_socket, response, strlen(response), 0);

        
    
    close(client_socket);

4. 启动服务

在应用程序的main()函数中调用create_tcp_server()函数来启动TCP服务器

void app_main() 
    create_tcp_server();
    while (1) 
        process_tcp_client();
    


四、完整代码

本文代码会基于前一章内容实现,将wifi连接部分独立文件放。
项目框架:

1. wifi.h

#ifndef WIFI_LIB
#define WIFI_LIB
#include <string.h>
/* 宏定义 */
#include <esp_event_base.h>

#include "esp_log.h"

#define LIGHT_ESP_WIFI_SSID     "你的wifi账号"
#define LIGHT_ESP_WIFI_PASS     "你的wifi密码"
#define LIGHT_ESP_MAXIMUM_RETRY 5

// 事件组允许每个事件有多个位,这里只使用两个事件:
// 已经连接到AP并获得了IP
// 在最大重试次数后仍未连接
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1


/* 函数声明 */
void wifi_initialize(void);
void wifi_station_initialize(void);
void event_handler(void *arg, esp_event_base_t event_base,
                          int32_t event_id, void *event_data);

#endif /* WIFI_LIB */

2. wifi.c

#include <freertos/FreeRTOS.h>
#include "main/network/include/wifi.h"
#include <freertos/event_groups.h>
#include <esp_wifi.h>

static const char *TAG = "wifi lib";

// FreeRTOS事件组,连接成功时发出信号
static EventGroupHandle_t s_wifi_event_group = NULL;
static int s_retry_num = 0;

// 事件回调
void event_handler(void *arg, esp_event_base_t event_base,
                          int32_t event_id, void *event_data)

    // 如果是Wi-Fi事件,并且事件ID是Wi-Fi事件STA_START
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) 
        esp_wifi_connect();
     else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) 
        // 如果是Wi-Fi事件,并且事件ID是Wi-Fi事件STA_DISCONNECTED
        /* 如果重试次数小于最大重试次数 */
        if (s_retry_num < LIGHT_ESP_MAXIMUM_RETRY) 
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
         else 
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        
        ESP_LOGI(TAG, "connect to the AP fail");
     else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) 
        // 如果是IP事件,并且事件ID是IP事件STA_GOT_IP

        // 获取事件结果
        ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));

        // 将重试次数重置为 0;
        s_retry_num = 0;
        // 通过调用 xEventGroupSetBits 函数,将 WIFI_CONNECTED_BIT 设置到事件组中,表示成功连接到 AP
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    


void wifi_initialize(void)

    // 创建一个事件组,用于管理Wi-Fi连接事件。
    s_wifi_event_group = xEventGroupCreate();

    // 初始化 TCP/IP 协议栈。
    ESP_ERROR_CHECK(esp_netif_init());

    // 创建默认事件循环。
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // 创建默认的Wi-Fi网络接口。
    esp_netif_create_default_wifi_sta();
    // 设置 Wi-Fi 初始化配置为默认配置
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    // 注册事件处理器,以处理 Wi-Fi 和 IP 相关事件
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));


void wifi_station_initialize(void)

    // 配置WiFi的配置信息
    wifi_config_t wifi_config = 
            .sta = 
                    .ssid = LIGHT_ESP_WIFI_SSID,
                    .password = LIGHT_ESP_WIFI_PASS,
                    // 启用WPA2模式,常用的WiFi连接方式
                    .threshold.authmode = WIFI_AUTH_WPA2_PSK,

                    .pmf_cfg = 
                            .capable = true,
                            .required = false
                    ,
            ,
    ;
    // WiFi工作模式设置为STA
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    // 设置WiFi工作模式
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    // 启动WiFi
    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_LOGI(TAG, "wifi_station_initialize finished.");

    /* 等待连接建立(WIFI_CONNECTED_BIT)或连接失败的次数达到最大值(WIFI_FAIL_BIT)。
     * 这些位通过 event_handler() 设置(详见上面)*/
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);

    /* xEventGroupWaitBits() 返回调用前的 bits,因此我们可以测试实际发生了什么事件。 */
    if (bits & WIFI_CONNECTED_BIT) 
        ESP_LOGI(TAG, "connected to ap SSID:%s password:%s", LIGHT_ESP_WIFI_SSID, LIGHT_ESP_WIFI_PASS);
     else if (bits & WIFI_FAIL_BIT) 
        ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s", LIGHT_ESP_WIFI_SSID, LIGHT_ESP_WIFI_PASS);
     else 
        ESP_LOGE(TAG, "UNEXPECTED EVENT");
    

3. tcpServer.h

#ifndef TCP_SERVER
#define TCP_SERVER
#include <string.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netdb.h>
#include <lwip/sockets.h>

#define PORT 3000
void create_tcp_server();
void process_tcp_client();
#endif

4. tcpServer.c

//
// 
//
#include <esp_log.h>
#include "tcpServer.h"
static struct sockaddr_in server_addr;
static int server_socket = -1;
static const char* TAG = "TCP SERVER";
void create_tcp_server()
    server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if(server_socket<0)
        ESP_LOGE(TAG, "Failed to create server socket!");
        return;
    

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);

    int err = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if(err !=0)
        ESP_LOGE(TAG, "Failed to bind server socket!");
        close(server_socket);
        return;
    
    err = listen(server_socket, 1);
    if(err != 0)
        ESP_LOGE(TAG, "Failed to listen on server socket!");
        close(server_socket);
        return;
    
    ESP_LOGI(TAG, "TCP server started on port %d", PORT);


void process_tcp_client()
    struct sockaddr_in client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
    if(client_socket <0)
        ESP_LOGE(TAG, "Failed to accept client socket!");
        return;
    
    ESP_LOGI(TAG, "Accepted new client connection");
    // 处理客户端请求
    bool end=false;
    while (!end) 
        char buffer[1024];
        // 初始化数组
        memset(buffer, 0, sizeof(buffer));
        int len = recv(client_socket, buffer, sizeof(buffer), 0);
        if (len > 0) 
            const char *response;
            if (strncmp(buffer, "ping", 4) == 0) 
                response = "pong";
                ESP_LOGI(TAG, "Received ping, sending pong");
             else if (strncmp(buffer, "bye", 3) == 0) 
                response = "bye";
                ESP_LOGI(TAG, "Received bye, closing connection");
                end = true;
             else 
                response = buffer;
                ESP_LOGI(TAG, "Received data: %s", response);
            
            send(client_socket, response, strlen(response), 0);

        
    
    close(client_socket);

5. main.c

#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include <nvs_flash.h>
#include "network/include/wifi.h"
#include "network/include/tcpServer.h"
static const char *TAG = "wifi connection";

void app_main()

    int i = 0;
    ESP_LOGE(TAG, "app_main");
    // 初始化NVS存储区
    ESP_ERROR_CHECK(nvs_flash_init());

    // Wi-Fi初始化
    ESP_LOGI(TAG, "Wi-Fi initialization");
    wifi_initialize();

    // Wi-Fi Station初始化
    wifi_station_initialize();

    // 创建 tcp server
    create_tcp_server();

    while (1) 
        ESP_LOGI(TAG, "[%02d] Wait Client Connection!", i++);
        process_tcp_client();
        vTaskDelay(pdMS_TO_TICKS(5000));
    


6. CmakeLists.txt

# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
以上是关于ESP-C3入门9. 创建TCP Server的主要内容,如果未能解决你的问题,请参考以下文章

ESP-C3入门10. 创建TCP Client

ESP-C3入门10. 创建TCP Client

ESP-C3入门11. 创建最基本的HTTP请求

ESP-C3入门11. 创建最基本的HTTP请求

ESP-C3入门14. 实现基本的web server

ESP-C3入门14. 实现基本的web server